diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 7959251..7cb144a 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -23,7 +23,7 @@ jobs: - name: Initialize Submodules uses: snickerbockers/submodules-init@v4 - name: Install dependencies - run: sudo apt-get install -y flex bison make + run: sudo apt-get install -y flex bison make flexc++ bisonc++ libbison-dev libfl-dev cmake - name: Create Build Environment run: cmake -E make_directory ${{github.workspace}}/build - name: Configure CMake diff --git a/.gitignore b/.gitignore index 526969c..4bda837 100644 --- a/.gitignore +++ b/.gitignore @@ -36,11 +36,13 @@ cmake-build-* .idea build bin +Debug/ +Release/ +Testing/ # editor things .emacs* .DS_Store compile_commands.json -Debug/ .cache/ diff --git a/.vscode/launch.json b/.vscode/launch.json index 803d83d..e3043da 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,6 +1,16 @@ { "version": "0.2.0", "configurations": [ + { + "name": "Launch demo x:=0_ms", + "type": "codelldb", + "request": "launch", + "program": "${workspaceFolder}/Debug/expr_demo", + "args": [ + "-e", "x:=0_ms" + ], + "cwd": "${workspaceFolder}/Debug" + }, { "name": "Launch interpreter x := 0_ms;", "type": "cppdbg", diff --git a/CMakeLists.txt b/CMakeLists.txt index 8af4702..8ac89bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,10 +19,8 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -cmake_minimum_required(VERSION 3.21) -# to generate a changelog, use: (python3 -m pip install git-changelog) -# $ git-changelog -s conventional . -o CHANGELOG.MD -project(expr VERSION 2.2.0) +cmake_minimum_required(VERSION 3.18) +project(expr VERSION 3.0.0) include(cmake/CPM.cmake) configure_file(src/config.h.in config.h) set(CMAKE_CXX_STANDARD 20) @@ -32,10 +30,12 @@ set(CXX_STANDARD_REQUIRED ON) option(ENABLE_Z3 "Enables the download and compilation of the expr::z3_driver driver. OFF by default" OFF) # DEPENDENCIES +# library dependencies CPMAddPackage("gh:yalibs/yaoverload@1.0.0") +CPMAddPackage("gh:yalibs/yahashcombine@1.0.0") CPMAddPackage("gh:yalibs/yatree@1.2.1") +# demo dependencies CPMAddPackage("gh:yalibs/yatimer@1.0.0") -CPMAddPackage("gh:yalibs/yahashcombine@1.0.0") CPMAddPackage("gh:sillydan1/argvparse@1.2.3") if(ENABLE_Z3) @@ -45,56 +45,53 @@ if(ENABLE_Z3) endif() set(${PROJECT_NAME}_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR} CACHE STRING "expr_BUILD_DIR" FORCE) -find_package(FLEX REQUIRED) -find_package(BISON REQUIRED) - -add_custom_command(OUTPUT - ${CMAKE_CURRENT_BINARY_DIR}/lex.l - COMMAND m4 - ARGS -I ${PROJECT_SOURCE_DIR}/src/parser/lex -P ${PROJECT_SOURCE_DIR}/src/parser/lex/scanner.l > ${CMAKE_CURRENT_BINARY_DIR}/lex.l - VERBATIM) -add_custom_command(OUTPUT - ${CMAKE_CURRENT_BINARY_DIR}/yacc.y - COMMAND m4 - ARGS -I ${PROJECT_SOURCE_DIR}/src/parser/yacc -P ${PROJECT_SOURCE_DIR}/src/parser/yacc/parser.y > ${CMAKE_CURRENT_BINARY_DIR}/yacc.y - VERBATIM) -BISON_TARGET(expr_parser ${CMAKE_CURRENT_BINARY_DIR}/yacc.y ${CMAKE_CURRENT_BINARY_DIR}/parser.cpp) -FLEX_TARGET(expr_lexer ${CMAKE_CURRENT_BINARY_DIR}/lex.l ${CMAKE_CURRENT_BINARY_DIR}/scanner.cpp) -ADD_FLEX_BISON_DEPENDENCY(expr_lexer expr_parser) add_library(${PROJECT_NAME} SHARED - ${BISON_expr_parser_OUTPUTS} - ${FLEX_expr_lexer_OUTPUTS} - src/drivers/interpreter.cpp - src/drivers/tree_interpreter.cpp - src/drivers/tree_compiler.cpp - src/drivers/compiler.cpp - - src/clock.cpp - src/symbol_table.cpp + src/symbol/clock.cpp + src/symbol/symbol_table.cpp src/operations/add.cpp src/operations/subtract.cpp src/operations/multiply.cpp src/operations/divide.cpp src/operations/modulo.cpp src/operations/pow.cpp - src/operations/boolean.cpp) + src/operations/boolean.cpp + src/driver/evaluator.cpp + ) target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/src + ${yatree_SOURCE_DIR}/include + ${yatimer_SOURCE_DIR}/include ${yaoverload_SOURCE_DIR}/include + ${yahashcombine_SOURCE_DIR}/include + ${argvparse_SOURCE_DIR}/include + ${z3_SOURCE_DIR}/src/api/c++ + ${z3_SOURCE_DIR}/include + src/symbol + include + src) + +add_library(${PROJECT_NAME}_generic_driver SHARED src/generic-driver.cpp) +target_include_directories(${PROJECT_NAME}_generic_driver PUBLIC + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/src ${yatree_SOURCE_DIR}/include ${yatimer_SOURCE_DIR}/include + ${yaoverload_SOURCE_DIR}/include ${yahashcombine_SOURCE_DIR}/include ${argvparse_SOURCE_DIR}/include ${z3_SOURCE_DIR}/src/api/c++ ${z3_SOURCE_DIR}/include + src/symbol include src) +add_subdirectory(src/expr-lang) + if(ENABLE_Z3) - target_sources(${PROJECT_NAME} PUBLIC src/drivers/z3_driver.cpp) + target_sources(${PROJECT_NAME} PUBLIC src/driver/z3/z3-driver.cpp) target_link_directories(${PROJECT_NAME} PUBLIC ${z3_SOURCE_DIR}/bin) target_link_libraries(${PROJECT_NAME} z3) target_compile_definitions(${PROJECT_NAME} PUBLIC ENABLE_Z3) @@ -104,4 +101,6 @@ endif() add_executable(${PROJECT_NAME}_demo src/main.cpp) set_target_properties(${PROJECT_NAME}_demo PROPERTIES RPATH ${z3_SOURCE_DIR}/bin) -target_link_libraries(${PROJECT_NAME}_demo ${PROJECT_NAME} argvparse) +target_link_libraries(${PROJECT_NAME}_demo ${PROJECT_NAME} expr_lang argvparse ${PROJECT_NAME}_generic_driver) +target_link_libraries(${PROJECT_NAME}_generic_driver expr_lang) + diff --git a/README.md b/README.md index 9fc04cd..0f674ec 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ This project is compiled through cmake, so you can simply configure the project mkdir bin && cd bin cmake .. \ -DCMAKE_BUILD_TYPE=Release \ - -DENABLE_Z3=ON # z3 theorem prover is an opt-in feature. Note that compilation will take much longer if enabled + -DENABLE_Z3=ON # z3 theorem prover is an opt-in feature make ``` Note that because the flex/bison parser is generated at compile-time, your editor might complain about some invalid @@ -26,50 +26,82 @@ If you want to use the project in your own cmake project, simply include `expr` with the `libexpr` library. The project is also tagged with release versions to be compatible with [cpm](https://github.com/cpm-cmake/CPM.cmake), so if your project uses that, simply include the project like so: ```cmake -CPMAddPackage("gh:sillydan1/expr@1.4.0") +CPMAddPackage("gh:sillydan1/expr@3.0.0") ``` ## Examples +Disclaimer: The examples in this section may be outdated. Look at [main.cpp](src/main.cpp) for an in-depth example of how to use the library. + This project comes with a demo command line interface called `expr_demo` so that you can test if the project supports your expression syntax. ``` -$ ./expr_demo - - -a := (1 + 2 + 3 + 4); -b := one + two; -^D -a :-> 6 i -b :-> 3 i +$ ./expr_demo -m - +provide an environment. End with <> (ctrl+d): +<< +a := 32 +>> +provide an expression. End with <> (ctrl+d): +<< +b := a / 2 +>> +ast: + > b :-> ROOT[/[a 2 i ]] +evaluated: + > b :-> 16 i +z3 sat check: (apply these changes to satisfy the expression) + > not a raw rhs, cannot check for sat ``` You can also use the project directly in code like so: ```c++ -using namespace expr; // All elements are in the expr:: namespace -symbol_table_t env{}; // Initialize an environment -interpreter drv{env}; // Initialize the expr interpreter with the environment -if (drv.parse("a := 32 + 2") == 0) // Parse your expressions - std::cout << drv.result; // Print the resulting symbol table -else // Parsing went wrong. Maybe bad syntax, type error or identifier not in environment - std::cout << drv.error; // Print what went wrong +expr::symbol_table_t env{}; // provide an environment for identifier lookup(s) +expr::generic_driver d{}; // instantiate default driver (make sure to link with libexpr_generic_driver.so for this +d.parse_expressions("a := 32 + 2"); // parse your expression +auto result = d.get_symbols(env); // extract the symbol-table ``` -### Using The `expr::z3_driver` +If you want more direct control, or want to inject some custom code (e.g. override the `add/+` operator), you can use the direct API: +```c++ +using namespace expr; // All elements are in the expr:: namespace +std::string expression = "a := 32+2"; // Some expression string +std::stringstream stream{expression}; // inputs must be wrapped in a c++ stream +ast_factory factory{}; // Initialize an overridable ast factory +declaration_tree_builder builder{}; // Initialize an overridable tree builder +scanner sc{stream, std::cerr, &factory}; // Initialize scanner with input stream - write errors to std::cerr +parser_args args{expression, &sc, &factory, &builder}; // Wrap parser arguments +if(parser{args}.parse() != 0) // Actually parse the expression(s) + throw std::logic_error(""); // something bad happened either throw or handle the error +auto result = builder.build(); // build the resulting AST(s) + +// Now, we can evaluate our built AST(s) +symbol_table_t env{}; // Initialize an environment for identifier lookup +symbol_operator op{}; // Initialize an overridable operator collection +evaluator e{{env}, op}; // Initialize an evaluator tree-visitor +symbol_table_t result_env{}; +for(auto& r : result.declarations) // Extract all the declarations + result_env[r.first] = e.evaluate(r.second.tree); +for(auto& s : result_env) // Print the resulting declarations + std::cout << s.first << " :-> " << s.second << "\n"; +if(result.raw_expression) // If the expression is just a RHS expression, print it + std::cout << e.evaluate(result.raw_expression.value()) << "\n"; +``` + +### Using The Z3 Functionality If enabled, expr includes an integration with the [z3 sat solver](https://github.com/Z3Prover/z3). -By providing an initial environment and a boolean expression, the `expr::z3_driver` can provide you with an assignment -of those variables, such that the boolean expression evaluates to `true`. +By providing an initial environment, some "unknown"/assignable variables and a boolean expression, +expr can provide you with an assignment of the "unknown" variables, such that the boolean expression evaluates to `true`. This is best explained with an example. The demo binary is a fine place to explore this driver: ```shell -$ ./expr_demo --driver z3 \ - --environment "a := false; b := false; c := false" \ +$ ./expr_demo --unknown-environment "a := false; b := false; c := false" \ --expression "a == !(b || c)" ``` We ask z3 to find a solution where `a` would be the same value as the expression `!(b || c)`. Below is the result of the above query. ``` -result: -a :-> false b -b :-> true b -c :-> false b +z3 sat check: (apply these changes to satisfy the expression) + > a :-> false b + > b :-> true b + > c :-> false b ``` There are in fact more than one solution to this query - you could just as well set `c :-> true` and the query would still be satisfied. @@ -77,12 +109,10 @@ If you want to iterate over all solutions, you can just append every solution yo If you provide a query, where no assignment of the variables would lead to `true`, the driver will exclaim `"unsat"` in the terminal, and the `result` symbol table will be empty: ```shell -$ ./expr_demo --driver z3 \ - --environment "a := false" \ +$ ./expr_demo --unknown-environment "a := false" \ --expression "a != a" # not satisfiable ``` ``` -unsat -result: - +z3 sat check: (apply these changes to satisfy the expression) + > unsatisfiable ``` diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake index e51b761..601d072 100644 --- a/cmake/CPM.cmake +++ b/cmake/CPM.cmake @@ -1,4 +1,4 @@ -set(CPM_DOWNLOAD_VERSION 0.36.0) +set(CPM_DOWNLOAD_VERSION 0.38.0) if(CPM_SOURCE_CACHE) # Expand relative path. This is important if the provided path contains a tilde (~) diff --git a/include/drivers/driver.h b/include/drivers/driver.h deleted file mode 100644 index 5b91abc..0000000 --- a/include/drivers/driver.h +++ /dev/null @@ -1,74 +0,0 @@ -/* MIT License - * - * Copyright (c) 2022 Asger Gitz-Johansen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef EXPR_DRIVER_H -#define EXPR_DRIVER_H -#include -#include "symbol_table.h" -#include "parser.hpp" -#include "drivers/buffer_state.h" -#define YY_DECL yy::parser::symbol_type yylex (expr::driver* drv) -YY_DECL; - -namespace expr { - // TODO: Break this into an interface (functions) and a base abstract class (basic implementation) - using symbol_table_ref_t = std::reference_wrapper; - using symbol_table_ref_collection_t = std::vector>; - struct driver { - driver(std::initializer_list environments) - : trace_parsing(false), trace_scanning(false), environments{environments}, buffer{} {} - virtual ~driver() = default; - - virtual int parse(const std::string &f) = 0; - virtual auto get_symbol(const std::string &identifier) -> syntax_tree_t = 0; - virtual void add_tree(const syntax_tree_t& tree) = 0; - virtual void add_tree(const std::string& identifier, const syntax_tree_t& tree) = 0; - virtual void add_tree(const std::string& access_modifier, const std::string& identifier, const syntax_tree_t& tree) = 0; - virtual auto contains(const std::string& identifier) const -> bool { - return find(identifier) != end; - } - virtual auto find(const std::string& identifier) const -> expr::symbol_table_t::const_iterator { - // TODO: Tree of environments - for(auto& env : environments) { - auto env_it = env.get().find(identifier); - if(env_it != env.get().end()) - return env_it; - } - return end; - } - - void scan_begin(); - void scan_end(); - - std::string error; - std::string file; - bool trace_parsing; - bool trace_scanning; - yy::location location; - protected: - expr::symbol_table_t::const_iterator end{}; - symbol_table_ref_collection_t environments; - yy_buffer_state* buffer; - }; -} - -#endif //EXPR_DRIVER_H diff --git a/include/drivers/generic_symbol_operator.h b/include/drivers/generic_symbol_operator.h deleted file mode 100644 index d83fc7d..0000000 --- a/include/drivers/generic_symbol_operator.h +++ /dev/null @@ -1,51 +0,0 @@ -/* MIT License - * - * Copyright (c) 2022 Asger Gitz-Johansen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef EXPR_GENERIC_SYMBOL_OPERATOR_H -#define EXPR_GENERIC_SYMBOL_OPERATOR_H -#include "operations.h" - -namespace expr { - struct generic_symbol_operator : public arithmetic_operator, boolean_operator, compare_operator { - auto add(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t override { return ::operator+(a,b); }; - auto sub(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t override { return a - b; }; - auto mul(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t override { return a * b; }; - auto div(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t override { return a / b; }; - auto mod(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t override { return a % b; }; - auto pow(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t override { return a ^ b; }; - - auto _not(const symbol_value_t &a) const -> symbol_value_t override { return not_(a); } - auto _and(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t override { return and_(a,b); } - auto _or(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t override { return or_(a,b); } - auto _xor(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t override { return xor_(a,b); } - auto _implies(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t override { return implies_(a,b); } - - auto gt(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t override { return gt_(a,b); } - auto ge(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t override { return ge_(a,b); } - auto ee(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t override { return ee_(a,b); } - auto ne(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t override { return ne_(a,b); } - auto le(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t override { return le_(a,b); } - auto lt(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t override { return lt_(a,b); } - }; -} - -#endif //EXPR_GENERIC_SYMBOL_OPERATOR_H diff --git a/include/drivers/tree_interpreter.h b/include/drivers/tree_interpreter.h deleted file mode 100644 index 390771e..0000000 --- a/include/drivers/tree_interpreter.h +++ /dev/null @@ -1,50 +0,0 @@ -/* MIT License - * - * Copyright (c) 2022 Asger Gitz-Johansen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef EXPR_TREE_INTERPRETER_H -#define EXPR_TREE_INTERPRETER_H -#include "tree_driver.h" -#include "compiler.h" -#include "generic_symbol_operator.h" - -namespace expr { - struct tree_interpreter : public tree_driver, generic_symbol_operator { - explicit tree_interpreter(const symbol_table_tree_t::_left_df_iterator& it); - ~tree_interpreter() override = default; - - auto parse(const std::string &f) -> int override; - auto interpret_declarations(const std::string& f) -> symbol_table_t; - auto interpret_expression(const std::string& f) -> symbol_value_t; - auto get_symbol(const std::string& identifier) -> syntax_tree_t override; - void add_tree(const syntax_tree_t& tree) override; - void add_tree(const std::string& identifier, const syntax_tree_t& tree) override; - void add_tree(const std::string& access_modifier, const std::string& identifier, const syntax_tree_t& tree) override; - - symbol_table_t result{}; - symbol_value_t expression_result{}; - - auto evaluate(const syntax_tree_t& tree) -> symbol_value_t; - auto evaluate(const compiler::compiled_expr_collection_t& tree) -> symbol_table_t; - }; -} - -#endif //EXPR_TREE_INTERPRETER_H diff --git a/src/driver/evaluator.cpp b/src/driver/evaluator.cpp new file mode 100644 index 0000000..4a666bb --- /dev/null +++ b/src/driver/evaluator.cpp @@ -0,0 +1,88 @@ +/* MIT License + * + * Copyright (c) 2022 Asger Gitz-Johansen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "evaluator.h" +#include "driver/parse-error.h" +#include +#include + +namespace expr { + evaluator::evaluator(const symbol_table_ref_collection_t& environments, const symbol_operator& op) : environments{environments}, op{op} { + + } + + auto evaluator::contains(const std::string& identifier) const -> bool { + return find(identifier) != end; + } + + auto evaluator::find(const std::string& identifier) const -> expr::symbol_table_t::const_iterator { + for(auto& env : environments) { + auto env_it = env.get().find(identifier); + if(env_it != env.get().end()) + return env_it; + } + return end; + } + + auto evaluator::evaluate(const syntax_tree_t& tree) -> symbol_value_t { + return std::visit(ya::overload( + [&](const identifier_t& r){ + auto s = find(r.ident); + if(s == end) + throw parse_error("not found: '" + r.ident + "'"); + return s->second; + }, + [&](const operator_t& o) { + switch (o.operator_type) { + // FIX: This this does not consider if children().size() > 2 + case operator_type_t::minus: return op.sub(evaluate(tree.children()[0]), evaluate(tree.children()[1])); + case operator_type_t::plus: return op.add(evaluate(tree.children()[0]), evaluate(tree.children()[1])); + case operator_type_t::star: return op.mul(evaluate(tree.children()[0]), evaluate(tree.children()[1])); + case operator_type_t::slash: return op.div(evaluate(tree.children()[0]), evaluate(tree.children()[1])); + case operator_type_t::percent: return op.mod(evaluate(tree.children()[0]), evaluate(tree.children()[1])); + case operator_type_t::hat: return op.pow(evaluate(tree.children()[0]), evaluate(tree.children()[1])); + case operator_type_t::_and: return op._and(evaluate(tree.children()[0]), evaluate(tree.children()[1])); + case operator_type_t::_or: return op._or(evaluate(tree.children()[0]), evaluate(tree.children()[1])); + case operator_type_t::_xor: return op._xor(evaluate(tree.children()[0]), evaluate(tree.children()[1])); + case operator_type_t::_not: return op._not(evaluate(tree.children()[0])); + case operator_type_t::_implies: return op._implies(evaluate(tree.children()[0]), evaluate(tree.children()[1])); + case operator_type_t::gt: return op.gt(evaluate(tree.children()[0]), evaluate(tree.children()[1])); + case operator_type_t::ge: return op.ge(evaluate(tree.children()[0]), evaluate(tree.children()[1])); + case operator_type_t::ne: return op.ne(evaluate(tree.children()[0]), evaluate(tree.children()[1])); + case operator_type_t::ee: return op.ee(evaluate(tree.children()[0]), evaluate(tree.children()[1])); + case operator_type_t::le: return op.le(evaluate(tree.children()[0]), evaluate(tree.children()[1])); + case operator_type_t::lt: return op.lt(evaluate(tree.children()[0]), evaluate(tree.children()[1])); + case operator_type_t::parentheses: return evaluate(tree.children()[0]); + } + throw std::logic_error("unsupported operator type"); + }, + [](const symbol_value_t& o){ return o; }, + [&](const root_t& r){ + if(!tree.children().empty()) + return evaluate(tree.children()[0]); + throw parse_error("ROOT has no children()"); + }, + [](auto&&){ throw parse_error("operator type not recognized"); } + ), static_cast(tree.node)); + } +} + diff --git a/include/drivers/tree_compiler.h b/src/driver/evaluator.h similarity index 54% rename from include/drivers/tree_compiler.h rename to src/driver/evaluator.h index 411ee56..58ad0f2 100644 --- a/include/drivers/tree_compiler.h +++ b/src/driver/evaluator.h @@ -20,29 +20,26 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -#ifndef EXPR_TREE_COMPILER_H -#define EXPR_TREE_COMPILER_H -#include "tree_driver.h" -#include -#include +#ifndef EXPR_INTERPRETER_H +#define EXPR_INTERPRETER_H +#include "operations/symbol-operator.h" +#include "symbol_table.h" namespace expr { - struct tree_compiler : tree_driver { - using compiled_expr_t = syntax_tree_t; -#ifndef NDEBUG - using compiled_expr_collection_t = std::map; -#else - using compiled_expr_collection_t = std::unordered_map; -#endif - explicit tree_compiler(const symbol_table_tree_t::iterator& scope) : tree_driver{scope}, trees{} {} - int parse(const std::string &f) override; - auto get_symbol(const std::string &identifier) -> syntax_tree_t override; - void add_tree(const syntax_tree_t& tree) override; - void add_tree(const std::string& identifier, const syntax_tree_t& tree) override; - void add_tree(const std::string& access_modifier, const std::string& identifier, const syntax_tree_t& tree) override; - - compiled_expr_collection_t trees; + using symbol_table_ref_collection_t = std::vector>; + class evaluator { + public: + evaluator(const symbol_table_ref_collection_t& environments, const symbol_operator& op); + virtual ~evaluator() = default; + virtual auto evaluate(const syntax_tree_t& tree) -> symbol_value_t; + virtual auto contains(const std::string& identifier) const -> bool; + virtual auto find(const std::string& identifier) const -> expr::symbol_table_t::const_iterator; + protected: + symbol_table_ref_collection_t environments; + expr::symbol_table_t::const_iterator end{}; + symbol_operator op; }; } -#endif //EXPR_TREE_COMPILER_H +#endif + diff --git a/include/drivers/interpreter.h b/src/driver/parse-error.h similarity index 50% rename from include/drivers/interpreter.h rename to src/driver/parse-error.h index 85e10e8..c19fe1a 100644 --- a/include/drivers/interpreter.h +++ b/src/driver/parse-error.h @@ -20,32 +20,16 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -#ifndef EXPR_INTERPRETER_H -#define EXPR_INTERPRETER_H -#include "operations.h" -#include "drivers/driver.h" -#include "compiler.h" -#include "generic_symbol_operator.h" +#ifndef EXPR_DRIVER_H +#define EXPR_DRIVER_H +#include "symbol_table.h" +#include namespace expr { - struct interpreter : public driver, generic_symbol_operator { - interpreter(std::initializer_list environments); - ~interpreter() override = default; - - auto parse(const std::string &f) -> int override; - auto interpret_declarations(const std::string& f) -> symbol_table_t; - auto interpret_expression(const std::string& f) -> symbol_value_t; - auto get_symbol(const std::string& identifier) -> syntax_tree_t override; - void add_tree(const syntax_tree_t& tree) override; - void add_tree(const std::string& identifier, const syntax_tree_t& tree) override; - void add_tree(const std::string& access_modifier, const std::string& identifier, const syntax_tree_t& tree) override; - - symbol_table_t result{}; - symbol_value_t expression_result{}; - - auto evaluate(const syntax_tree_t& tree) -> symbol_value_t; - auto evaluate(const compiler::compiled_expr_collection_t& tree) -> symbol_table_t; + struct parse_error : public std::logic_error { + parse_error(const std::string& msg) : std::logic_error(msg.c_str()) {} }; } #endif + diff --git a/src/driver/z3/z3-driver.cpp b/src/driver/z3/z3-driver.cpp new file mode 100644 index 0000000..dd72324 --- /dev/null +++ b/src/driver/z3/z3-driver.cpp @@ -0,0 +1,125 @@ +/* MIT License + * + * Copyright (c) 2022 Asger Gitz-Johansen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "z3-driver.h" +#include "symbol_table.h" + +namespace expr { + // TODO: delay_identfier should be randomly generated to avoid known/unknown collissions + z3_driver::z3_driver(const symbol_table_t& known, const symbol_table_t& unknown) + : c{}, s{c}, delay_identifier{"d"}, known{known}, unknown{unknown} {} + + auto z3_driver::find_solution(const syntax_tree_t& expression) -> std::optional { + s.add(as_z3(expression)); // NOTE: Only accepts boolean expressions (will throw if not) + symbol_table_t result{}; + switch (s.check()) { + case z3::unsat: s.reset(); return{}; + case z3::unknown: s.reset(); return{}; + case z3::sat: + auto m = s.get_model(); + s.reset(); + for(int i = 0; i < m.size(); i++) { + auto xx = m[i]; + auto interp = xx.is_const() ? m.get_const_interp(xx) : m.get_func_interp(xx).else_value(); + if(xx.name().str() == delay_identifier) + result.set_delay_amount(as_symbol_value(interp)); + else + result[xx.name().str()] = as_symbol_value(interp); + } + } + return result; + } + + auto z3_driver::as_symbol_value(const z3::expr& e) -> symbol_value_t { + if(e.is_int()) + return (int) e.as_int64(); + if(e.is_real()) + return (float) e.as_double(); + if(e.is_bool()) + return e.bool_value() == 1; // NOTE: bool_value() is -1/1 in z3, because who needs standards? + if(e.is_string_value()) + return e.get_string(); + throw std::logic_error("invalid z3::expr value - unable to convert to expr::symbol_value_t"); + } + + auto z3_driver::as_z3(const symbol_value_t& val) -> z3::expr { + return std::visit(ya::overload( + [this](const int& i) { return c.int_val(i); }, + [this](const float& f) { return c.real_val(std::to_string(f).c_str()); }, + [this](const bool& b) { return c.bool_val(b); }, + [this](const std::string& sv) { return c.string_val(sv); }, + [this](const expr::clock_t& v){ return c.int_val(v.time_units); }, + [](auto&& x){ throw std::logic_error("unable to convert symbol value to z3::expr"); } + ), static_cast(val)); + } + + auto z3_driver::as_z3(const identifier_t& ref) -> z3::expr { + if(known.contains(ref.ident)) + return std::visit(ya::overload( + [this,&ref](const expr::clock_t& v) { return c.int_val(v.time_units) + c.int_const(delay_identifier.c_str()); }, + [this,&ref](auto&& x) { return as_z3(known.at(ref.ident)); } + ), static_cast(known.at(ref.ident))); + if(!unknown.contains(ref.ident)) + throw std::logic_error{ref.ident + " not found"}; + return std::visit(ya::overload( + [this, &ref](const int& _) { return c.int_const(ref.ident.c_str()); }, + [this, &ref](const float& _) { return c.real_const(ref.ident.c_str()); }, + [this, &ref](const bool& _) { return c.bool_const(ref.ident.c_str()); }, + [this, &ref](const std::string& _) { return c.string_const(ref.ident.c_str()); }, + [this, &ref](const expr::clock_t& v){ return c.int_val(v.time_units) + c.int_const(delay_identifier.c_str()); }, + [](auto&& x){ throw std::logic_error("unable to convert symbol reference to z3::expr"); } + ), static_cast(unknown.find(ref.ident)->second)); + } + + auto z3_driver::as_z3(const syntax_tree_t &tree) -> z3::expr { + return std::visit(ya::overload( + [this](const identifier_t& r) { return as_z3(r); }, + [&](const operator_t& o) { + switch (o.operator_type) { + case operator_type_t::minus: return as_z3(tree.children()[0]) - as_z3(tree.children()[1]); + case operator_type_t::plus: return as_z3(tree.children()[0]) + as_z3(tree.children()[1]); + case operator_type_t::star: return as_z3(tree.children()[0]) * as_z3(tree.children()[1]); + case operator_type_t::slash: return as_z3(tree.children()[0]) / as_z3(tree.children()[1]); + case operator_type_t::percent: return as_z3(tree.children()[0]) % as_z3(tree.children()[1]); + case operator_type_t::hat: return z3::pw(as_z3(tree.children()[0]), as_z3(tree.children()[1])); + case operator_type_t::_and: return as_z3(tree.children()[0]) && as_z3(tree.children()[1]); + case operator_type_t::_or: return as_z3(tree.children()[0]) || as_z3(tree.children()[1]); + case operator_type_t::_xor: return as_z3(tree.children()[0]) xor as_z3(tree.children()[1]); + case operator_type_t::_not: return !as_z3(tree.children()[0]); + case operator_type_t::_implies: return implies(as_z3(tree.children()[0]),as_z3(tree.children()[1])); + case operator_type_t::gt: return (as_z3(tree.children()[0]) > as_z3(tree.children()[1])); + case operator_type_t::ge: return (as_z3(tree.children()[0]) >= as_z3(tree.children()[1])); + case operator_type_t::ne: return (as_z3(tree.children()[0]) != as_z3(tree.children()[1])); + case operator_type_t::ee: return (as_z3(tree.children()[0]) == as_z3(tree.children()[1])); + case operator_type_t::le: return (as_z3(tree.children()[0]) <= as_z3(tree.children()[1])); + case operator_type_t::lt: return (as_z3(tree.children()[0]) < as_z3(tree.children()[1])); + case operator_type_t::parentheses: return (as_z3(tree.children()[0])); + } + throw std::logic_error("unsupported operator"); + }, + [this](const symbol_value_t& o){ return as_z3(o); }, + [&](const root_t& r){ return as_z3(tree.children()[0]); }, + [](auto&&){ throw std::logic_error("tree node type not recognized"); } + ), static_cast(tree.node)); + } +} + diff --git a/include/drivers/z3_driver.h b/src/driver/z3/z3-driver.h similarity index 59% rename from include/drivers/z3_driver.h rename to src/driver/z3/z3-driver.h index 898e5dd..4663441 100644 --- a/include/drivers/z3_driver.h +++ b/src/driver/z3/z3-driver.h @@ -21,38 +21,28 @@ * SOFTWARE. */ #ifndef EXPR_Z3_DRIVER_H -#ifdef ENABLE_Z3 #define EXPR_Z3_DRIVER_H -#include "operations.h" -#include "drivers/driver.h" +#include "../parse-error.h" +#include "symbol_table.h" #include namespace expr { - struct z3_driver : public driver { - z3_driver(const symbol_table_t& known_env, const symbol_table_t& unknown_env); - ~z3_driver() override; - - auto parse(const std::string &f) -> int override; - auto get_symbol(const std::string& identifier) -> syntax_tree_t override; - void add_tree(const syntax_tree_t& tree) override; - void add_tree(const std::string& identifier, const syntax_tree_t& tree) override; - void add_tree(const std::string& access_modifier, const std::string& identifier, const syntax_tree_t& tree) override; - + class z3_driver { + public: + z3_driver(const symbol_table_t& known, const symbol_table_t& unknown); + auto find_solution(const syntax_tree_t& expression) -> std::optional; + private: + auto as_z3(const syntax_tree_t &tree) -> z3::expr; + auto as_z3(const identifier_t& ref) -> z3::expr; + auto as_z3(const symbol_value_t& val) -> z3::expr; auto as_symbol_value(const z3::expr& e) -> symbol_value_t; - auto as_z3_expression(const syntax_tree_t& tree) -> z3::expr; - auto as_z3_expression(const identifier_t& ref) -> z3::expr; - auto as_z3_expression(const symbol_value_t& val) -> z3::expr; - - symbol_table_t result{}; - protected: - z3::context c{}; + z3::context c; z3::solver s; std::string delay_identifier; const symbol_table_t& known; const symbol_table_t& unknown; - void solve(); }; } -#endif //ENABLE_Z3 -#endif //EXPR_Z3_DRIVER_H +#endif + diff --git a/src/drivers/buffer_state.h b/src/drivers/buffer_state.h deleted file mode 100644 index fb19b66..0000000 --- a/src/drivers/buffer_state.h +++ /dev/null @@ -1,93 +0,0 @@ -/* MIT License - * - * Copyright (c) 2022 Asger Gitz-Johansen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#ifndef YY_STRUCT_YY_BUFFER_STATE -#define YY_STRUCT_YY_BUFFER_STATE -#include -struct yy_buffer_state - { -/* %if-c-only */ - FILE *yy_input_file; -/* %endif */ - -/* %if-c++-only */ -/* %endif */ - - char *yy_ch_buf; /* input buffer */ - char *yy_buf_pos; /* current position in input buffer */ - - /* Size of input buffer in bytes, not including room for EOB - * characters. - */ - int yy_buf_size; - - /* Number of characters read into yy_ch_buf, not including EOB - * characters. - */ - int yy_n_chars; - - /* Whether we "own" the buffer - i.e., we know we created it, - * and can realloc() it to grow it, and should free() it to - * delete it. - */ - int yy_is_our_buffer; - - /* Whether this is an "interactive" input source; if so, and - * if we're using stdio for input, then we want to use getc() - * instead of fread(), to make sure we stop fetching input after - * each newline. - */ - int yy_is_interactive; - - /* Whether we're considered to be at the beginning of a line. - * If so, '^' rules will be active on the next match, otherwise - * not. - */ - int yy_at_bol; - - int yy_bs_lineno; /**< The line count. */ - int yy_bs_column; /**< The column count. */ - - /* Whether to try to fill the input buffer when we reach the - * end of it. - */ - int yy_fill_buffer; - - int yy_buffer_status; - -#define YY_BUFFER_NEW 0 -#define YY_BUFFER_NORMAL 1 - /* When an EOF's been seen but there's still some text to process - * then we mark the buffer as YY_EOF_PENDING, to indicate that we - * shouldn't try reading from the input source any more. We might - * still have a bunch of tokens to match, though, because of - * possible backing-up. - * - * When we actually see the EOF, we change the status to "new" - * (via yyrestart()), so that the user can continue scanning by - * just pointing yyin at a new input file. - */ -#define YY_BUFFER_EOF_PENDING 2 - - }; -#endif /* !YY_STRUCT_YY_BUFFER_STATE */ - diff --git a/src/drivers/compiler.cpp b/src/drivers/compiler.cpp deleted file mode 100644 index 646534c..0000000 --- a/src/drivers/compiler.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/* MIT License - * - * Copyright (c) 2022 Asger Gitz-Johansen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#include "drivers/compiler.h" - -namespace expr { - int compiler::parse(const std::string& f) { - if (f.empty()) { -#ifdef DEFAULT_EXPRESSION_VALUE - if(!std::string(DEFAULT_EXPRESSION_VALUE).empty()) - return parse(DEFAULT_EXPRESSION_VALUE); -#endif - error = "empty expression and no DEFAULT_EXPRESSION_VALUE"; - return 1; - } - file = f; - location.initialize(&file); - scan_begin(); - yy::parser parse(this); - parse.set_debug_level(trace_parsing); - try { - int res = parse(); - scan_end(); - return res; - } catch (std::exception &e) { - error = e.what(); - return 1; - } - } - auto compiler::get_symbol(const std::string& identifier) -> syntax_tree_t { - if (!contains(identifier)) - throw std::out_of_range(identifier + " not found"); - return syntax_tree_t{identifier_t{identifier}}; - } - void compiler::add_tree(const syntax_tree_t& tree) { - trees["expression_result"] = (tree); - } - void compiler::add_tree(const std::string& identifier, const syntax_tree_t& tree) { - trees[identifier] = tree; - } - void compiler::add_tree(const std::string& access_modifier, const std::string& identifier, - const expr::syntax_tree_t& tree) { - add_tree(identifier, tree); - } -} diff --git a/src/drivers/interpreter.cpp b/src/drivers/interpreter.cpp deleted file mode 100644 index ef5beb4..0000000 --- a/src/drivers/interpreter.cpp +++ /dev/null @@ -1,134 +0,0 @@ -/* MIT License - * - * Copyright (c) 2022 Asger Gitz-Johansen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#include "drivers/interpreter.h" -#include -#include "parser.hpp" - -namespace expr { - interpreter::interpreter(std::initializer_list environments) : driver{environments} {} - - int interpreter::parse(const std::string &f) { - if (f.empty()) { -#ifdef DEFAULT_EXPRESSION_VALUE - expression_result = DEFAULT_EXPRESSION_VALUE; -#endif - return 0; - } - file = f; - location.initialize(&file); - scan_begin(); - yy::parser parse(this); - parse.set_debug_level(trace_parsing); - try { - int res = parse(); - scan_end(); - return res; - } catch (std::exception &e) { - error = e.what(); - return 1; - } - } - - auto interpreter::interpret_declarations(const std::string &f) -> symbol_table_t { - result = {}; - auto res = parse(f); - if(res != 0) - throw std::logic_error(error); - return result; - } - - auto interpreter::interpret_expression(const std::string &f) -> symbol_value_t { - expression_result = {}; - auto res = parse(f); - if(res != 0) - throw std::logic_error(error); - return expression_result; - } - - auto interpreter::get_symbol(const std::string &identifier) -> syntax_tree_t { - if (!contains(identifier)) - throw std::out_of_range(identifier + " not found"); - return syntax_tree_t{identifier_t{identifier}}; - } - - void interpreter::add_tree(const syntax_tree_t& tree) { - expression_result = evaluate(tree); - } - - void interpreter::add_tree(const std::string& identifier, const syntax_tree_t& tree) { - result[identifier] = evaluate(tree); - } - - void interpreter::add_tree(const std::string& access_modifier, const std::string& identifier, - const syntax_tree_t& tree) { - add_tree(identifier, tree); - } - - auto interpreter::evaluate(const syntax_tree_t& tree) -> symbol_value_t { - return std::visit(ya::overload( - [&](const identifier_t& r){ - auto s = find(r.ident); - if(s == end) - throw std::out_of_range("not found: " + r.ident); - return s->second; - }, - [&](const operator_t& o) { - switch (o.operator_type) { - case operator_type_t::minus: return sub(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::plus: return add(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::star: return mul(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::slash: return div(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::percent: return mod(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::hat: return pow(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::_and: return _and(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::_or: return _or(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::_xor: return _xor(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::_not: return _not(evaluate(tree.children()[0])); - case operator_type_t::_implies: return _implies(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::gt: return gt(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::ge: return ge(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::ne: return ne(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::ee: return ee(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::le: return le(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::lt: return lt(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::parentheses: return evaluate(tree.children()[0]); - } - throw std::logic_error("unsupported operator type"); - }, - [](const symbol_value_t& o){ return o; }, - [&](const root_t& r){ - if(!tree.children().empty()) - return evaluate(tree.children()[0]); - throw std::logic_error("ROOT has no children()"); - }, - [](auto&&){ throw std::logic_error("operator type not recognized"); } - ), static_cast(tree.node)); - } - - auto interpreter::evaluate(const compiler::compiled_expr_collection_t& trees) -> symbol_table_t { - symbol_table_t res{}; - for(auto& tree : trees) - res[tree.first] = evaluate(tree.second); - return res; - } -} diff --git a/src/drivers/tree_compiler.cpp b/src/drivers/tree_compiler.cpp deleted file mode 100644 index 7319818..0000000 --- a/src/drivers/tree_compiler.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/* MIT License - * - * Copyright (c) 2022 Asger Gitz-Johansen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#include - -namespace expr { - int tree_compiler::parse(const std::string& f) { - if (f.empty()) { -#ifdef DEFAULT_EXPRESSION_VALUE - if(!std::string(DEFAULT_EXPRESSION_VALUE).empty()) - return parse(DEFAULT_EXPRESSION_VALUE); -#endif - error = "empty expression and no DEFAULT_EXPRESSION_VALUE"; - return 1; - } - file = f; - location.initialize(&file); - scan_begin(); - yy::parser parse(this); - parse.set_debug_level(trace_parsing); - try { - int res = parse(); - scan_end(); - return res; - } catch (std::exception &e) { - error = e.what(); - return 1; - } - } - auto tree_compiler::get_symbol(const std::string& identifier) -> syntax_tree_t { - if (!contains(identifier)) - throw std::out_of_range(identifier + " not found"); - return syntax_tree_t{identifier_t{identifier}}; - } - void tree_compiler::add_tree(const syntax_tree_t& tree) { - trees["expression_result"] = (tree); - } - void tree_compiler::add_tree(const std::string& identifier, const syntax_tree_t& tree) { - trees[identifier] = tree; - } - void tree_compiler::add_tree(const std::string& access_modifier, const std::string& identifier, const expr::syntax_tree_t& tree) { - add_tree(identifier, tree); - } -} diff --git a/src/drivers/tree_interpreter.cpp b/src/drivers/tree_interpreter.cpp deleted file mode 100644 index 2a5861f..0000000 --- a/src/drivers/tree_interpreter.cpp +++ /dev/null @@ -1,132 +0,0 @@ -/* MIT License - * - * Copyright (c) 2022 Asger Gitz-Johansen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#include "drivers/tree_interpreter.h" - -namespace expr { - tree_interpreter::tree_interpreter(const symbol_table_tree_t::_left_df_iterator& it) : tree_driver(it) {} - - int tree_interpreter::parse(const std::string &f) { - if (f.empty()) { -#ifdef DEFAULT_EXPRESSION_VALUE - expression_result = DEFAULT_EXPRESSION_VALUE; -#endif - return 0; - } - file = f; - location.initialize(&file); - scan_begin(); - yy::parser parse(this); - parse.set_debug_level(trace_parsing); - try { - int res = parse(); - scan_end(); - return res; - } catch (std::exception &e) { - error = e.what(); - return 1; - } - } - - auto tree_interpreter::interpret_declarations(const std::string &f) -> symbol_table_t { - result = {}; - auto res = parse(f); - if(res != 0) - throw std::logic_error(error); - return result; - } - - auto tree_interpreter::interpret_expression(const std::string &f) -> symbol_value_t { - expression_result = {}; - auto res = parse(f); - if(res != 0) - throw std::logic_error(error); - return expression_result; - } - - auto tree_interpreter::get_symbol(const std::string &identifier) -> syntax_tree_t { - if (!contains(identifier)) - throw std::out_of_range(identifier + " not found"); - return syntax_tree_t{identifier_t{identifier}}; - } - - void tree_interpreter::add_tree(const syntax_tree_t& tree) { - expression_result = evaluate(tree); - } - - void tree_interpreter::add_tree(const std::string& identifier, const syntax_tree_t& tree) { - result[identifier] = evaluate(tree); - } - - void tree_interpreter::add_tree(const std::string& access_modifier, const std::string& identifier, - const expr::syntax_tree_t& tree) { - add_tree(identifier, tree); - } - - auto tree_interpreter::evaluate(const syntax_tree_t& tree) -> symbol_value_t { - return std::visit(ya::overload( - [&](const identifier_t& r){ - auto s = find(r.ident); - if(s == end) - throw std::out_of_range("not found: " + r.ident); - return s->second; - }, - [&](const operator_t& o) { - switch (o.operator_type) { - case operator_type_t::minus: return sub(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::plus: return add(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::star: return mul(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::slash: return div(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::percent: return mod(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::hat: return pow(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::_and: return _and(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::_or: return _or(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::_xor: return _xor(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::_not: return _not(evaluate(tree.children()[0])); - case operator_type_t::_implies: return _implies(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::gt: return gt(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::ge: return ge(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::ne: return ne(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::ee: return ee(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::le: return le(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::lt: return lt(evaluate(tree.children()[0]), evaluate(tree.children()[1])); - case operator_type_t::parentheses: return evaluate(tree.children()[0]); - } - throw std::logic_error("unsupported operator type"); - }, - [](const symbol_value_t& o){ return o; }, - [&](const root_t& r){ - if(!tree.children().empty()) - return evaluate(tree.children()[0]); - throw std::logic_error("ROOT has no children()"); - }, - [](auto&&){ throw std::logic_error("operator type not recognized"); } - ), static_cast(tree.node)); - } - - auto tree_interpreter::evaluate(const compiler::compiled_expr_collection_t& trees) -> symbol_table_t { - symbol_table_t res{}; - for(auto& tree : trees) - res[tree.first] = evaluate(tree.second); - return res; - } -} diff --git a/src/drivers/z3_driver.cpp b/src/drivers/z3_driver.cpp deleted file mode 100644 index 9e1503f..0000000 --- a/src/drivers/z3_driver.cpp +++ /dev/null @@ -1,165 +0,0 @@ -/* MIT License - * - * Copyright (c) 2022 Asger Gitz-Johansen - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -#include "drivers/z3_driver.h" - -namespace expr { - z3_driver::z3_driver(const symbol_table_t& known_env, const symbol_table_t& unknown_env) - // TODO: delay_identifier should be randomly generated - : driver{{known_env, unknown_env}}, c{}, s{c}, - delay_identifier{"d"}, known{known_env}, unknown{unknown_env} {} - - z3_driver::~z3_driver() = default; - - int z3_driver::parse(const std::string &f) { - if (f.empty()) - return 0; - file = f; - location.initialize(&file); - scan_begin(); - yy::parser parse(this); - parse.set_debug_level(trace_parsing); - try { - int res = parse(); - scan_end(); - return res; - } catch (std::domain_error& e) { - error = e.what(); - return 2; - } catch (std::exception &e) { - error = e.what(); - return 1; - } - } - - auto z3_driver::get_symbol(const std::string &identifier) -> syntax_tree_t { - if(!contains(identifier)) - throw std::out_of_range(identifier + " not found"); - return syntax_tree_t{identifier_t{identifier}}; - } - - void z3_driver::add_tree(const syntax_tree_t& tree) { - s.add(as_z3_expression(tree)); // Note: Only accepts boolean expressions (will throw if not) - solve(); - } - - void z3_driver::add_tree(const std::string& identifier, const syntax_tree_t& tree) { - std::cout << "z3_driver ignoring declaration\n"; - } - - void z3_driver::add_tree(const std::string& access_modifier, const std::string& identifier, - const expr::syntax_tree_t& tree) { - add_tree(identifier, tree); - } - - void z3_driver::solve() { - switch (s.check()) { - case z3::unsat: s.reset(); throw std::domain_error("unsat"); - case z3::unknown: s.reset(); throw std::logic_error("unknown"); - case z3::sat: - auto m = s.get_model(); - s.reset(); - for(int i = 0; i < m.size(); i++) { - auto xx = m[i]; - auto interp = xx.is_const() ? m.get_const_interp(xx) : m.get_func_interp(xx).else_value(); - if(xx.name().str() == delay_identifier) - result.set_delay_amount(as_symbol_value(interp)); - else - result[xx.name().str()] = as_symbol_value(interp); - } - } - } - - auto z3_driver::as_symbol_value(const z3::expr &e) -> symbol_value_t { - if(e.is_int()) - return (int) e.as_int64(); - if(e.is_real()) - return (float) e.as_double(); - if(e.is_bool()) - return e.bool_value() == 1; // bool_value() is -1/1 - if(e.is_string_value()) - return e.get_string(); - throw std::logic_error("invalid z3::expr value - unable to convert to expr::symbol_value_t"); - } - - auto z3_driver::as_z3_expression(const symbol_value_t& val) -> z3::expr { - return std::visit(ya::overload( - [this](const int& i) { return c.int_val(i); }, - [this](const float& f) { return c.real_val(std::to_string(f).c_str()); }, - [this](const bool& b) { return c.bool_val(b); }, - [this](const std::string& sv) { return c.string_val(sv); }, - [this](const expr::clock_t& v){ return c.int_val(v.time_units); }, - [](auto&& x){ throw std::logic_error("unable to convert symbol value to z3::expr"); } - ), static_cast(val)); - } - - auto z3_driver::as_z3_expression(const identifier_t& ref) -> z3::expr { - if(known.contains(ref.ident)) - return std::visit(ya::overload( - [this,&ref](const expr::clock_t& v) { return c.int_val(v.time_units) + c.int_const(delay_identifier.c_str()); }, - [this,&ref](auto&& x) { return as_z3_expression(known.at(ref.ident)); } - ), static_cast(known.at(ref.ident))); - auto it = find(ref.ident); - if(it == end) - throw std::logic_error{ref.ident + " not found"}; - return std::visit(ya::overload( - [this, &ref](const int& _) { return c.int_const(ref.ident.c_str()); }, - [this, &ref](const float& _) { return c.real_const(ref.ident.c_str()); }, - [this, &ref](const bool& _) { return c.bool_const(ref.ident.c_str()); }, - [this, &ref](const std::string& _) { return c.string_const(ref.ident.c_str()); }, - [this, &ref](const expr::clock_t& v){ return c.int_val(v.time_units) + c.int_const(delay_identifier.c_str()); }, - [](auto&& x){ throw std::logic_error("unable to convert symbol reference to z3::expr"); } - ), static_cast(it->second)); - } - - auto z3_driver::as_z3_expression(const syntax_tree_t &tree) -> z3::expr { - return std::visit(ya::overload( - [this](const identifier_t& r) { return as_z3_expression(r); }, - [&](const operator_t& o) { - switch (o.operator_type) { - case operator_type_t::minus: return as_z3_expression(tree.children()[0]) - as_z3_expression(tree.children()[1]); - case operator_type_t::plus: return as_z3_expression(tree.children()[0]) + as_z3_expression(tree.children()[1]); - case operator_type_t::star: return as_z3_expression(tree.children()[0]) * as_z3_expression(tree.children()[1]); - case operator_type_t::slash: return as_z3_expression(tree.children()[0]) / as_z3_expression(tree.children()[1]); - case operator_type_t::percent: return as_z3_expression(tree.children()[0]) % as_z3_expression(tree.children()[1]); - case operator_type_t::hat: return z3::pw(as_z3_expression(tree.children()[0]), as_z3_expression(tree.children()[1])); - case operator_type_t::_and: return as_z3_expression(tree.children()[0]) && as_z3_expression(tree.children()[1]); - case operator_type_t::_or: return as_z3_expression(tree.children()[0]) || as_z3_expression(tree.children()[1]); - case operator_type_t::_xor: return as_z3_expression(tree.children()[0]) xor as_z3_expression(tree.children()[1]); - case operator_type_t::_not: return !as_z3_expression(tree.children()[0]); - case operator_type_t::_implies: return implies(as_z3_expression(tree.children()[0]),as_z3_expression(tree.children()[1])); - case operator_type_t::gt: return (as_z3_expression(tree.children()[0]) > as_z3_expression(tree.children()[1])); - case operator_type_t::ge: return (as_z3_expression(tree.children()[0]) >= as_z3_expression(tree.children()[1])); - case operator_type_t::ne: return (as_z3_expression(tree.children()[0]) != as_z3_expression(tree.children()[1])); - case operator_type_t::ee: return (as_z3_expression(tree.children()[0]) == as_z3_expression(tree.children()[1])); - case operator_type_t::le: return (as_z3_expression(tree.children()[0]) <= as_z3_expression(tree.children()[1])); - case operator_type_t::lt: return (as_z3_expression(tree.children()[0]) < as_z3_expression(tree.children()[1])); - case operator_type_t::parentheses: return (as_z3_expression(tree.children()[0])); - } - throw std::logic_error("unsupported operator"); - }, - [this](const symbol_value_t& o){ return as_z3_expression(o); }, - [&](const root_t& r){ return as_z3_expression(tree.children()[0]); }, - [](auto&&){ throw std::logic_error("tree node type not recognized"); } - ), static_cast(tree.node)); - } -} diff --git a/src/expr-lang/CMakeLists.txt b/src/expr-lang/CMakeLists.txt new file mode 100644 index 0000000..dc33ac2 --- /dev/null +++ b/src/expr-lang/CMakeLists.txt @@ -0,0 +1,49 @@ +# MIT License +# +# Copyright (c) 2022 Asger Gitz-Johansen +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +cmake_minimum_required(VERSION 3.18) +project(expr_lang VERSION 3.0.0) +set(CMAKE_CXX_STANDARD 20) +set(CXX_STANDARD_REQUIRED ON) +find_package(FLEX REQUIRED) +find_package(BISON REQUIRED) +set(${PROJECT_NAME}_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR} CACHE STRING "${PROJECT_NAME}_BUILD_DIR" FORCE) # CPM compatibility +BISON_TARGET(expr_parser expr.y ${CMAKE_CURRENT_BINARY_DIR}/expr-parser.cpp) +FLEX_TARGET(expr_lexer expr.l ${CMAKE_CURRENT_BINARY_DIR}/expr-scanner.cpp) +ADD_FLEX_BISON_DEPENDENCY(expr_lexer expr_parser) +add_library(${PROJECT_NAME} SHARED + ${BISON_expr_parser_OUTPUTS} + ${FLEX_expr_lexer_OUTPUTS} + ast-factory.cpp + language-builder.cpp +) +target_include_directories(${PROJECT_NAME} PUBLIC + ${CMAKE_CURRENT_BINARY_DIR} + ${yaoverload_SOURCE_DIR}/include + ${yatree_SOURCE_DIR}/include + ${yatimer_SOURCE_DIR}/include + ${yahashcombine_SOURCE_DIR}/include + ${argvparse_SOURCE_DIR}/include + ${FLEX_INCLUDE_DIRS} + ${BISON_INCLUDE_DIRS} + .. .) +target_link_libraries(${PROJECT_NAME} expr) + diff --git a/src/expr-lang/ast-factory.cpp b/src/expr-lang/ast-factory.cpp new file mode 100644 index 0000000..520baae --- /dev/null +++ b/src/expr-lang/ast-factory.cpp @@ -0,0 +1,68 @@ +/* MIT License + * + * Copyright (c) 2022 Asger Gitz-Johansen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "ast-factory.h" +#include "symbol_table.h" +#include + +namespace expr { + ast_factory::ast_factory() {} + ast_factory::~ast_factory() {} + + auto ast_factory::build_operator(const operator_type_t& op, const syntax_tree_t& child) -> syntax_tree_t { + return build_operator(op, std::vector{child}); + } + + auto ast_factory::build_operator(const operator_type_t& op, const syntax_tree_t& child1, const syntax_tree_t& child2) -> syntax_tree_t { + return build_operator(op, std::vector{child1, child2}); + } + + auto ast_factory::build_operator(const operator_type_t& op, const std::vector& children) -> syntax_tree_t { + syntax_tree_t result{operator_t{op}}; + for(auto& child : children) + result.concat(child); + return result; + } + + auto ast_factory::build_literal(const symbol_value_t& value) -> syntax_tree_t { + return syntax_tree_t{value}; + } + + auto ast_factory::build_identifier(const std::string& identifier) -> syntax_tree_t { + return syntax_tree_t{identifier_t{identifier}}; + } + + auto ast_factory::build_root(const syntax_tree_t& child) -> syntax_tree_t { + return syntax_tree_t{}.concat(child); + } + + auto ast_factory::build_declaration(const std::string &identifier, const syntax_tree_t &tree, const symbol_access_modifier_t& access_modifier) -> syntax_tree_t { + return build_declaration(identifier, tree, symbol_type_name_t::_auto, access_modifier); + } + + auto ast_factory::build_declaration(const std::string& identifier, const syntax_tree_t& tree, const symbol_type_name_t& type_name, const symbol_access_modifier_t& access_modifier) -> syntax_tree_t { + // TODO: add the thing to the thing + std::cout << identifier << " " << type_name << " " << access_modifier << ": " << tree << std::endl; + return tree; + } +} + diff --git a/src/expr-lang/ast-factory.h b/src/expr-lang/ast-factory.h new file mode 100644 index 0000000..5ec0521 --- /dev/null +++ b/src/expr-lang/ast-factory.h @@ -0,0 +1,48 @@ +/* MIT License + * + * Copyright (c) 2022 Asger Gitz-Johansen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef EXPR_FACTORY_H +#define EXPR_FACTORY_H +#include "symbol/symbol_table.h" + +namespace expr { + /* + * Use this to build abstract syntax trees + * */ + class ast_factory { + public: + ast_factory(); + virtual ~ast_factory(); + virtual auto build_operator(const operator_type_t& op, const syntax_tree_t& child) -> syntax_tree_t; + virtual auto build_operator(const operator_type_t& op, const syntax_tree_t& child1, const syntax_tree_t& child2) -> syntax_tree_t; + virtual auto build_operator(const operator_type_t& op, const std::vector& children) -> syntax_tree_t; + virtual auto build_literal(const symbol_value_t& value) -> syntax_tree_t; + virtual auto build_identifier(const std::string& identifier) -> syntax_tree_t; + virtual auto build_root(const syntax_tree_t& child) -> syntax_tree_t; + + virtual auto build_declaration(const std::string& identifier, const syntax_tree_t& tree, const symbol_access_modifier_t& access_modifier) -> syntax_tree_t; + virtual auto build_declaration(const std::string& identifier, const syntax_tree_t& tree, const symbol_type_name_t& type_name = symbol_type_name_t::_auto, const symbol_access_modifier_t& access_modifier = symbol_access_modifier_t::_private) -> syntax_tree_t; + }; +} + +#endif + diff --git a/include/drivers/tree_driver.h b/src/expr-lang/expr-scanner.hpp similarity index 62% rename from include/drivers/tree_driver.h rename to src/expr-lang/expr-scanner.hpp index 9bee8dc..643a397 100644 --- a/include/drivers/tree_driver.h +++ b/src/expr-lang/expr-scanner.hpp @@ -20,28 +20,27 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -#ifndef EXPR_TREE_DRIVER_H -#define EXPR_TREE_DRIVER_H -#include "driver.h" +#ifndef EXPR_SCANNER_HPP +#define EXPR_SCANNER_HPP +#include "expr-lang/ast-factory.h" +#if ! defined(yyFlexLexerOnce) +#include +#endif + +#include "expr-parser.hpp" +#include "expr-lang/location.hh" namespace expr { - struct tree_driver : public driver { - tree_driver(const symbol_table_tree_t::iterator& it) : driver{}, it{it} {} - auto find(const std::string& identifier) const -> expr::symbol_table_t::const_iterator override { - auto* x = &(*it); - while(x) { - auto i = x->node.find(identifier); - if(i != x->node.end()) - return i; - if(!x->parent().has_value()) - return end; - x = x->parent().value(); - } - return end; - } + class scanner : public yyFlexLexer { + public: + scanner(std::istream& arg_yyin, std::ostream& arg_yyout, ast_factory* fct); + scanner(std::istream* arg_yyin = nullptr, std::ostream* arg_yyout = nullptr, ast_factory* fct = nullptr); + virtual ~scanner(); + virtual int yylex(parser::semantic_type* const lval, parser::location_type* location); private: - symbol_table_tree_t::iterator it; + ast_factory* fct; }; } -#endif //EXPR_TREE_DRIVER_H +#endif + diff --git a/src/expr-lang/expr.l b/src/expr-lang/expr.l new file mode 100644 index 0000000..ffefa8c --- /dev/null +++ b/src/expr-lang/expr.l @@ -0,0 +1,120 @@ +/* MIT License + * + * Copyright (c) 2022 Asger Gitz-Johansen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +%{ +#include "expr-parser.hpp" +#include "expr-scanner.hpp" +#undef YY_DECL +#define YY_DECL int expr::scanner::yylex(expr::parser::semantic_type* const lval, expr::parser::location_type* loc) +using token = expr::parser::token; +#define yyterminate() return( token::END ) +#define YY_NO_UNISTD_H +#define YY_USER_ACTION loc->step(); loc->columns(yyleng); +%} + +%option c++ noyywrap nodefault +%option yyclass="expr::scanner" +%option outfile="expr-scanner.cpp" +%option prefix="expr" + +/* ================================================== */ +id [a-z_A-Z]([.\(\)a-zA-Z_0-9]*[a-zA-Z_0-9]+)? +int [0-9]+[Ll]? +clk [0-9]+(_ms) +flt [0-9]+[.][0-9]+[fd]? +bool [Ff]alse|[Tt]rue +str \"(\\.|[^\\"])*\" +blank [ \t\r] +accmod [Pp](ublic|rivate|rotected) +type int|long|float|double|string|bool|clock|timer|var|auto + +%% + +{blank}+ /* nothing */ +\n+ /* nothing */ + +"-" return token::MINUS; +"+" return token::PLUS; +"*" return token::STAR; +"/" return token::SLASH; +"%" return token::PERCENT; +"^" return token::HAT; +"&&" return token::AND; +"||" return token::OR; +"^^" return token::XOR; +"=>" return token::IMPLIES; +">" return token::GT; +">=" return token::GE; +"==" return token::EE; +"!=" return token::NE; +"<=" return token::LE; +"<" return token::LT; +"!" return token::NOT; +"(" return token::LPAREN; +")" return token::RPAREN; +":=" return token::ASSIGN; +";" return token::TERM; +{type} { + lval->build(stotypename(std::string{YYText()})); + return token::TYPE; + } +{int} { + lval->build(atoi(YYText())); + return token::NUMBER; + } +{flt} { + lval->build(atof(YYText())); + return token::FLOAT; + } +{clk} { + lval->build(stoclk(YYText())); + return token::CLOCK; + } +{bool} { + lval->build(stob(YYText())); + return token::BOOL; + } +{str} { + std::string s{YYText()}; + lval->build(s.substr(1, s.size()-2)); + return token::STRING; + } +{accmod} { + lval->build(stoaccmod(std::string{YYText()})); + return token::ACCESS_MOD; + } +{id} { + lval->build(std::string{YYText()}); + return token::IDENTIFIER; + } +<> return token::YYEOF; +. throw expr::parser::syntax_error(*loc, "invalid character: " + std::string(YYText())); + +%% +/* ================================================== */ + +namespace expr { + scanner::scanner(std::istream& arg_yyin, std::ostream& arg_yyout, ast_factory* fct) : exprFlexLexer{arg_yyin, arg_yyout}, fct(fct) {} + scanner::scanner(std::istream* arg_yyin, std::ostream* arg_yyout, ast_factory* fct) : exprFlexLexer{arg_yyin, arg_yyout}, fct(fct) {} + scanner::~scanner() {} +} + diff --git a/src/expr-lang/expr.y b/src/expr-lang/expr.y new file mode 100644 index 0000000..0281bca --- /dev/null +++ b/src/expr-lang/expr.y @@ -0,0 +1,160 @@ +/* MIT License + * + * Copyright (c) 2022 Asger Gitz-Johansen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +%skeleton "lalr1.cc" +%require "3.2" +%defines "expr-parser.hpp" +%output "expr-parser.cpp" +%define api.parser.class { parser } +%define api.value.automove true +%define api.namespace { expr } +%define api.value.type variant +%define parse.assert +%define parse.lac full +%define parse.trace true +%define parse.error detailed +%locations + +%code requires { + #include + #include "symbol/symbol_table.h" + #include "ast-factory.h" + #include "language-builder.h" + + namespace expr { + class scanner; + struct parser_args { + std::optional expression; // used for debugging / error message purposes + scanner* scn; + ast_factory* fct; + language_builder* builder; + }; + } +} + +%parse-param { parser_args& args } + +%code { + #include + #include "expr-scanner.hpp" + #undef yylex + #define yylex args.scn->yylex +} + +/* ================================================== */ +%token YYEOF 0 +%token MINUS PLUS STAR SLASH PERCENT HAT AND OR XOR IMPLIES GT GE EE NE LE LT NOT LPAREN RPAREN ASSIGN TERM +%token IDENTIFIER +%token ACCESS_MOD +%token TYPE +%token NUMBER +%token FLOAT +%token BOOL +%token STRING +%token CLOCK +%nterm exp bin_op mono_op lit + +%left XOR +%left OR +%left AND +%left GT GE EE NE LE LT +%left PLUS MINUS STAR SLASH PERCENT HAT +%left IMPLIES +%precedence LPAREN NOT + +%% + +%start unit; + +unit: + statements { } +| exp { args.builder->add_expression(args.fct->build_root($1)); } +; + +statements: + %empty { } +| statement statements { } +; + +statement: + IDENTIFIER ASSIGN exp { args.builder->add_declaration($1, args.fct->build_root($3) ); } +| TYPE IDENTIFIER ASSIGN exp { args.builder->add_declaration($2, args.fct->build_root($4), $1 ); } +| ACCESS_MOD IDENTIFIER ASSIGN exp { args.builder->add_declaration($2, args.fct->build_root($4), $1 ); } +| ACCESS_MOD TYPE IDENTIFIER ASSIGN exp { args.builder->add_declaration($3, args.fct->build_root($5), $2, $1); } +| statement TERM { } +; + +exp: + lit { $$ = $1; } +| bin_op { $$ = $1; } +| mono_op { $$ = $1; } +; + +bin_op: + exp PLUS exp { $$ = args.fct->build_operator (expr::operator_type_t::plus,$1,$3); } +| exp MINUS exp { $$ = args.fct->build_operator (expr::operator_type_t::minus,$1,$3); } +| exp STAR exp { $$ = args.fct->build_operator (expr::operator_type_t::star,$1,$3); } +| exp SLASH exp { $$ = args.fct->build_operator (expr::operator_type_t::slash,$1,$3); } +| exp PERCENT exp { $$ = args.fct->build_operator (expr::operator_type_t::percent,$1,$3); } +| exp HAT exp { $$ = args.fct->build_operator (expr::operator_type_t::hat,$1,$3); } +| exp GT exp { $$ = args.fct->build_operator (expr::operator_type_t::gt,$1,$3); } +| exp GE exp { $$ = args.fct->build_operator (expr::operator_type_t::ge,$1,$3); } +| exp EE exp { $$ = args.fct->build_operator (expr::operator_type_t::ee,$1,$3); } +| exp NE exp { $$ = args.fct->build_operator (expr::operator_type_t::ne,$1,$3); } +| exp LE exp { $$ = args.fct->build_operator (expr::operator_type_t::le,$1,$3); } +| exp LT exp { $$ = args.fct->build_operator (expr::operator_type_t::lt,$1,$3); } +| exp OR exp { $$ = args.fct->build_operator (expr::operator_type_t::_or,$1,$3); } +| exp XOR exp { $$ = args.fct->build_operator (expr::operator_type_t::_xor,$1,$3); } +| exp IMPLIES exp { $$ = args.fct->build_operator (expr::operator_type_t::_implies,$1,$3); } +| exp AND exp { $$ = args.fct->build_operator (expr::operator_type_t::_and,$1,$3); } +; + +mono_op: + NOT exp { $$ = args.fct->build_operator (expr::operator_type_t::_not,$2); } +| LPAREN exp RPAREN { $$ = args.fct->build_operator (expr::operator_type_t::parentheses,$2); } +; + +lit: + NUMBER { $$ = args.fct->build_literal ($1); } +| MINUS NUMBER { $$ = args.fct->build_literal (-$2); } +| FLOAT { $$ = args.fct->build_literal ($1); } +| MINUS FLOAT { $$ = args.fct->build_literal (-$2); } +| STRING { $$ = args.fct->build_literal ($1); } +| BOOL { $$ = args.fct->build_literal ($1); } +| CLOCK { $$ = args.fct->build_literal ($1); } +| IDENTIFIER { $$ = args.fct->build_identifier ($1); } +; + +%% +/* ================================================== */ + +void expr::parser::error(const location_type& l, const std::string& msg) { + // TODO: This assumes that the input string is always 1 line - it will not look good on multiline inputs + if(args.expression) { + std::cerr << msg << "\n" << args.expression.value() << "\n"; + for(int i = 0; i < l.begin.column - 1; i++) + std::cerr << "_"; + std::cerr << "^\n"; + } else + std::cerr << msg << " at " << l << "\n"; // boring +} + diff --git a/src/expr-lang/language-builder.cpp b/src/expr-lang/language-builder.cpp new file mode 100644 index 0000000..2a02274 --- /dev/null +++ b/src/expr-lang/language-builder.cpp @@ -0,0 +1,45 @@ +/* MIT License + * + * Copyright (c) 2022 Asger Gitz-Johansen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "language-builder.h" + +namespace expr { + auto declaration_tree_builder::add_declaration(const std::string& identifier, const syntax_tree_t& tree, const symbol_access_modifier_t& access_modifier) -> declaration_tree_builder& { + result.declarations[identifier] = {access_modifier, symbol_type_name_t::_auto, tree}; + return *this; + } + + auto declaration_tree_builder::add_declaration(const std::string& identifier, const syntax_tree_t& tree, const symbol_type_name_t& type_name, const symbol_access_modifier_t& access_modifier) -> declaration_tree_builder& { + result.declarations[identifier] = {access_modifier, type_name, tree}; + return *this; + } + + auto declaration_tree_builder::add_expression(const syntax_tree_t& tree) -> declaration_tree_builder& { + result.raw_expression = tree; + return *this; + } + + auto declaration_tree_builder::build() -> result_t { + return result; // TODO: check for proper build + } +} + diff --git a/src/expr-lang/language-builder.h b/src/expr-lang/language-builder.h new file mode 100644 index 0000000..b324863 --- /dev/null +++ b/src/expr-lang/language-builder.h @@ -0,0 +1,61 @@ +/* MIT License + * + * Copyright (c) 2022 Asger Gitz-Johansen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef EXPR_LANG_SYMBOL_TABLE_BUILDER_H +#define EXPR_LANG_SYMBOL_TABLE_BUILDER_H +#include "symbol/symbol_table.h" +#include + +namespace expr { + class language_builder { + public: + language_builder() = default; + virtual ~language_builder() = default; + virtual auto add_declaration(const std::string& identifier, const syntax_tree_t& tree, const symbol_access_modifier_t& access_modifier) -> language_builder& = 0; + virtual auto add_declaration(const std::string& identifier, const syntax_tree_t& tree, const symbol_type_name_t& type_name = symbol_type_name_t::_auto, const symbol_access_modifier_t& access_modifier = symbol_access_modifier_t::_private) -> language_builder& = 0; + virtual auto add_expression(const syntax_tree_t& tree) -> language_builder& = 0; + }; + + class declaration_tree_builder : public language_builder { + public: + declaration_tree_builder() = default; + virtual ~declaration_tree_builder() = default; + struct result_t { + struct decl_t { + symbol_access_modifier_t access_modifier{}; + symbol_type_name_t type_name{}; + syntax_tree_t tree; + }; + std::map declarations{}; + std::optional raw_expression{}; + }; + auto add_declaration(const std::string& identifier, const syntax_tree_t& tree, const symbol_access_modifier_t& access_modifier) -> declaration_tree_builder& override; + auto add_declaration(const std::string& identifier, const syntax_tree_t& tree, const symbol_type_name_t& type_name = symbol_type_name_t::_auto, const symbol_access_modifier_t& access_modifier = symbol_access_modifier_t::_private) -> declaration_tree_builder& override; + auto add_expression(const syntax_tree_t& tree) -> declaration_tree_builder& override; + virtual auto build() -> result_t; + private: + result_t result{}; + }; +} + +#endif + diff --git a/src/extensions.h b/src/extensions.h new file mode 100644 index 0000000..2e59afa --- /dev/null +++ b/src/extensions.h @@ -0,0 +1,43 @@ +/* MIT License + * + * Copyright (c) 2022 Asger Gitz-Johansen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef EXPR_EXTENSIONS_H +#define EXPR_EXTENSIONS_H +#include +#include +// This file includes c++ extensions that could become their own yalibs libraries + +namespace expr { + inline constexpr auto hash_djb2a(const std::string_view sv) { + unsigned long hash{ 5381 }; + for (unsigned char c : sv) + hash = ((hash << 5) + hash) ^ c; + return hash; + } + + inline constexpr auto operator"" _sh(const char *str, size_t len) { + return hash_djb2a(std::string_view{ str, len }); + } +} + +#endif + diff --git a/src/generic-driver.cpp b/src/generic-driver.cpp new file mode 100644 index 0000000..3f8b20a --- /dev/null +++ b/src/generic-driver.cpp @@ -0,0 +1,126 @@ +/* MIT License + * + * Copyright (c) 2022 Asger Gitz-Johansen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "generic-driver.h" +#include "expr-scanner.hpp" +#include "expr-parser.hpp" +#include "ast-factory.h" +#include "expr-lang/language-builder.h" +#include "symbol_table.h" +#include "driver/evaluator.h" +#include +#ifdef ENABLE_Z3 +#include "driver/z3/z3-driver.h" +#endif + +namespace expr { + auto generic_driver::get_symbol_table(const symbol_table_t& env) -> symbol_table_t { + symbol_operator op{}; + evaluator e{{env}, op}; + symbol_table_t result{}; + for(auto& r : declarations) + result[r.first] = e.evaluate(r.second); + return result; + } + + auto generic_driver::get_expression_value(const symbol_table_t& env) -> symbol_value_t { + if(!expression) + throw std::logic_error("language result does not contain a rhs expression"); + symbol_operator op{}; + evaluator e{{env}, op}; + return e.evaluate(expression.value()); + } + + void generic_driver::print(const symbol_table_t& known, const symbol_table_t& unknown) { + std::cout << "ast:\n"; + for(auto& d : declarations) + std::cout << "\t> " << d.first << " :-> " << d.second << "\n"; + if(expression) + std::cout << "\t> raw rhs: " << expression.value() << "\n"; + std::cout << "evaluated:\n"; + for(auto& s : get_symbol_table(known + unknown)) + std::cout << "\t> " << s.first << " :-> " << s.second << "\n"; + if(expression) + std::cout << "\t> raw rhs: " << get_expression_value(known + unknown) << "\n"; + std::cout << "z3 sat check: (apply these changes to satisfy the expression)\n"; +#ifdef ENABLE_Z3 + if(!expression) { + std::cout << "\t> not a raw rhs, cannot check for sat\n"; + return; + } + auto ex = get_expression_value(known + unknown); + if(!std::holds_alternative(ex)) { + std::cout << "\t> z3 requires the expression to be a boolean expression\n"; + return; + } + z3_driver z{known, unknown}; + auto sol = z.find_solution(expression.value()); + if(!sol) + std::cout << "\t> unsatisfiable\n"; + else { + if(sol.value().empty()) + std::cout << "\t> already satisfied\n"; + else { + for(auto& v : sol.value()) + std::cout << "\t> " << v.first << " :-> " << v.second << "\n"; + } + } +#else + std::cout << "\t> not compiled with z3 support\n"; +#endif + } + + void generic_driver::parse_expressions(const std::string& s) { + std::istringstream iss{s}; + ast_factory factory{}; + declaration_tree_builder builder{}; + scanner sc{iss, std::cerr, &factory}; + parser_args pa{s, &sc, &factory, &builder}; + parser p{pa}; + if(p.parse() != 0) + throw std::logic_error("unable to parse the expression(s)" + s); + auto res = builder.build(); + for(auto& e : res.declarations) + declarations[e.first] = e.second.tree; + if(res.raw_expression) + expression = res.raw_expression.value(); + } + + auto generic_driver::parse_symbol_table(const std::string& s) -> symbol_table_t { + std::istringstream iss{s}; + ast_factory factory{}; + declaration_tree_builder builder{}; + scanner sc{iss, std::cerr, &factory}; + parser_args pa{s, &sc, &factory, &builder}; + parser p{pa}; + if(p.parse() != 0) + throw std::logic_error("unable to parse the expression(s): " + s); + auto res = builder.build(); + symbol_operator op{}; + evaluator e{{}, op}; + symbol_table_t result{}; + for(auto& r : res.declarations) + result[r.first] = e.evaluate(r.second.tree); + return result; + } +} + diff --git a/include/drivers/compiler.h b/src/generic-driver.h similarity index 55% rename from include/drivers/compiler.h rename to src/generic-driver.h index 6a472c5..1dd5a80 100644 --- a/include/drivers/compiler.h +++ b/src/generic-driver.h @@ -20,30 +20,25 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -#ifndef EXPR_COMPILER_H -#define EXPR_COMPILER_H -#include +#ifndef GENERIC_DRIVER_H +#define GENERIC_DRIVER_H +#include +#include +#include #include -#include "drivers/driver.h" +#include "symbol_table.h" namespace expr { - class compiler : public driver { - public: - using compiled_expr_t = syntax_tree_t; -#ifndef NDEBUG - using compiled_expr_collection_t = std::map; -#else - using compiled_expr_collection_t = std::unordered_map; -#endif - compiler(std::initializer_list environments) : driver{environments}, trees{} {} - int parse(const std::string &f) override; - auto get_symbol(const std::string &identifier) -> syntax_tree_t override; - void add_tree(const syntax_tree_t& tree) override; - void add_tree(const std::string& identifier, const syntax_tree_t& tree) override; - void add_tree(const std::string& access_modifier, const std::string& identifier, const syntax_tree_t& tree) override; - - compiled_expr_collection_t trees; + struct generic_driver { + std::unordered_map declarations; + std::optional expression; + auto get_symbol_table(const symbol_table_t& env) -> symbol_table_t; + auto get_expression_value(const symbol_table_t& env) -> symbol_value_t; + void parse_expressions(const std::string& s); + auto parse_symbol_table(const std::string& s) -> symbol_table_t; + void print(const symbol_table_t& known, const symbol_table_t& unknown); }; } -#endif //EXPR_COMPILER_H +#endif + diff --git a/src/main.cpp b/src/main.cpp index 53ecd9e..4dc98b2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,124 +21,70 @@ * SOFTWARE. */ #include -#include "drivers/interpreter.h" -#include "drivers/compiler.h" -#include "drivers/z3_driver.h" -#include "config.h" -#include "drivers/tree_interpreter.h" +#include +#include +#include #include +#include #include -#include +#include +#include "config.h" +#include "generic-driver.h" int main (int argc, char *argv[]) { using namespace expr; - symbol_table_t env{}; - env["false_b"] = false; - env["one_i"] = 1; - env["two_f"] = 2.0f; - env["hello_s"] = "Hello"; std::vector my_options = { - {"expression", 'e', argument_requirement::REQUIRE_ARG, "(required) provide the expression to process"}, - {"driver", 'd', argument_requirement::REQUIRE_ARG, "(required) determine which driver to use [z3, interpreter, compiler]"}, - {"environment", 'm', argument_requirement::OPTIONAL_ARG, "provide an environment"}, - {"unknown-environment", 'u', argument_requirement::REQUIRE_ARG, "provide an environment of unknown variables (z3 driver only)"}, - {"parser-trace", 'p', argument_requirement::NO_ARG, "enable tracing for the parser"}, - {"scanner-trace", 's', argument_requirement::NO_ARG, "enable tracing for the scanner"}, + {"expression", 'e', argument_requirement::REQUIRE_ARG, "provide the expression to process, prompt appears if not provided"}, + {"environment", 'm', argument_requirement::OPTIONAL_ARG, "provide an environment, use \"-\" for interactive prompt"}, + {"unknown-environment", 'u', argument_requirement::REQUIRE_ARG, "provide an environment of unknown variables (z3 only), use \"-\" for interactive prompt"}, }; auto cli_arguments = get_arguments(my_options, argc, argv); - if(cli_arguments["help"] || !cli_arguments["expression"] || !cli_arguments["driver"]) { + if(cli_arguments["help"]) { std::cout << "=================== Welcome to the " << PROJECT_NAME << " v" << PROJECT_VER << " demo ==================\n" << "USAGE: " << argv[0] << " [OPTIONS]\n" << "\n" - << "For this demo, a simple environment has been provided. (see below)\n" - << "You can use these variables on the right-hand-side of your expressions\n" - << "Another sentence in my help message" - << "like so: 'a := one + 30'. Variable assignments are done atomically, so\n" - << "statements lie these: 'a := 2 ; b := a + 1' will not compile, because\n" - << "Another thing" - << "the variable 'a' is not defined before AFTER all assignments have been\n" - << "evaluated and performed.\n" - << "PROVIDED ENVIRONMENT:\n" - << env + << "With this demo, you can experiment with the language of expr.\n" + << "You can manipulate variables by assigning them directly to a value, or\n" + << "calculate based on some expression.\n" + << "Assignment is done like so: 'a := one + 30'. Variable assignments are\n" + << "done atomically, so statements like these: 'a := 2 ; b := a + 1' will\n" + << "not compile, because the variable 'a' is not defined before AFTER all\n" + << "assignments have been evaluated and performed.\n" << "\n" << "OPTIONS:\n" << my_options << "======================================================================\n"; return 0; } - try { - if(cli_arguments["environment"]) { - interpreter i{{}}; - auto res = i.parse(cli_arguments["environment"].as_string()); - if(res != 0) { - std::cout << "error: " << i.error << std::endl; - return res; - } - env = i.result; - } - symbol_table_t unknowns{}; - if(cli_arguments["unknown-environment"]) { - interpreter i{{}}; - auto res = i.parse(cli_arguments["unknown-environment"].as_string()); - if(res != 0) { - std::cout << "error: " << i.error << std::endl; - return res; - } - unknowns = i.result; - } - - std::shared_ptr drv{}; - if(cli_arguments["driver"].as_string() == "compiler") - drv = std::make_shared(std::initializer_list>{env}); - else if(cli_arguments["driver"].as_string() == "interpreter") - drv = std::make_shared(std::initializer_list>{env}); -#ifdef ENABLE_Z3 - else if(cli_arguments["driver"].as_string() == "z3") - drv = std::make_shared(env,unknowns); -#endif - else { - std::cerr << "no such driver available " << cli_arguments["driver"].as_string() - << " please check your spelling and compilation flags" << std::endl; - return 1; - } - - drv->trace_parsing = static_cast(cli_arguments["parser-trace"]); - drv->trace_scanning = static_cast(cli_arguments["scanner-trace"]); - ya::timer t{}; - auto res = drv->parse(cli_arguments["expression"].as_string()); - if(res != 0) { - std::cout << "error: " << drv->error << "\n"; - return res; - } - - if(cli_arguments["driver"].as_string() == "compiler") { - auto drv_c = std::dynamic_pointer_cast(drv); - for(auto& tree : drv_c->trees) - std::cout << tree.first << ": " << tree.second << "\n"; - std::cout << "\n"; - interpreter i{{env}}; - std::cout << i.evaluate(drv_c->trees) << "\n"; - } - if(cli_arguments["driver"].as_string() == "interpreter") { - auto drv_i = std::dynamic_pointer_cast(drv); - if(!drv_i->result.empty()) - std::cout << drv_i->result << "\n"; - std::cout << "expression_result: " << drv_i->expression_result << std::endl; - } -#ifdef ENABLE_Z3 - if(cli_arguments["driver"].as_string() == "z3") { - auto drv_z = std::dynamic_pointer_cast(drv); - std::cout << "result: \n" << drv_z->result; - - std::cout << "\n==========\n"; - std::cout << "env + result: \n" << (env + drv_z->result); - } -#endif - std::cout << "\n" << t.milliseconds_elapsed() << "ms" << std::endl; - return res; - } catch(const std::exception& e) { - std::cout << e.what() << std::endl; - return 1; + std::string environment = cli_arguments["environment"].as_string_or_default(""); + if(environment == "-") { + std::rewind(stdin); + std::cout << "provide an environment. End with <> (ctrl+d):\n<<\n"; + std::istreambuf_iterator begin(std::cin), end; + environment = std::string(begin, end); + std::cout << "\n>>\n"; + } + std::string unknown_environment = cli_arguments["unknown-environment"].as_string_or_default(""); + if(unknown_environment == "-") { + std::rewind(stdin); + std::cout << "provide an environment. End with <> (ctrl+d):\n<<\n"; + std::istreambuf_iterator begin(std::cin), end; + unknown_environment = std::string(begin, end); + std::cout << "\n>>\n"; } + std::string expression = cli_arguments["expression"].as_string_or_default("-"); + if(expression == "-") { + std::rewind(stdin); + std::cout << "provide an expression. End with <> (ctrl+d):\n<<\n"; + std::istreambuf_iterator begin(std::cin), end; + expression = std::string(begin, end); + std::cout << "\n>>\n"; + } + + expr::generic_driver c{}; + c.parse_expressions(expression); + c.print(c.parse_symbol_table(environment), c.parse_symbol_table(unknown_environment)); + return 0; } + diff --git a/src/man/expr.3 b/src/man/expr.3 index e9e21d8..658f73f 100644 --- a/src/man/expr.3 +++ b/src/man/expr.3 @@ -1,4 +1,4 @@ -.TH expr 3 2022-10-11 "version v2.1.0" expr +.TH expr 3 2023-03-05 "version v3.0.0" expr .SH NAME expr \- a variable environment manipulation expression parser written in C++20 with flex and bison. @@ -36,21 +36,22 @@ Since \fIa\fR maps to \fI0\fR and \fIb\fR maps to \fI1\fR, this example would re variables \fIx\fR to \fI1\fR (0 + 1 = 1) and \fIy\fR to \fI1\fR (33 modulo (1 times 2) = 1). Take careful note that the resulting symbol table of this program does \fBnot\fR include the variables \fIa\fR and \fIb\fR. -.SH DRIVERS +.SH USE CASES .B expr -provides a series of expression drivers with slightly different use cases: -\fIcompiler\fR, \fIinterpreter\fR, \fItree_interpreter\fR and \fIz3\fR. -The \fIcompiler\fR driver can compile a string, or list of strings into a binary format based on +provides the facilities for some slightly different use cases: +\fIcompilation\fR, \fIevaluation\fR and \fIsat check\fR with Z3 sat solver. +When \fIcompiling\fR, you can compile a string, or list of strings into a binary format based on an abstract syntax tree (AST) structure. -The \fIinterpreter\fR and \fItree_interpreter\fR drivers can accept either the binary AST format, -or an expression string and returns a modified \fBsymbol table\fR based on the interpreted expression. -The \fIz3\fR driver is a special driver, using the Z3 sat solver to check if the provided expression -string (must be a boolean expression) is satisfiable, and then generate a \fBsymbol table\fR that contains -the variable assignments required to force the expression to \fRtrue\fR. +When \fIevaluating\fR, you feed the AST format into the provided \fBevaluator\fR class and it returns a symbol table. +Note that symbol tables can be "combined" with eachother, so a symbol table can be interpreted as either a specific +state of symbols and values, or a "difference" from some other state. You can combine tables with the \fB+-operator\fR, +or the provided \fBput\fR method. -Note that the \fIz3\fR driver might not be available if not explicitly told to be compiled with the rest of the library. +When \fIsat solving\fR, you can use the Z3 sat solver to check if a provided expression +string (must be a boolean expression) is satisfiable, and then generate a symbol table that contains +the variable assignments required to force the expression to \fRtrue\fR. -The \fItree_*\fR driver variants operate on trees of \fBsymbol tables\fR to provide scopes with variable shadowing. +Note that the \fIz3\fR functionality might not be available if not explicitly told to be compiled with the rest of the library. .SH AUTHOR Asger Gitz\-Johansen . @@ -65,4 +66,4 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. .SH SEE ALSO -expr_demo(1), aaltitoad(3) + diff --git a/include/operations.h b/src/operations/operations.h similarity index 99% rename from include/operations.h rename to src/operations/operations.h index 7d3a7b1..3b0bbf3 100644 --- a/include/operations.h +++ b/src/operations/operations.h @@ -57,3 +57,4 @@ namespace expr { } #endif //EXPR_OPERATIONS_H + diff --git a/src/operations/symbol-operator.h b/src/operations/symbol-operator.h new file mode 100644 index 0000000..e80ccbd --- /dev/null +++ b/src/operations/symbol-operator.h @@ -0,0 +1,51 @@ +/* MIT License + * + * Copyright (c) 2022 Asger Gitz-Johansen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef EXPR_GENERIC_SYMBOL_OPERATOR_H +#define EXPR_GENERIC_SYMBOL_OPERATOR_H +#include "operations/operations.h" +#include "symbol_table.h" + +namespace expr { + struct symbol_operator { + virtual auto add(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t { return ::operator+(a,b); }; + virtual auto sub(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t { return ::operator-(a,b); }; + virtual auto mul(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t { return a * b; }; + virtual auto div(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t { return a / b; }; + virtual auto mod(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t { return a % b; }; + virtual auto pow(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t { return a ^ b; }; + virtual auto _not(const symbol_value_t &a) const -> symbol_value_t { return not_(a); } + virtual auto _and(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t { return and_(a,b); } + virtual auto _or(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t { return or_(a,b); } + virtual auto _xor(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t { return xor_(a,b); } + virtual auto _implies(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t { return implies_(a,b); } + virtual auto gt(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t { return gt_(a,b); } + virtual auto ge(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t { return ge_(a,b); } + virtual auto ee(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t { return ee_(a,b); } + virtual auto ne(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t { return ne_(a,b); } + virtual auto le(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t { return le_(a,b); } + virtual auto lt(const symbol_value_t &a, const symbol_value_t &b) const -> symbol_value_t { return lt_(a,b); } + }; +} + +#endif + diff --git a/src/parser/lex/expr_driver_footer.l b/src/parser/lex/expr_driver_footer.l deleted file mode 100644 index c57b777..0000000 --- a/src/parser/lex/expr_driver_footer.l +++ /dev/null @@ -1,12 +0,0 @@ -m4_changequote() - -void expr::driver::scan_begin() { - yy_flex_debug = trace_scanning; - if(file.empty() || file == "-") - yyin = stdin; - else - yy_scan_string(file.c_str()); -} - -void expr::driver::scan_end() { -} diff --git a/src/parser/lex/footer.l b/src/parser/lex/footer.l deleted file mode 100644 index 15fc56a..0000000 --- a/src/parser/lex/footer.l +++ /dev/null @@ -1,38 +0,0 @@ -m4_changequote() - -PARSER_NS ::parser::symbol_type make_NUMBER(const std::string &s, const PARSER_NS ::parser::location_type& loc) { - errno = 0; - long n = strtol (s.c_str(), NULL, 10); - if (! (INT_MIN <= n && n <= INT_MAX && errno != ERANGE)) - throw PARSER_NS ::parser::syntax_error (loc, "integer is out of range: " + s); - return PARSER_NS ::parser::make_NUMBER ((int) n, loc); -} - -PARSER_NS ::parser::symbol_type make_FLOAT(const std::string &s, const PARSER_NS ::parser::location_type& loc) { - try { - double n = std::stod(s.c_str()); - return PARSER_NS ::parser::make_FLOAT((double)n, loc); - } catch(std::out_of_range& e) { - throw PARSER_NS ::parser::syntax_error (loc, "double is out of range: " + s); - } -} - -PARSER_NS ::parser::symbol_type make_STRING(const std::string &s, const PARSER_NS ::parser::location_type& loc) { - return PARSER_NS ::parser::make_STRING(s.substr(1, s.size()-2), loc); -} - -PARSER_NS ::parser::symbol_type make_BOOL(std::string s, const PARSER_NS ::parser::location_type& loc) { - std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c){ return std::tolower(c); }); - bool b; - std::istringstream(s) >> std::boolalpha >> b; - return PARSER_NS ::parser::make_BOOL(b, loc); -} - -PARSER_NS ::parser::symbol_type make_CLOCK(const std::string &s, const PARSER_NS ::parser::location_type& loc) { - try { - auto c = expr::stoclk(s.c_str()); - return PARSER_NS ::parser::make_CLOCK(c, loc); - } catch(std::out_of_range& e) { - throw PARSER_NS ::parser::syntax_error (loc, "clock value is out of range: " + s); - } -} diff --git a/src/parser/lex/includes.l b/src/parser/lex/includes.l deleted file mode 100644 index caba52d..0000000 --- a/src/parser/lex/includes.l +++ /dev/null @@ -1,10 +0,0 @@ -%{ -#include -#include -#include -#include // strerror -#include -#include -#include "drivers/driver.h" -#include "parser.hpp" -%} diff --git a/src/parser/lex/lexer.l b/src/parser/lex/lexer.l deleted file mode 100644 index 8e6f332..0000000 --- a/src/parser/lex/lexer.l +++ /dev/null @@ -1,38 +0,0 @@ -m4_changequote() - -{blank}+ loc.step(); -\n+ { loc.lines(yyleng); loc.step(); } - -"-" return PARSER_NS ::parser::make_MINUS (loc); -"+" return PARSER_NS ::parser::make_PLUS (loc); -"*" return PARSER_NS ::parser::make_STAR (loc); -"/" return PARSER_NS ::parser::make_SLASH (loc); -"%" return PARSER_NS ::parser::make_PERCENT(loc); -"^" return PARSER_NS ::parser::make_HAT (loc); -"&&" return PARSER_NS ::parser::make_AND (loc); -"||" return PARSER_NS ::parser::make_OR (loc); -"^^" return PARSER_NS ::parser::make_XOR (loc); -"=>" return PARSER_NS ::parser::make_IMPLIES(loc); -">" return PARSER_NS ::parser::make_GT (loc); -">=" return PARSER_NS ::parser::make_GE (loc); -"==" return PARSER_NS ::parser::make_EE (loc); -"!=" return PARSER_NS ::parser::make_NE (loc); -"<=" return PARSER_NS ::parser::make_LE (loc); -"<" return PARSER_NS ::parser::make_LT (loc); -"!" return PARSER_NS ::parser::make_NOT (loc); -"(" return PARSER_NS ::parser::make_LPAREN (loc); -")" return PARSER_NS ::parser::make_RPAREN (loc); -":=" return PARSER_NS ::parser::make_ASSIGN (loc); -";" return PARSER_NS ::parser::make_TERM (loc); - -{accmod} return PARSER_NS ::parser::make_ACCMOD(yytext, loc); -{type} return PARSER_NS ::parser::make_TYPE(loc); - -{int} return make_NUMBER(yytext, loc); -{flt} return make_FLOAT(yytext, loc); -{str} return make_STRING(yytext, loc); -{clk} return make_CLOCK(yytext, loc); -{bool} return make_BOOL(yytext, loc); -{id} return PARSER_NS ::parser::make_IDENTIFIER (yytext, loc); -. { throw PARSER_NS ::parser::syntax_error(loc, "invalid character: " + std::string(yytext)); } -<> return PARSER_NS ::parser::make_YYEOF (loc); diff --git a/src/parser/lex/scanner.l b/src/parser/lex/scanner.l deleted file mode 100644 index 0a11e0f..0000000 --- a/src/parser/lex/scanner.l +++ /dev/null @@ -1,28 +0,0 @@ -m4_changequote() -/* -m4_include(../mit.license) -*/ -m4_define(PARSER_NS, yy) -m4_include(includes.l) -m4_include(skeleton.l) -m4_include(tokens.l) - -%{ - // Code run each time a pattern is matched. - #define YY_USER_ACTION loc.columns(yyleng); -%} - -%% -%{ - // A handy shortcut to the location held by the driver. - yy::location& loc = drv->location; - // Code run each time yylex is called. - loc.step(); -%} - -m4_include(lexer.l) - -%% - -m4_include(footer.l) -m4_include(expr_driver_footer.l) diff --git a/src/parser/lex/skeleton.l b/src/parser/lex/skeleton.l deleted file mode 100644 index d87ed6b..0000000 --- a/src/parser/lex/skeleton.l +++ /dev/null @@ -1,83 +0,0 @@ -m4_changequote() - -%{ -#if defined __clang__ -# define CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) -#endif - -// Clang and ICC like to pretend they are GCC. -#if defined __GNUC__ && !defined __clang__ && !defined __ICC -# define GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) -#endif - -// Pacify warnings in yy_init_buffer (observed with Flex 2.6.4) -// and GCC 6.4.0, 7.3.0 with -O3. -#if defined GCC_VERSION && 600 <= GCC_VERSION -# pragma GCC diagnostic ignored "-Wnull-dereference" -#endif - -// This example uses Flex's C back end, yet compiles it as C++. -// So expect warnings about C style casts and NULL. -#if defined CLANG_VERSION && 500 <= CLANG_VERSION -# pragma clang diagnostic ignored "-Wold-style-cast" -# pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" -#elif defined GCC_VERSION && 407 <= GCC_VERSION -# pragma GCC diagnostic ignored "-Wold-style-cast" -# pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" -#endif - -#define FLEX_VERSION (YY_FLEX_MAJOR_VERSION * 100 + YY_FLEX_MINOR_VERSION) - -// Old versions of Flex (2.5.35) generate an incomplete documentation comment. -// -// In file included from src/scan-code-c.c:3: -// src/scan-code.c:2198:21: error: empty paragraph passed to '@param' command -// [-Werror,-Wdocumentation] -// * @param line_number -// ~~~~~~~~~~~~~~~~~^ -// 1 error generated. -#if FLEX_VERSION < 206 && defined CLANG_VERSION -# pragma clang diagnostic ignored "-Wdocumentation" -#endif - -// Old versions of Flex (2.5.35) use 'register'. Warnings introduced in -// GCC 7 and Clang 6. -#if FLEX_VERSION < 206 -# if defined CLANG_VERSION && 600 <= CLANG_VERSION -# pragma clang diagnostic ignored "-Wdeprecated-register" -# elif defined GCC_VERSION && 700 <= GCC_VERSION -# pragma GCC diagnostic ignored "-Wregister" -# endif -#endif - -#if FLEX_VERSION < 206 -# if defined CLANG_VERSION -# pragma clang diagnostic ignored "-Wconversion" -# pragma clang diagnostic ignored "-Wdocumentation" -# pragma clang diagnostic ignored "-Wshorten-64-to-32" -# pragma clang diagnostic ignored "-Wsign-conversion" -# elif defined GCC_VERSION -# pragma GCC diagnostic ignored "-Wconversion" -# pragma GCC diagnostic ignored "-Wsign-conversion" -# endif -#endif - -// Flex 2.6.4, GCC 9 -// warning: useless cast to type 'int' [-Wuseless-cast] -// 1361 | YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2); -// | ^ -#if defined GCC_VERSION && 900 <= GCC_VERSION -# pragma GCC diagnostic ignored "-Wuseless-cast" -#endif -%} - -%option noyywrap nounput noinput batch debug - -%{ - // A number symbol corresponding to the value in provided string. - PARSER_NS ::parser::symbol_type make_NUMBER(const std::string &s, const PARSER_NS ::parser::location_type& loc); - PARSER_NS ::parser::symbol_type make_FLOAT(const std::string &s, const PARSER_NS ::parser::location_type& loc); - PARSER_NS ::parser::symbol_type make_STRING(const std::string &s, const PARSER_NS ::parser::location_type& loc); - PARSER_NS ::parser::symbol_type make_BOOL(std::string s, const PARSER_NS ::parser::location_type& loc); - PARSER_NS ::parser::symbol_type make_CLOCK(const std::string& s, const PARSER_NS ::parser::location_type& loc); -%} diff --git a/src/parser/lex/tokens.l b/src/parser/lex/tokens.l deleted file mode 100644 index 3a9d836..0000000 --- a/src/parser/lex/tokens.l +++ /dev/null @@ -1,14 +0,0 @@ -m4_changequote() - -%{ - // TODO: Remove [ðđ€\(\)] -%} -id [a-z_A-Z]([.ðđ€\(\)a-zA-Z_0-9]*[a-zA-Z_0-9]+)? -int [0-9]+[Ll]? -clk [0-9]+(_ms) -flt [0-9]+[.][0-9]+[fd]? -bool [Ff]alse|[Tt]rue -str \"(\\.|[^\\"])*\" -blank [ \t\r] -accmod [Pp](ublic|rivate|rotected) -type int|long|float|double|string|bool|clock|timer|var|auto diff --git a/src/parser/mit.license b/src/parser/mit.license deleted file mode 100644 index ca9b9a0..0000000 --- a/src/parser/mit.license +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 Asger Gitz-Johansen - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/src/parser/yacc/exp.y b/src/parser/yacc/exp.y deleted file mode 100644 index 6c02761..0000000 --- a/src/parser/yacc/exp.y +++ /dev/null @@ -1,42 +0,0 @@ -m4_changequote() - -exp: - lit { $$ = $1; } -| bin_op { $$ = $1; } -| mono_op { $$ = $1; } -; - -bin_op: - exp PLUS exp { $$ = BINOP_CTOR (plus,$1,$3); } -| exp MINUS exp { $$ = BINOP_CTOR (minus,$1,$3); } -| exp STAR exp { $$ = BINOP_CTOR (star,$1,$3); } -| exp SLASH exp { $$ = BINOP_CTOR (slash,$1,$3); } -| exp PERCENT exp { $$ = BINOP_CTOR (percent,$1,$3); } -| exp HAT exp { $$ = BINOP_CTOR (hat,$1,$3); } -| exp GT exp { $$ = BINOP_CTOR (gt,$1,$3); } -| exp GE exp { $$ = BINOP_CTOR (ge,$1,$3); } -| exp EE exp { $$ = BINOP_CTOR (ee,$1,$3); } -| exp NE exp { $$ = BINOP_CTOR (ne,$1,$3); } -| exp LE exp { $$ = BINOP_CTOR (le,$1,$3); } -| exp LT exp { $$ = BINOP_CTOR (lt,$1,$3); } -| exp OR exp { $$ = BINOP_CTOR (_or,$1,$3); } -| exp XOR exp { $$ = BINOP_CTOR (_xor,$1,$3); } -| exp IMPLIES exp { $$ = BINOP_CTOR (_implies,$1,$3); } -| exp AND exp { $$ = BINOP_CTOR (_and,$1,$3); } -; - -mono_op: - NOT exp { $$ = MONOOP_CTOR (_not,$2); } -| LPAREN exp RPAREN { $$ = MONOOP_CTOR (parentheses,$2); } -; - -lit: - "number" { $$ = LIT_CTOR ($1); } -| MINUS "number" { $$ = LIT_CTOR (-$2); } -| "float" { $$ = LIT_CTOR ($1); } -| MINUS "float" { $$ = LIT_CTOR (-$2); } -| "string" { $$ = LIT_CTOR ($1); } -| "bool" { $$ = LIT_CTOR ($1); } -| "identifier" { $$ = IDENT_CTOR ($1); } -| "clk" { $$ = LIT_CTOR ($1); } -; diff --git a/src/parser/yacc/parser.y b/src/parser/yacc/parser.y deleted file mode 100644 index c2d5177..0000000 --- a/src/parser/yacc/parser.y +++ /dev/null @@ -1,33 +0,0 @@ -m4_changequote() -/* -m4_include(../mit.license) -*/ -m4_define(PARSER_NS, yy) -m4_include(skeleton.y) -%define api.prefix {PARSER_NS} -m4_include(tokens.y) -m4_include(token_types.y) - -%% -%start unit; -unit: - statements { } -| exp { drv->add_tree($1); } -; - -statements: - %empty { } -| statement statements { } -; - -statement: - "identifier" ASSIGN exp { drv->add_tree($1, expr::syntax_tree_t{}.concat($3)); } -| "type" "identifier" ASSIGN exp { drv->add_tree($2, expr::syntax_tree_t{}.concat($4)); } -| "access_modifier" "identifier" ASSIGN exp { drv->add_tree($1, $2, expr::syntax_tree_t{}.concat($4)); } -| "access_modifier" "type" "identifier" ASSIGN exp { drv->add_tree($1, $3, expr::syntax_tree_t{}.concat($5)); } -| statement TERM { } -; - -m4_include(exp.y) - -%% diff --git a/src/parser/yacc/skeleton.y b/src/parser/yacc/skeleton.y deleted file mode 100644 index 5ab4626..0000000 --- a/src/parser/yacc/skeleton.y +++ /dev/null @@ -1,37 +0,0 @@ -m4_changequote() -%skeleton "lalr1.cc" -%require "3.5" - -%define api.token.raw - -%define api.token.constructor -%define api.value.type variant -%define parse.assert - -// Forward declare the driver (include later) -%code requires { - #include - #include - #include - namespace expr { class driver; } -} - -%param { expr::driver* drv } - -// Enable parser location tracking -%locations - -// Enable parser tracing and detailed errors -%define parse.trace -%define parse.error verbose -// Enable full lookahead to avoid incorrect error information -// See https://www.gnu.org/software/bison/manual/html_node/LAC.html for details -%define parse.lac full - -// Include the driver -%code { - #include "drivers/driver.h" - void PARSER_NS ::parser::error (const location_type& l, const std::string& m) { - std::cerr << l << ": " << m << '\n'; - } -} diff --git a/src/parser/yacc/token_types.y b/src/parser/yacc/token_types.y deleted file mode 100644 index d0c0df5..0000000 --- a/src/parser/yacc/token_types.y +++ /dev/null @@ -1,2 +0,0 @@ -m4_changequote() -%nterm exp bin_op mono_op lit diff --git a/src/parser/yacc/tokens.y b/src/parser/yacc/tokens.y deleted file mode 100644 index 91f2abc..0000000 --- a/src/parser/yacc/tokens.y +++ /dev/null @@ -1,46 +0,0 @@ -m4_changequote() - -%define api.token.prefix {TOK_} -%token YYEOF 0 -%token - MINUS "-" - PLUS "+" - STAR "*" - SLASH "/" - PERCENT "%" - HAT "^" - AND "&&" - OR "||" - XOR "^^" - IMPLIES "=>" - GT ">" - GE ">=" - EE "==" - NE "!=" - LE "<=" - LT "<" - NOT "!" - LPAREN "(" - RPAREN ")" - ASSIGN ":=" - TYPE "type" - TERM ";" -; - -// Identifiers are strings -%token IDENTIFIER "identifier" -%token ACCMOD "access_modifier" -%token NUMBER "number" -%token FLOAT "float" -%token BOOL "bool" -%token CLOCK "clk" -%token STRING "string" -%printer { yyo << $$; } <*>; - -%left XOR -%left OR -%left AND -%left GT GE EE NE LE LT -%left PLUS MINUS STAR SLASH PERCENT HAT -%left IMPLIES -%precedence LPAREN NOT diff --git a/src/clock.cpp b/src/symbol/clock.cpp similarity index 100% rename from src/clock.cpp rename to src/symbol/clock.cpp diff --git a/include/clock.h b/src/symbol/clock.h similarity index 99% rename from include/clock.h rename to src/symbol/clock.h index 2aa0839..6978a23 100644 --- a/include/clock.h +++ b/src/symbol/clock.h @@ -43,3 +43,4 @@ namespace expr { auto operator"" _ms(unsigned long long val) -> expr::clock_t; #endif //EXPR_CLOCK_H + diff --git a/src/symbol_table.cpp b/src/symbol/symbol_table.cpp similarity index 75% rename from src/symbol_table.cpp rename to src/symbol/symbol_table.cpp index 1c81aee..26dea7e 100644 --- a/src/symbol_table.cpp +++ b/src/symbol/symbol_table.cpp @@ -21,7 +21,9 @@ * SOFTWARE. */ #include "symbol_table.h" +#include "extensions.h" #include +#include namespace expr { auto symbol_table_t::operator+=(const symbol_table_t &other) -> symbol_table_t& { @@ -209,6 +211,73 @@ namespace expr { [](auto &&v) -> std::string { return std::to_string(v); } }, static_cast(v)); } + + auto stob(const char *s) -> bool { + auto cpy = std::string{s}; + std::transform(cpy.begin(), cpy.end(), cpy.begin(), + [](unsigned char c) { return std::tolower(c); }); + bool b; + std::istringstream(s) >> std::boolalpha >> b; + return b; + } + + auto stotypename(const std::string& s) -> symbol_type_name_t { + switch(hash_djb2a(s)) { + case "auto"_sh: return symbol_type_name_t::_auto; + case "var"_sh: return symbol_type_name_t::_auto; + case "int"_sh: return symbol_type_name_t::_int; + case "long"_sh: return symbol_type_name_t::_int; + case "float"_sh: return symbol_type_name_t::_float; + case "double"_sh: return symbol_type_name_t::_float; + case "string"_sh: return symbol_type_name_t::_string; + case "bool"_sh: return symbol_type_name_t::_bool; + case "clock"_sh: return symbol_type_name_t::_clock; + case "timer"_sh: return symbol_type_name_t::_clock; + default: throw std::logic_error(std::string{s+" is not a valid type name"}.c_str()); + } + } + + auto stoaccmod(const std::string& s) -> symbol_access_modifier_t { + switch(hash_djb2a(s)) { + case "public"_sh: return symbol_access_modifier_t::_public; + case "Public"_sh: return symbol_access_modifier_t::_public; + case "private"_sh: return symbol_access_modifier_t::_private; + case "Private"_sh: return symbol_access_modifier_t::_private; + case "protected"_sh: return symbol_access_modifier_t::_protected; + case "Protected"_sh: return symbol_access_modifier_t::_protected; + default: throw std::logic_error(std::string{s+" is not a valid access modifier"}.c_str()); + } + } + + std::ostream& operator<<(std::ostream& out, const symbol_type_name_t& value){ + const char* s = "invalid"; +#define PROCESS_VAL(p) case(p): s = #p; break; + switch(value) { + PROCESS_VAL(symbol_type_name_t::_int); + PROCESS_VAL(symbol_type_name_t::_long); + PROCESS_VAL(symbol_type_name_t::_float); + PROCESS_VAL(symbol_type_name_t::_double); + PROCESS_VAL(symbol_type_name_t::_string); + PROCESS_VAL(symbol_type_name_t::_bool); + PROCESS_VAL(symbol_type_name_t::_clock); + PROCESS_VAL(symbol_type_name_t::_auto); + } +#undef PROCESS_VAL + return out << s; + } + + std::ostream& operator<<(std::ostream& out, const symbol_access_modifier_t& value){ + const char* s = "invalid"; +#define PROCESS_VAL(p) case(p): s = #p; break; + switch(value) { + PROCESS_VAL(symbol_access_modifier_t::_private); + PROCESS_VAL(symbol_access_modifier_t::_public); + PROCESS_VAL(symbol_access_modifier_t::_protected); + } +#undef PROCESS_VAL + + return out << s; + } } auto std::hash::operator()(const expr::symbol_value_t& v) const -> size_t { diff --git a/include/symbol_table.h b/src/symbol/symbol_table.h similarity index 90% rename from include/symbol_table.h rename to src/symbol/symbol_table.h index 78593b3..fcd5df0 100644 --- a/include/symbol_table.h +++ b/src/symbol/symbol_table.h @@ -35,11 +35,27 @@ #include "clock.h" namespace expr { + enum class symbol_access_modifier_t { + _private = 0, + _protected = 1, + _public = 2 + }; + + enum class symbol_type_name_t { + _int, _long, _float, _double, _string, _bool, _clock, _auto + }; + + auto stotypename(const std::string& s) -> symbol_type_name_t; + auto stoaccmod(const std::string& s) -> symbol_access_modifier_t; + + std::ostream& operator<<(std::ostream& out, const symbol_type_name_t& value); + std::ostream& operator<<(std::ostream& out, const symbol_access_modifier_t& value); + using underlying_symbol_value_t = std::variant; struct symbol_value_t : public underlying_symbol_value_t { symbol_value_t() = default; - template + template symbol_value_t(const T &x) : underlying_symbol_value_t{x} {} template @@ -89,7 +105,6 @@ namespace expr { std::optional delay_amount{}; }; - // TODO: operator+/* should be slightly different here using symbol_table_tree_t = ya::tree; auto operator+(const symbol_table_t &a, const symbol_table_t &b) -> symbol_table_t; @@ -137,6 +152,7 @@ namespace expr { auto operator<<(std::ostream &o, const identifier_t &r) -> std::ostream &; auto operator<<(std::ostream &o, const underlying_syntax_node_t &n) -> std::ostream &; auto operator<<(std::ostream &o, const syntax_tree_t &t) -> std::ostream &; + auto stob(const char *s) -> bool; } namespace std { @@ -148,17 +164,5 @@ namespace std { }; } -#ifndef BINOP_CTOR -#define BINOP_CTOR(op,arg1,arg2) expr::syntax_tree_t{expr::operator_t{expr::operator_type_t::op}}.concat(arg1).concat(arg2) -#endif -#ifndef IDENT_CTOR -#define IDENT_CTOR(arg1) drv->get_symbol(arg1); -#endif -#ifndef MONOOP_CTOR -#define MONOOP_CTOR(op,arg1) expr::syntax_tree_t{expr::operator_t{expr::operator_type_t::op}}.concat(arg1) -#endif -#ifndef LIT_CTOR -#define LIT_CTOR(arg1) expr::syntax_tree_t{arg1} #endif -#endif