diff --git a/CMakeLists.txt b/CMakeLists.txt index f27a5e2..1f32e5f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,8 @@ # 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 1.7.0) include(cmake/CPM.cmake) configure_file(src/config.h.in config.h) diff --git a/include/drivers/compiler.h b/include/drivers/compiler.h index c460776..0f56b17 100644 --- a/include/drivers/compiler.h +++ b/include/drivers/compiler.h @@ -35,16 +35,13 @@ namespace expr { #else using compiled_expr_collection_t = std::unordered_map; #endif - explicit compiler(const symbol_table_t &env) : driver{}, trees{}, environment{env} {} + 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; - auto get_environment() const -> const symbol_table_t& { return environment; } compiled_expr_collection_t trees; - protected: - const symbol_table_t& environment{}; }; } diff --git a/include/drivers/interpreter.h b/include/drivers/interpreter.h index 2d8eeef..ad5472d 100644 --- a/include/drivers/interpreter.h +++ b/include/drivers/interpreter.h @@ -28,7 +28,7 @@ namespace expr { struct interpreter : public driver, arithmetic_operator, boolean_operator, compare_operator { - explicit interpreter(const symbol_table_t &env); + interpreter(std::initializer_list environments); ~interpreter() override = default; auto parse(const std::string &f) -> int override; @@ -63,11 +63,6 @@ namespace expr { auto evaluate(const syntax_tree_t& tree) -> symbol_value_t; auto evaluate(const compiler::compiled_expr_collection_t& tree) -> symbol_table_t; - static auto evaluate(const syntax_tree_t& tree, const interpreter& op, const symbol_table_t& symbols) -> symbol_value_t; - static auto evaluate(const compiler::compiled_expr_collection_t& symbol_tree_map, const interpreter& op, const symbol_table_t& symbols) -> symbol_table_t; - - protected: - const symbol_table_t &environment{}; }; } diff --git a/include/symbol_table.h b/include/symbol_table.h index 8e2cb26..1806c49 100644 --- a/include/symbol_table.h +++ b/include/symbol_table.h @@ -95,21 +95,18 @@ namespace expr { explicit operator_t(operator_type_t t) : operator_type{t} {} }; - using symbol_reference_t = symbol_table_t::iterator; - using c_symbol_reference_t = symbol_table_t::const_iterator; + struct identifier_t { + std::string ident; + }; struct root_t {}; - using underlying_syntax_node_t = std::variant; + using underlying_syntax_node_t = std::variant; struct syntax_node_t : public underlying_syntax_node_t { syntax_node_t() : underlying_syntax_node_t{root_t{}} {} template syntax_node_t(const T &t) : underlying_syntax_node_t{t} {} - syntax_node_t(symbol_reference_t r) : underlying_syntax_node_t{r} {} - - syntax_node_t(c_symbol_reference_t r) : underlying_syntax_node_t{r} {} - template auto &operator=(const T &t) { this->underlying_syntax_node_t::operator=(t); @@ -121,8 +118,7 @@ namespace expr { auto operator<<(std::ostream &o, const operator_t &p) -> std::ostream &; auto operator<<(std::ostream &o, const root_t &r) -> std::ostream &; - auto operator<<(std::ostream &o, const symbol_reference_t &r) -> std::ostream &; - auto operator<<(std::ostream &o, const c_symbol_reference_t &r) -> std::ostream &; + 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 &; } diff --git a/src/drivers/compiler.cpp b/src/drivers/compiler.cpp index 7fde7bb..af5f2df 100644 --- a/src/drivers/compiler.cpp +++ b/src/drivers/compiler.cpp @@ -47,11 +47,9 @@ namespace expr { } } auto compiler::get_symbol(const std::string& identifier) -> syntax_tree_t { -#ifndef NDEBUG - if (!environment.contains(identifier)) + if (!contains(identifier)) throw std::out_of_range(identifier + " not found"); -#endif - return syntax_tree_t{environment.find(identifier)}; + return syntax_tree_t{identifier_t{identifier}}; } void compiler::add_tree(const syntax_tree_t& tree) { trees["expression_result"] = (tree); diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 13cb86c..e625b41 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -29,14 +29,28 @@ YY_DECL; namespace expr { + using symbol_table_ref_t = std::reference_wrapper; + using symbol_table_ref_collection_t = std::vector>; struct driver { - explicit driver() : trace_parsing(false), trace_scanning(false) {} + driver(std::initializer_list environments) + : trace_parsing(false), trace_scanning(false), environments{environments} {} 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; + auto contains(const std::string& identifier) const -> bool { + return find(identifier) != end; + } + auto 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; + } void scan_begin(); void scan_end(); @@ -46,6 +60,9 @@ namespace expr { bool trace_parsing; bool trace_scanning; yy::location location; + protected: + expr::symbol_table_t::const_iterator end{}; + symbol_table_ref_collection_t environments; }; } diff --git a/src/drivers/interpreter.cpp b/src/drivers/interpreter.cpp index c190e54..39bbc63 100644 --- a/src/drivers/interpreter.cpp +++ b/src/drivers/interpreter.cpp @@ -25,8 +25,7 @@ #include "parser.hpp" namespace expr { - interpreter::interpreter(const symbol_table_t& map) : environment{map}, driver{} { - } + interpreter::interpreter(std::initializer_list environments) : driver{environments} {} int interpreter::parse(const std::string &f) { if (f.empty()) { @@ -67,11 +66,9 @@ namespace expr { } auto interpreter::get_symbol(const std::string &identifier) -> syntax_tree_t { -#ifndef NDEBUG - if (!environment.contains(identifier)) + if (!contains(identifier)) throw std::out_of_range(identifier + " not found"); -#endif - return syntax_tree_t{environment.find(identifier)}; + return syntax_tree_t{identifier_t{identifier}}; } void interpreter::add_tree(const syntax_tree_t& tree) { @@ -83,54 +80,50 @@ namespace expr { } auto interpreter::evaluate(const syntax_tree_t& tree) -> symbol_value_t { - return evaluate(tree, *this, environment); - } - - auto interpreter::evaluate(const compiler::compiled_expr_collection_t& trees) -> symbol_table_t { - return evaluate(trees, *this, environment); - } - - auto interpreter::evaluate(const syntax_tree_t& tree, const interpreter& op, const symbol_table_t& symbols) -> symbol_value_t { symbol_value_t v{}; std::visit(ya::overload( - [&](const symbol_reference_t& r){ v = symbols.at(r->first); }, // TODO: Should we look up every time? If so, what is the point of storing an iterator in the ast? - [&](const c_symbol_reference_t& r){ v = symbols.at(r->first); }, // TODO: Should we look up every time? If so, what is the point of storing an iterator in the ast? + [&](const identifier_t& r){ + auto s = find(r.ident); + if(s == end) + throw std::out_of_range("not found: " + r.ident); + v = s->second; + }, [&](const operator_t& o) { switch (o.operator_type) { - case operator_type_t::minus: v = op.sub(evaluate(tree.children[0], op, symbols), evaluate(tree.children[1], op, symbols)); break; - case operator_type_t::plus: v = op.add(evaluate(tree.children[0], op, symbols), evaluate(tree.children[1], op, symbols)); break; - case operator_type_t::star: v = op.mul(evaluate(tree.children[0], op, symbols), evaluate(tree.children[1], op, symbols)); break; - case operator_type_t::slash: v = op.div(evaluate(tree.children[0], op, symbols), evaluate(tree.children[1], op, symbols)); break; - case operator_type_t::percent: v = op.mod(evaluate(tree.children[0], op, symbols), evaluate(tree.children[1], op, symbols)); break; - case operator_type_t::hat: v = op.pow(evaluate(tree.children[0], op, symbols), evaluate(tree.children[1], op, symbols)); break; - case operator_type_t::_and: v = op._and(evaluate(tree.children[0], op, symbols), evaluate(tree.children[1], op, symbols)); break; - case operator_type_t::_or: v = op._or(evaluate(tree.children[0], op, symbols), evaluate(tree.children[1], op, symbols)); break; - case operator_type_t::_xor: v = op._xor(evaluate(tree.children[0], op, symbols), evaluate(tree.children[1], op, symbols)); break; - case operator_type_t::_not: v = op._not(evaluate(tree.children[0], op, symbols)); break; - case operator_type_t::_implies: v = op._implies(evaluate(tree.children[0], op, symbols), evaluate(tree.children[1], op, symbols)); break; - case operator_type_t::gt: v = op.gt(evaluate(tree.children[0], op, symbols), evaluate(tree.children[1], op, symbols)); break; - case operator_type_t::ge: v = op.ge(evaluate(tree.children[0], op, symbols), evaluate(tree.children[1], op, symbols)); break; - case operator_type_t::ne: v = op.ne(evaluate(tree.children[0], op, symbols), evaluate(tree.children[1], op, symbols)); break; - case operator_type_t::ee: v = op.ee(evaluate(tree.children[0], op, symbols), evaluate(tree.children[1], op, symbols)); break; - case operator_type_t::le: v = op.le(evaluate(tree.children[0], op, symbols), evaluate(tree.children[1], op, symbols)); break; - case operator_type_t::lt: v = op.lt(evaluate(tree.children[0], op, symbols), evaluate(tree.children[1], op, symbols)); break; - case operator_type_t::parentheses: v = evaluate(tree.children[0], op, symbols); break; + case operator_type_t::minus: v = sub(evaluate(tree.children[0]), evaluate(tree.children[1])); break; + case operator_type_t::plus: v = add(evaluate(tree.children[0]), evaluate(tree.children[1])); break; + case operator_type_t::star: v = mul(evaluate(tree.children[0]), evaluate(tree.children[1])); break; + case operator_type_t::slash: v = div(evaluate(tree.children[0]), evaluate(tree.children[1])); break; + case operator_type_t::percent: v = mod(evaluate(tree.children[0]), evaluate(tree.children[1])); break; + case operator_type_t::hat: v = pow(evaluate(tree.children[0]), evaluate(tree.children[1])); break; + case operator_type_t::_and: v = _and(evaluate(tree.children[0]), evaluate(tree.children[1])); break; + case operator_type_t::_or: v = _or(evaluate(tree.children[0]), evaluate(tree.children[1])); break; + case operator_type_t::_xor: v = _xor(evaluate(tree.children[0]), evaluate(tree.children[1])); break; + case operator_type_t::_not: v = _not(evaluate(tree.children[0])); break; + case operator_type_t::_implies: v = _implies(evaluate(tree.children[0]), evaluate(tree.children[1])); break; + case operator_type_t::gt: v = gt(evaluate(tree.children[0]), evaluate(tree.children[1])); break; + case operator_type_t::ge: v = ge(evaluate(tree.children[0]), evaluate(tree.children[1])); break; + case operator_type_t::ne: v = ne(evaluate(tree.children[0]), evaluate(tree.children[1])); break; + case operator_type_t::ee: v = ee(evaluate(tree.children[0]), evaluate(tree.children[1])); break; + case operator_type_t::le: v = le(evaluate(tree.children[0]), evaluate(tree.children[1])); break; + case operator_type_t::lt: v = lt(evaluate(tree.children[0]), evaluate(tree.children[1])); break; + case operator_type_t::parentheses: v = evaluate(tree.children[0]); break; } }, [&v](const symbol_value_t& o){ v = o; }, [&](const root_t& r){ if(!tree.children.empty()) - v = evaluate(tree.children[0], op, symbols); + v = evaluate(tree.children[0]); }, [](auto&&){ throw std::logic_error("operator type not recognized"); } ), static_cast(tree.node)); return v; } - auto interpreter::evaluate(const compiler::compiled_expr_collection_t& symbol_tree_map, const interpreter& op, const symbol_table_t& symbols) -> symbol_table_t { - symbol_table_t result{}; - for(auto& tree : symbol_tree_map) - result[tree.first] = evaluate(tree.second, op, symbols); - return result; + 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/symbol_table.cpp b/src/symbol_table.cpp index 6c2a647..ba1036e 100644 --- a/src/symbol_table.cpp +++ b/src/symbol_table.cpp @@ -127,14 +127,12 @@ namespace expr { } } - auto operator<<(std::ostream &o, const root_t &r) -> std::ostream & { return o << "ROOT"; } - - auto operator<<(std::ostream &o, const symbol_reference_t &r) -> std::ostream & { - return o << r->first << " :-> " << r->second; + auto operator<<(std::ostream &o, const root_t &r) -> std::ostream & { + return o << "ROOT"; } - auto operator<<(std::ostream &o, const c_symbol_reference_t &r) -> std::ostream & { - return o << r->first << " :-> " << r->second; + auto operator<<(std::ostream& o, const identifier_t& r) -> std::ostream& { + return o << r.ident; } auto operator<<(std::ostream &o, const underlying_syntax_node_t &n) -> std::ostream & {