// This file is distributed under the BSD License. // See "license.txt" for details. // Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com) // Copyright 2009-2018, Jason Turner (jason@emptycrate.com) // http://www.chaiscript.com // This is an open source non-commercial project. Dear PVS-Studio, please check it. // PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #ifndef CHAISCRIPT_EVAL_HPP_ #define CHAISCRIPT_EVAL_HPP_ #include #include #include #include #include #include #include #include #include #include #include "../chaiscript_defines.hpp" #include "../dispatchkit/boxed_cast.hpp" #include "../dispatchkit/boxed_number.hpp" #include "../dispatchkit/boxed_value.hpp" #include "../dispatchkit/dispatchkit.hpp" #include "../dispatchkit/dynamic_object_detail.hpp" #include "../dispatchkit/proxy_functions.hpp" #include "../dispatchkit/proxy_functions_detail.hpp" #include "../dispatchkit/register_function.hpp" #include "../dispatchkit/type_info.hpp" #include "chaiscript_algebraic.hpp" #include "chaiscript_common.hpp" namespace chaiscript::exception { class bad_boxed_cast; } // namespace chaiscript::exception namespace chaiscript { /// \brief Classes and functions that are part of the runtime eval system namespace eval { template struct AST_Node_Impl; template using AST_Node_Impl_Ptr = typename std::unique_ptr>; namespace detail { /// Helper function that will set up the scope around a function call, including handling the named function parameters template Boxed_Value eval_function(chaiscript::detail::Dispatch_Engine &t_ss, const AST_Node_Impl &t_node, const std::vector &t_param_names, const Function_Params &t_vals, const std::map *t_locals = nullptr, bool has_this_capture = false) { chaiscript::detail::Dispatch_State state(t_ss); const Boxed_Value *thisobj = [&]() -> const Boxed_Value * { if (auto &stack = t_ss.get_stack_data(state.stack_holder()).back(); !stack.empty() && stack.back().first == "__this") { return &stack.back().second; } else if (!t_vals.empty()) { return &t_vals[0]; } else { return nullptr; } }(); chaiscript::eval::detail::Stack_Push_Pop tpp(state); if (thisobj && !has_this_capture) { state.add_object("this", *thisobj); } if (t_locals) { for (const auto &[name, value] : *t_locals) { state.add_object(name, value); } } for (size_t i = 0; i < t_param_names.size(); ++i) { if (t_param_names[i] != "this") { state.add_object(t_param_names[i], t_vals[i]); } } try { return t_node.eval(state); } catch (detail::Return_Value &rv) { return std::move(rv.retval); } } inline Boxed_Value clone_if_necessary(Boxed_Value incoming, std::atomic_uint_fast32_t &t_loc, const chaiscript::detail::Dispatch_State &t_ss) { if (!incoming.is_return_value()) { if (incoming.get_type_info().is_arithmetic()) { return Boxed_Number::clone(incoming); } else if (incoming.get_type_info().bare_equal_type_info(typeid(bool))) { return Boxed_Value(*static_cast(incoming.get_const_ptr())); } else if (incoming.get_type_info().bare_equal_type_info(typeid(std::string))) { return Boxed_Value(*static_cast(incoming.get_const_ptr())); } else { std::array params{std::move(incoming)}; return t_ss->call_function("clone", t_loc, Function_Params{params}, t_ss.conversions()); } } else { incoming.reset_return_value(); return incoming; } } class Strong_Typedef_Binary_Op final : public dispatch::Proxy_Function_Base { public: Strong_Typedef_Binary_Op( std::string t_type_name, std::string t_op_name, Operators::Opers t_oper, bool t_rewrap, chaiscript::detail::Dispatch_Engine &t_engine) : Proxy_Function_Base( {chaiscript::detail::Get_Type_Info::get(), user_type(), user_type()}, 2) , m_type_name(std::move(t_type_name)) , m_op_name(std::move(t_op_name)) , m_oper(t_oper) , m_rewrap(t_rewrap) , m_engine(t_engine) { } bool operator==(const Proxy_Function_Base &f) const noexcept override { if (const auto *other = dynamic_cast(&f)) { return m_type_name == other->m_type_name && m_op_name == other->m_op_name; } return false; } bool call_match(const Function_Params &vals, const Type_Conversions_State &t_conversions) const noexcept override { return vals.size() == 2 && type_matches(vals[0], t_conversions) && type_matches(vals[1], t_conversions); } protected: Boxed_Value do_call(const Function_Params ¶ms, const Type_Conversions_State &t_conversions) const override { if (!call_match(params, t_conversions)) { throw chaiscript::exception::guard_error(); } const auto &lhs = boxed_cast(params[0], &t_conversions); const auto &rhs = boxed_cast(params[1], &t_conversions); const auto lhs_val = lhs.get_attr("__value"); const auto rhs_val = rhs.get_attr("__value"); Boxed_Value result; if (m_oper != Operators::Opers::invalid && lhs_val.get_type_info().is_arithmetic() && rhs_val.get_type_info().is_arithmetic()) { result = Boxed_Number::do_oper(m_oper, lhs_val, rhs_val); } else { std::array underlying_params{lhs_val, rhs_val}; result = m_engine.call_function(m_op_name, m_loc, Function_Params(underlying_params), t_conversions); } if (m_rewrap) { auto bv = Boxed_Value(dispatch::Dynamic_Object(m_type_name), true); auto *obj = static_cast(bv.get_ptr()); obj->get_attr("__value") = result; return bv; } return result; } private: bool type_matches(const Boxed_Value &bv, const Type_Conversions_State &t_conversions) const noexcept { if (!bv.get_type_info().bare_equal(user_type())) { return false; } try { const auto &d = boxed_cast(bv, &t_conversions); return d.get_type_name() == m_type_name; } catch (...) { return false; } } std::string m_type_name; std::string m_op_name; Operators::Opers m_oper; bool m_rewrap; chaiscript::detail::Dispatch_Engine &m_engine; mutable std::atomic_uint_fast32_t m_loc{0}; }; class Strong_Typedef_Compound_Assign_Op final : public dispatch::Proxy_Function_Base { public: Strong_Typedef_Compound_Assign_Op( std::string t_type_name, std::string t_op_name, Operators::Opers t_base_oper, std::string t_base_op_name, chaiscript::detail::Dispatch_Engine &t_engine) : Proxy_Function_Base( {user_type(), user_type(), user_type()}, 2) , m_type_name(std::move(t_type_name)) , m_op_name(std::move(t_op_name)) , m_base_oper(t_base_oper) , m_base_op_name(std::move(t_base_op_name)) , m_engine(t_engine) { } bool operator==(const Proxy_Function_Base &f) const noexcept override { if (const auto *other = dynamic_cast(&f)) { return m_type_name == other->m_type_name && m_op_name == other->m_op_name; } return false; } bool call_match(const Function_Params &vals, const Type_Conversions_State &t_conversions) const noexcept override { return vals.size() == 2 && type_matches(vals[0], t_conversions) && type_matches(vals[1], t_conversions); } protected: Boxed_Value do_call(const Function_Params ¶ms, const Type_Conversions_State &t_conversions) const override { if (!call_match(params, t_conversions)) { throw chaiscript::exception::guard_error(); } auto &lhs = boxed_cast(params[0], &t_conversions); const auto &rhs = boxed_cast(params[1], &t_conversions); const auto lhs_val = lhs.get_attr("__value"); const auto rhs_val = rhs.get_attr("__value"); Boxed_Value result; if (m_base_oper != Operators::Opers::invalid && lhs_val.get_type_info().is_arithmetic() && rhs_val.get_type_info().is_arithmetic()) { result = Boxed_Number::do_oper(m_base_oper, lhs_val, rhs_val); } else { std::array underlying_params{lhs_val, rhs_val}; result = m_engine.call_function(m_base_op_name, m_loc, Function_Params(underlying_params), t_conversions); } lhs.get_attr("__value") = result; return params[0]; } private: bool type_matches(const Boxed_Value &bv, const Type_Conversions_State &t_conversions) const noexcept { if (!bv.get_type_info().bare_equal(user_type())) { return false; } try { const auto &d = boxed_cast(bv, &t_conversions); return d.get_type_name() == m_type_name; } catch (...) { return false; } } std::string m_type_name; std::string m_op_name; Operators::Opers m_base_oper; std::string m_base_op_name; chaiscript::detail::Dispatch_Engine &m_engine; mutable std::atomic_uint_fast32_t m_loc{0}; }; } // namespace detail template struct AST_Node_Impl : AST_Node { AST_Node_Impl(std::string t_ast_node_text, AST_Node_Type t_id, Parse_Location t_loc, std::vector> t_children = std::vector>()) : AST_Node(std::move(t_ast_node_text), t_id, std::move(t_loc)) , children(std::move(t_children)) { } static bool get_scoped_bool_condition(const AST_Node_Impl &node, const chaiscript::detail::Dispatch_State &t_ss) { chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); return get_bool_condition(node.eval(t_ss), t_ss); } std::vector> get_children() const final { std::vector> retval; retval.reserve(children.size()); for (auto &child : children) { retval.emplace_back(*child); } return retval; } Boxed_Value eval(const chaiscript::detail::Dispatch_State &t_e) const final { try { T::trace(t_e, this); return eval_internal(t_e); } catch (exception::eval_error &ee) { ee.call_stack.push_back(*this); throw; } } std::vector> children; protected: virtual Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &) const { throw std::runtime_error("Undispatched ast_node (internal error)"); } }; template struct Compiled_AST_Node : AST_Node_Impl { Compiled_AST_Node(AST_Node_Impl_Ptr t_original_node, std::vector> t_children, std::function> &, const chaiscript::detail::Dispatch_State &t_ss)> t_func) : AST_Node_Impl(t_original_node->text, AST_Node_Type::Compiled, t_original_node->location, std::move(t_children)) , m_func(std::move(t_func)) , m_original_node(std::move(t_original_node)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { return m_func(this->children, t_ss); } std::function> &, const chaiscript::detail::Dispatch_State &t_ss)> m_func; AST_Node_Impl_Ptr m_original_node; }; template struct Fold_Right_Binary_Operator_AST_Node : AST_Node_Impl { Fold_Right_Binary_Operator_AST_Node(const std::string &t_oper, Parse_Location t_loc, std::vector> t_children, Boxed_Value t_rhs) : AST_Node_Impl(t_oper, AST_Node_Type::Binary, std::move(t_loc), std::move(t_children)) , m_oper(Operators::to_operator(t_oper)) , m_rhs(std::move(t_rhs)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { return do_oper(t_ss, this->text, this->children[0]->eval(t_ss)); } protected: Boxed_Value do_oper(const chaiscript::detail::Dispatch_State &t_ss, const std::string &t_oper_string, const Boxed_Value &t_lhs) const { try { if (t_lhs.get_type_info().is_arithmetic()) { // If it's an arithmetic operation we want to short circuit dispatch try { return Boxed_Number::do_oper(m_oper, t_lhs, m_rhs); } catch (const chaiscript::exception::arithmetic_error &) { throw; } catch (...) { throw exception::eval_error("Error with numeric operator calling: " + t_oper_string); } } else { chaiscript::eval::detail::Function_Push_Pop fpp(t_ss); std::array params{t_lhs, m_rhs}; fpp.save_params(Function_Params{params}); return t_ss->call_function(t_oper_string, m_loc, Function_Params{params}, t_ss.conversions()); } } catch (const exception::dispatch_error &e) { throw exception::eval_error("Can not find appropriate '" + t_oper_string + "' operator.", e.parameters, e.functions, false, *t_ss); } } private: Operators::Opers m_oper; Boxed_Value m_rhs; mutable std::atomic_uint_fast32_t m_loc = {0}; }; template struct Binary_Operator_AST_Node : AST_Node_Impl { Binary_Operator_AST_Node(const std::string &t_oper, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(t_oper, AST_Node_Type::Binary, std::move(t_loc), std::move(t_children)) , m_oper(Operators::to_operator(t_oper)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { auto lhs = this->children[0]->eval(t_ss); auto rhs = this->children[1]->eval(t_ss); return do_oper(t_ss, m_oper, this->text, lhs, rhs, m_loc); } // static and public so we can use this to process Switch_AST_Node case equality static Boxed_Value do_oper(const chaiscript::detail::Dispatch_State &t_ss, Operators::Opers t_oper, const std::string &t_oper_string, const Boxed_Value &t_lhs, const Boxed_Value &t_rhs, std::atomic_uint_fast32_t &t_loc) { try { if (t_oper != Operators::Opers::invalid && t_lhs.get_type_info().is_arithmetic() && t_rhs.get_type_info().is_arithmetic()) { // If it's an arithmetic operation we want to short circuit dispatch try { return Boxed_Number::do_oper(t_oper, t_lhs, t_rhs); } catch (const chaiscript::exception::arithmetic_error &) { throw; } catch (...) { throw exception::eval_error("Error with numeric operator calling: " + t_oper_string); } } else { chaiscript::eval::detail::Function_Push_Pop fpp(t_ss); std::array params{t_lhs, t_rhs}; fpp.save_params(Function_Params(params)); return t_ss->call_function(t_oper_string, t_loc, Function_Params(params), t_ss.conversions()); } } catch (const exception::dispatch_error &e) { throw exception::eval_error("Can not find appropriate '" + t_oper_string + "' operator.", e.parameters, e.functions, false, *t_ss); } } private: Operators::Opers m_oper; mutable std::atomic_uint_fast32_t m_loc = {0}; }; template struct Constant_AST_Node final : AST_Node_Impl { Constant_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, Boxed_Value t_value) : AST_Node_Impl(t_ast_node_text, AST_Node_Type::Constant, std::move(t_loc)) , m_value(std::move(t_value)) { } explicit Constant_AST_Node(Boxed_Value t_value) : AST_Node_Impl("", AST_Node_Type::Constant, Parse_Location()) , m_value(std::move(t_value)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &) const override { return m_value; } Boxed_Value m_value; }; template struct Id_AST_Node final : AST_Node_Impl { Id_AST_Node(const std::string &t_ast_node_text, Parse_Location t_loc) : AST_Node_Impl(t_ast_node_text, AST_Node_Type::Id, std::move(t_loc)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { try { return t_ss.get_object(this->text, m_loc); } catch (std::exception &) { throw exception::eval_error("Can not find object: " + this->text); } } private: mutable std::atomic_uint_fast32_t m_loc = {0}; }; template struct Fun_Call_AST_Node : AST_Node_Impl { Fun_Call_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Fun_Call, std::move(t_loc), std::move(t_children)) { assert(!this->children.empty()); } template Boxed_Value do_eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const { chaiscript::eval::detail::Function_Push_Pop fpp(t_ss); std::vector params; params.reserve(this->children[1]->children.size()); for (const auto &child : this->children[1]->children) { params.push_back(child->eval(t_ss)); } if (Save_Params) { fpp.save_params(Function_Params{params}); } Boxed_Value fn(this->children[0]->eval(t_ss)); try { return (*t_ss->boxed_cast(fn))(Function_Params{params}, t_ss.conversions()); } catch (const exception::dispatch_error &e) { throw exception::eval_error(std::string(e.what()) + " with function '" + this->children[0]->text + "'", e.parameters, e.functions, false, *t_ss); } catch (const exception::bad_boxed_cast &) { try { using ConstFunctionTypeRef = const Const_Proxy_Function &; Const_Proxy_Function f = t_ss->boxed_cast(fn); // handle the case where there is only 1 function to try to call and dispatch fails on it throw exception::eval_error("Error calling function '" + this->children[0]->text + "'", params, make_vector(f), false, *t_ss); } catch (const exception::bad_boxed_cast &) { throw exception::eval_error("'" + this->children[0]->pretty_print() + "' does not evaluate to a function."); } } catch (const exception::arity_error &e) { throw exception::eval_error(std::string(e.what()) + " with function '" + this->children[0]->text + "'"); } catch (const exception::guard_error &e) { throw exception::eval_error(std::string(e.what()) + " with function '" + this->children[0]->text + "'"); } catch (detail::Return_Value &rv) { return rv.retval; } } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { return do_eval_internal(t_ss); } }; template struct Unused_Return_Fun_Call_AST_Node final : Fun_Call_AST_Node { Unused_Return_Fun_Call_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : Fun_Call_AST_Node(std::move(t_ast_node_text), std::move(t_loc), std::move(t_children)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { return this->template do_eval_internal(t_ss); } }; template struct Arg_AST_Node final : AST_Node_Impl { Arg_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Arg_List, std::move(t_loc), std::move(t_children)) { } }; template struct Arg_List_AST_Node final : AST_Node_Impl { Arg_List_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Arg_List, std::move(t_loc), std::move(t_children)) { } static std::string get_arg_name(const AST_Node_Impl &t_node) { if (t_node.children.empty()) { return t_node.text; } else if (t_node.children.size() == 1) { return t_node.children[0]->text; } else { return t_node.children[1]->text; } } static std::vector get_arg_names(const AST_Node_Impl &t_node) { std::vector retval; for (const auto &node : t_node.children) { retval.push_back(get_arg_name(*node)); } return retval; } static std::pair get_arg_type(const AST_Node_Impl &t_node, const chaiscript::detail::Dispatch_State &t_ss) { if (t_node.children.size() < 2) { return {}; } else { return {t_node.children[0]->text, t_ss->get_type(t_node.children[0]->text, false)}; } } static dispatch::Param_Types get_arg_types(const AST_Node_Impl &t_node, const chaiscript::detail::Dispatch_State &t_ss) { std::vector> retval; for (const auto &child : t_node.children) { retval.push_back(get_arg_type(*child, t_ss)); } return dispatch::Param_Types(std::move(retval)); } }; template struct Equation_AST_Node final : AST_Node_Impl { Equation_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Equation, std::move(t_loc), std::move(t_children)) , m_oper(Operators::to_operator(this->text)) { assert(this->children.size() == 2); } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { chaiscript::eval::detail::Function_Push_Pop fpp(t_ss); auto params = [&]() { // The RHS *must* be evaluated before the LHS // consider `var range = range(x)` // if we declare the variable in scope first, then the name lookup fails // for the RHS auto rhs = this->children[1]->eval(t_ss); auto lhs = this->children[0]->eval(t_ss); std::array p{std::move(lhs), std::move(rhs)}; return p; }(); if (params[0].is_return_value()) { throw exception::eval_error("Error, cannot assign to temporary value."); } else if (params[0].is_const()) { throw exception::eval_error("Error, cannot assign to constant value."); } if (m_oper != Operators::Opers::invalid && params[0].get_type_info().is_arithmetic() && params[1].get_type_info().is_arithmetic()) { try { return Boxed_Number::do_oper(m_oper, params[0], params[1]); } catch (const chaiscript::exception::arithmetic_error &) { throw; } catch (const std::exception &) { throw exception::eval_error("Error with unsupported arithmetic assignment operation."); } } else if (m_oper == Operators::Opers::assign) { try { if (params[0].is_undef()) { if (!this->children.empty() && ((this->children[0]->identifier == AST_Node_Type::Reference) || (!this->children[0]->children.empty() && this->children[0]->children[0]->identifier == AST_Node_Type::Reference))) { /// \todo This does not handle the case of an unassigned reference variable /// being assigned outside of its declaration params[0].assign(params[1]); params[0].reset_return_value(); return params[1]; } else { params[1] = detail::clone_if_necessary(std::move(params[1]), m_clone_loc, t_ss); } } try { return t_ss->call_function(this->text, m_loc, Function_Params{params}, t_ss.conversions()); } catch (const exception::dispatch_error &e) { throw exception::eval_error("Unable to find appropriate'" + this->text + "' operator.", e.parameters, e.functions, false, *t_ss); } } catch (const exception::dispatch_error &e) { throw exception::eval_error("Missing clone or copy constructor for right hand side of equation", e.parameters, e.functions, false, *t_ss); } } else if (this->text == ":=") { if (params[0].is_undef() || Boxed_Value::type_match(params[0], params[1])) { params[0].assign(params[1]); params[0].reset_return_value(); } else { throw exception::eval_error("Mismatched types in equation"); } } else { try { return t_ss->call_function(this->text, m_loc, Function_Params{params}, t_ss.conversions()); } catch (const exception::dispatch_error &e) { throw exception::eval_error("Unable to find appropriate'" + this->text + "' operator.", e.parameters, e.functions, false, *t_ss); } } return params[1]; } private: Operators::Opers m_oper; mutable std::atomic_uint_fast32_t m_loc = {0}; mutable std::atomic_uint_fast32_t m_clone_loc = {0}; }; template struct Global_Decl_AST_Node final : AST_Node_Impl { Global_Decl_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Global_Decl, std::move(t_loc), std::move(t_children)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { const std::string &idname = (this->children[0]->identifier == AST_Node_Type::Reference) ? this->children[0]->children[0]->text : this->children[0]->text; return t_ss->add_global_no_throw(Boxed_Value(), idname); } }; template struct Var_Decl_AST_Node final : AST_Node_Impl { Var_Decl_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Var_Decl, std::move(t_loc), std::move(t_children)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { const std::string &idname = this->children[0]->text; try { Boxed_Value bv; t_ss.add_object(idname, bv); return bv; } catch (const exception::name_conflict_error &e) { throw exception::eval_error("Variable redefined '" + e.name() + "'"); } } }; template struct Assign_Decl_AST_Node final : AST_Node_Impl { Assign_Decl_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Assign_Decl, std::move(t_loc), std::move(t_children)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { const std::string &idname = this->children[0]->text; try { Boxed_Value bv(detail::clone_if_necessary(this->children[1]->eval(t_ss), m_loc, t_ss)); bv.reset_return_value(); t_ss.add_object(idname, bv); return bv; } catch (const exception::name_conflict_error &e) { throw exception::eval_error("Variable redefined '" + e.name() + "'"); } } private: mutable std::atomic_uint_fast32_t m_loc = {0}; }; template struct Const_Var_Decl_AST_Node final : AST_Node_Impl { Const_Var_Decl_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Const_Var_Decl, std::move(t_loc), std::move(t_children)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &) const override { throw exception::eval_error("const variables must be initialized at declaration"); } }; template struct Const_Assign_Decl_AST_Node final : AST_Node_Impl { Const_Assign_Decl_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Const_Assign_Decl, std::move(t_loc), std::move(t_children)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { const std::string &idname = this->children[0]->text; try { Boxed_Value bv(detail::clone_if_necessary(this->children[1]->eval(t_ss), m_loc, t_ss)); bv.reset_return_value(); bv.make_const(); t_ss.add_object(idname, bv); return bv; } catch (const exception::name_conflict_error &e) { throw exception::eval_error("Variable redefined '" + e.name() + "'"); } } private: mutable std::atomic_uint_fast32_t m_loc = {0}; }; template struct Array_Call_AST_Node final : AST_Node_Impl { Array_Call_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Array_Call, std::move(t_loc), std::move(t_children)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { chaiscript::eval::detail::Function_Push_Pop fpp(t_ss); std::array params{this->children[0]->eval(t_ss), this->children[1]->eval(t_ss)}; try { fpp.save_params(Function_Params{params}); return t_ss->call_function("[]", m_loc, Function_Params{params}, t_ss.conversions()); } catch (const exception::dispatch_error &e) { throw exception::eval_error("Can not find appropriate array lookup operator '[]'.", e.parameters, e.functions, false, *t_ss); } } private: mutable std::atomic_uint_fast32_t m_loc = {0}; }; template struct Dot_Access_AST_Node final : AST_Node_Impl { Dot_Access_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Dot_Access, std::move(t_loc), std::move(t_children)) , m_fun_name(((this->children[1]->identifier == AST_Node_Type::Fun_Call) || (this->children[1]->identifier == AST_Node_Type::Array_Call)) ? this->children[1]->children[0]->text : this->children[1]->text) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { chaiscript::eval::detail::Function_Push_Pop fpp(t_ss); Boxed_Value retval = this->children[0]->eval(t_ss); auto params = make_vector(retval); bool has_function_params = false; if (this->children[1]->children.size() > 1) { has_function_params = true; for (const auto &child : this->children[1]->children[1]->children) { params.push_back(child->eval(t_ss)); } } fpp.save_params(Function_Params{params}); try { retval = t_ss->call_member(m_fun_name, m_loc, Function_Params{params}, has_function_params, t_ss.conversions()); } catch (const exception::dispatch_error &e) { if (e.functions.empty()) { throw exception::eval_error("'" + m_fun_name + "' is not a function."); } else { throw exception::eval_error(std::string(e.what()) + " for function '" + m_fun_name + "'", e.parameters, e.functions, true, *t_ss); } } catch (detail::Return_Value &rv) { retval = std::move(rv.retval); } if (this->children[1]->identifier == AST_Node_Type::Array_Call) { try { std::array p{retval, this->children[1]->children[1]->eval(t_ss)}; retval = t_ss->call_function("[]", m_array_loc, Function_Params{p}, t_ss.conversions()); } catch (const exception::dispatch_error &e) { throw exception::eval_error("Can not find appropriate array lookup operator '[]'.", e.parameters, e.functions, true, *t_ss); } } return retval; } private: mutable std::atomic_uint_fast32_t m_loc = {0}; mutable std::atomic_uint_fast32_t m_array_loc = {0}; const std::string m_fun_name; }; template struct Lambda_AST_Node final : AST_Node_Impl { Lambda_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(t_ast_node_text, AST_Node_Type::Lambda, std::move(t_loc), std::vector>(std::make_move_iterator(t_children.begin()), std::make_move_iterator(std::prev(t_children.end())))) , m_param_names(Arg_List_AST_Node::get_arg_names(*this->children[1])) , m_this_capture(has_this_capture(this->children[0]->children)) , m_lambda_node(std::move(t_children.back())) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { const auto captures = [&]() -> std::map { std::map named_captures; for (const auto &capture : this->children[0]->children) { named_captures.insert(std::make_pair(capture->children[0]->text, capture->children[0]->eval(t_ss))); } return named_captures; }(); const auto numparams = this->children[1]->children.size(); const auto param_types = Arg_List_AST_Node::get_arg_types(*this->children[1], t_ss); std::reference_wrapper engine(*t_ss); return Boxed_Value(dispatch::make_dynamic_proxy_function( [engine, lambda_node = this->m_lambda_node, param_names = this->m_param_names, captures, this_capture = this->m_this_capture]( const Function_Params &t_params) { return detail::eval_function(engine, *lambda_node, param_names, t_params, &captures, this_capture); }, static_cast(numparams), m_lambda_node, param_types)); } static bool has_this_capture(const std::vector> &t_children) noexcept { return std::any_of(std::begin(t_children), std::end(t_children), [](const auto &child) { return child->children[0]->text == "this"; }); } private: const std::vector m_param_names; const bool m_this_capture = false; const std::shared_ptr> m_lambda_node; }; template struct Scopeless_Block_AST_Node final : AST_Node_Impl { Scopeless_Block_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Scopeless_Block, std::move(t_loc), std::move(t_children)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { const auto num_children = this->children.size(); for (size_t i = 0; i < num_children - 1; ++i) { this->children[i]->eval(t_ss); } return this->children.back()->eval(t_ss); } }; template struct Block_AST_Node final : AST_Node_Impl { Block_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Block, std::move(t_loc), std::move(t_children)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); const auto num_children = this->children.size(); for (size_t i = 0; i < num_children - 1; ++i) { this->children[i]->eval(t_ss); } return this->children.back()->eval(t_ss); } }; template struct Def_AST_Node final : AST_Node_Impl { std::shared_ptr> m_body_node; std::shared_ptr> m_guard_node; Def_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Def, std::move(t_loc), std::vector>(std::make_move_iterator(t_children.begin()), std::make_move_iterator( std::prev(t_children.end(), has_guard(t_children, 1) ? 2 : 1)))) , // This apparent use after move is safe because we are only moving out the specific elements we need // on each operation. m_body_node(get_body_node(std::move(t_children))) , m_guard_node(get_guard_node(std::move(t_children), t_children.size() - this->children.size() == 2)) { } static std::shared_ptr> get_guard_node(std::vector> &&vec, bool has_guard) { if (has_guard) { return std::move(*std::prev(vec.end(), 2)); } else { return {}; } } static std::shared_ptr> get_body_node(std::vector> &&vec) { return std::move(vec.back()); } static bool has_guard(const std::vector> &t_children, const std::size_t offset) noexcept { if ((t_children.size() > 2 + offset) && (t_children[1 + offset]->identifier == AST_Node_Type::Arg_List)) { if (t_children.size() > 3 + offset) { return true; } } else { if (t_children.size() > 2 + offset) { return true; } } return false; } static std::shared_ptr make_proxy_function( const Def_AST_Node &t_node, const chaiscript::detail::Dispatch_State &t_ss) { std::vector t_param_names; size_t numparams = 0; dispatch::Param_Types param_types; if ((t_node.children.size() > 1) && (t_node.children[1]->identifier == AST_Node_Type::Arg_List)) { numparams = t_node.children[1]->children.size(); t_param_names = Arg_List_AST_Node::get_arg_names(*t_node.children[1]); param_types = Arg_List_AST_Node::get_arg_types(*t_node.children[1], t_ss); } std::reference_wrapper engine(*t_ss); std::shared_ptr guard; if (t_node.m_guard_node) { guard = dispatch::make_dynamic_proxy_function( [engine, guardnode = t_node.m_guard_node, t_param_names](const Function_Params &t_params) { return detail::eval_function(engine, *guardnode, t_param_names, t_params); }, static_cast(numparams), t_node.m_guard_node); } return dispatch::make_dynamic_proxy_function( [engine, func_node = t_node.m_body_node, t_param_names](const Function_Params &t_params) { return detail::eval_function(engine, *func_node, t_param_names, t_params); }, static_cast(numparams), t_node.m_body_node, param_types, guard); } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { try { t_ss->add(make_proxy_function(*this, t_ss), this->children[0]->text); } catch (const exception::name_conflict_error &e) { throw exception::eval_error("Function redefined '" + e.name() + "'"); } return void_var(); } }; template struct While_AST_Node final : AST_Node_Impl { While_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::While, std::move(t_loc), std::move(t_children)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); try { while (this->get_scoped_bool_condition(*this->children[0], t_ss)) { try { this->children[1]->eval(t_ss); } catch (detail::Continue_Loop &) { // we got a continue exception, which means all of the remaining // loop implementation is skipped and we just need to continue to // the next condition test } } } catch (detail::Break_Loop &) { // loop was broken intentionally } return void_var(); } }; template struct Class_AST_Node final : AST_Node_Impl { Class_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Class, std::move(t_loc), std::move(t_children)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); const auto &class_name = this->children[0]->text; /// \todo do this better // put class name in current scope so it can be looked up by the attrs and methods t_ss.add_object("_current_class_name", const_var(class_name)); const bool has_base_class = (this->children.size() == 3); const auto &block = has_base_class ? this->children[2] : this->children[1]; // Register inheritance before evaluating the class body so that // function dispatch ordering can account for the relationship if (has_base_class) { const auto &base_name = this->children[1]->text; dispatch::Dynamic_Object::register_inheritance(class_name, base_name); } block->eval(t_ss); return void_var(); } }; template struct Using_AST_Node final : AST_Node_Impl { Using_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Using, std::move(t_loc), std::move(t_children)) { assert(this->children.size() == 2); } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { const auto &new_type_name = this->children[0]->text; const auto &base_type_name = this->children[1]->text; const auto base_type = t_ss->get_type(base_type_name, true); t_ss->add(user_type(), new_type_name); dispatch::Param_Types param_types(std::vector>{ {new_type_name, Type_Info()}, {base_type_name, base_type}}); auto ctor_body = dispatch::make_dynamic_proxy_function( [](const Function_Params &t_params) -> Boxed_Value { auto *obj = static_cast(t_params[0].get_ptr()); obj->get_attr("__value") = t_params[1]; return void_var(); }, 2, std::shared_ptr(), param_types); try { t_ss->add(std::make_shared(new_type_name, ctor_body), new_type_name); } catch (const exception::name_conflict_error &e) { throw exception::eval_error("Type alias redefined '" + e.name() + "'"); } dispatch::Param_Types to_underlying_param_types(std::vector>{ {new_type_name, user_type()}}); auto to_underlying_body = dispatch::make_dynamic_proxy_function( [](const Function_Params &t_params) -> Boxed_Value { const auto *obj = static_cast(t_params[0].get_const_ptr()); return obj->get_attr("__value"); }, 1, std::shared_ptr(), to_underlying_param_types); t_ss->add(to_underlying_body, "to_underlying"); auto &engine = *t_ss; struct Op_Entry { const char *name; Operators::Opers oper; bool rewrap; }; static constexpr Op_Entry ops[] = { {"+", Operators::Opers::sum, true}, {"-", Operators::Opers::difference, true}, {"*", Operators::Opers::product, true}, {"/", Operators::Opers::quotient, true}, {"%", Operators::Opers::remainder, true}, {"<<", Operators::Opers::shift_left, true}, {">>", Operators::Opers::shift_right, true}, {"&", Operators::Opers::bitwise_and, true}, {"|", Operators::Opers::bitwise_or, true}, {"^", Operators::Opers::bitwise_xor, true}, {"<", Operators::Opers::less_than, false}, {">", Operators::Opers::greater_than, false}, {"<=", Operators::Opers::less_than_equal, false}, {">=", Operators::Opers::greater_than_equal, false}, {"==", Operators::Opers::equals, false}, {"!=", Operators::Opers::not_equal, false}, }; for (const auto &op : ops) { t_ss->add( chaiscript::make_shared( new_type_name, std::string(op.name), op.oper, op.rewrap, engine), op.name); } struct Compound_Op_Entry { const char *name; Operators::Opers base_oper; const char *base_op_name; }; static constexpr Compound_Op_Entry compound_ops[] = { {"+=", Operators::Opers::sum, "+"}, {"-=", Operators::Opers::difference, "-"}, {"*=", Operators::Opers::product, "*"}, {"/=", Operators::Opers::quotient, "/"}, {"%=", Operators::Opers::remainder, "%"}, {"<<=", Operators::Opers::shift_left, "<<"}, {">>=", Operators::Opers::shift_right, ">>"}, {"&=", Operators::Opers::bitwise_and, "&"}, {"|=", Operators::Opers::bitwise_or, "|"}, {"^=", Operators::Opers::bitwise_xor, "^"}, }; for (const auto &op : compound_ops) { t_ss->add( chaiscript::make_shared( new_type_name, std::string(op.name), op.base_oper, std::string(op.base_op_name), engine), op.name); } return void_var(); } }; template struct Enum_AST_Node final : AST_Node_Impl { Enum_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Enum, std::move(t_loc), std::move(t_children)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { const auto &enum_name = this->children[0]->text; const auto &underlying_type_name = this->children[1]->text; const auto underlying_ti = t_ss->get_type(underlying_type_name); dispatch::Dynamic_Object container(enum_name); std::vector valid_values; for (size_t i = 2; i < this->children.size(); i += 2) { const auto &val_name = this->children[i]->text; const auto val_bv = Boxed_Number(this->children[i + 1]->eval(t_ss)).get_as(underlying_ti).bv; valid_values.push_back(val_bv); dispatch::Dynamic_Object dobj(enum_name); dobj.get_attr("value") = val_bv; dobj.set_explicit(true); container[val_name] = const_var(dobj); } auto shared_valid = std::make_shared>(std::move(valid_values)); container[enum_name] = var( fun([shared_valid, enum_name, underlying_ti](const Boxed_Number &t_val) -> Boxed_Value { const auto converted = t_val.get_as(underlying_ti); for (const auto &v : *shared_valid) { if (Boxed_Number::equals(Boxed_Number(v), converted)) { dispatch::Dynamic_Object dobj(enum_name); dobj.get_attr("value") = converted.bv; dobj.set_explicit(true); return const_var(dobj); } } throw exception::eval_error("Value is not valid for enum '" + enum_name + "'"); })); t_ss->add_global_const(const_var(container), enum_name); t_ss->add( std::make_shared( enum_name, fun([](const dispatch::Dynamic_Object &lhs, const dispatch::Dynamic_Object &rhs) { return Boxed_Number::equals(Boxed_Number(lhs.get_attr("value")), Boxed_Number(rhs.get_attr("value"))); })), "=="); t_ss->add( std::make_shared( enum_name, fun([](const dispatch::Dynamic_Object &lhs, const dispatch::Dynamic_Object &rhs) { return !Boxed_Number::equals(Boxed_Number(lhs.get_attr("value")), Boxed_Number(rhs.get_attr("value"))); })), "!="); t_ss->add( std::make_shared( enum_name, fun([](const dispatch::Dynamic_Object &obj) { return obj.get_attr("value"); })), "to_underlying"); return void_var(); } }; template struct Namespace_Block_AST_Node final : AST_Node_Impl { Namespace_Block_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Namespace_Block, std::move(t_loc), std::move(t_children)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { const auto &ns_name = this->children[0]->text; auto ns_name_bv = const_var(ns_name); t_ss->call_function("namespace", m_ns_loc, Function_Params{&ns_name_bv, 1}, t_ss.conversions()); std::vector parts; { std::string::size_type start = 0; std::string::size_type pos = 0; while ((pos = ns_name.find("::", start)) != std::string::npos) { parts.push_back(ns_name.substr(start, pos - start)); start = pos + 2; } parts.push_back(ns_name.substr(start)); } Boxed_Value ns_bv = t_ss.get_object(parts[0], m_root_loc); for (size_t i = 1; i < parts.size(); ++i) { auto &parent_ns = boxed_cast(ns_bv); ns_bv = parent_ns.get_attr(parts[i]); } auto &target_ns = boxed_cast(ns_bv); const auto process_statement = [&](const AST_Node_Impl &stmt) { if (stmt.identifier == AST_Node_Type::Def) { const auto &def_node = static_cast &>(stmt); target_ns[def_node.children[0]->text] = Boxed_Value(Def_AST_Node::make_proxy_function(def_node, t_ss)); } else if (stmt.identifier == AST_Node_Type::Assign_Decl || stmt.identifier == AST_Node_Type::Const_Assign_Decl) { const auto &var_name = stmt.children[0]->text; auto value = detail::clone_if_necessary(stmt.children[1]->eval(t_ss), m_clone_loc, t_ss); value.reset_return_value(); if (stmt.identifier == AST_Node_Type::Const_Assign_Decl) { value.make_const(); } target_ns[var_name] = std::move(value); } else if (stmt.identifier == AST_Node_Type::Equation && !stmt.children.empty() && (stmt.children[0]->identifier == AST_Node_Type::Var_Decl || stmt.children[0]->identifier == AST_Node_Type::Const_Var_Decl)) { const auto &var_name = stmt.children[0]->children[0]->text; auto value = detail::clone_if_necessary(stmt.children[1]->eval(t_ss), m_clone_loc, t_ss); value.reset_return_value(); target_ns[var_name] = std::move(value); } else if (stmt.identifier == AST_Node_Type::Var_Decl) { const auto &var_name = stmt.children[0]->text; target_ns[var_name] = Boxed_Value(); } else { throw exception::eval_error("Only declarations (def, var, auto, global) are allowed inside namespace blocks"); } }; const auto &body = this->children[1]; if (body->identifier == AST_Node_Type::Block || body->identifier == AST_Node_Type::Scopeless_Block) { for (const auto &child : body->children) { process_statement(*child); } } else { process_statement(*body); } return void_var(); } private: mutable std::atomic_uint_fast32_t m_ns_loc = {0}; mutable std::atomic_uint_fast32_t m_root_loc = {0}; mutable std::atomic_uint_fast32_t m_clone_loc = {0}; }; template struct If_AST_Node final : AST_Node_Impl { If_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::If, std::move(t_loc), std::move(t_children)) { assert(this->children.size() == 3); } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { if (this->get_bool_condition(this->children[0]->eval(t_ss), t_ss)) { return this->children[1]->eval(t_ss); } else { return this->children[2]->eval(t_ss); } } }; template struct Ranged_For_AST_Node final : AST_Node_Impl { Ranged_For_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Ranged_For, std::move(t_loc), std::move(t_children)) { assert(this->children.size() == 3); } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { const auto get_function = [&t_ss](const std::string &t_name, auto &t_hint) { uint_fast32_t hint = t_hint; auto [funs_loc, funs] = t_ss->get_function(t_name, hint); if (funs_loc != hint) { t_hint = uint_fast32_t(funs_loc); } return std::move(funs); }; const auto call_function = [&t_ss](const auto &t_funcs, const Boxed_Value &t_param) { return dispatch::dispatch(*t_funcs, Function_Params{&t_param, 1}, t_ss.conversions()); }; const std::string &loop_var_name = this->children[0]->text; Boxed_Value range_expression_result = this->children[1]->eval(t_ss); const auto do_loop = [&loop_var_name, &t_ss, this](const auto &ranged_thing) { try { for (auto &&loop_var : ranged_thing) { // This scope push and pop might not be the best thing for perf // but we know it's 100% correct chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); /// to-do make this if-constexpr with C++17 branch if (!std::is_same, Boxed_Value>::value) { t_ss.add_get_object(loop_var_name, Boxed_Value(std::ref(loop_var))); } else { t_ss.add_get_object(loop_var_name, Boxed_Value(loop_var)); } try { this->children[2]->eval(t_ss); } catch (detail::Continue_Loop &) { } } } catch (detail::Break_Loop &) { // loop broken } return void_var(); }; if (range_expression_result.get_type_info().bare_equal_type_info(typeid(std::vector))) { return do_loop(boxed_cast &>(range_expression_result)); } else if (range_expression_result.get_type_info().bare_equal_type_info(typeid(std::map))) { return do_loop(boxed_cast &>(range_expression_result)); } else { const auto range_funcs = get_function("range", m_range_loc); const auto empty_funcs = get_function("empty", m_empty_loc); const auto front_funcs = get_function("front", m_front_loc); const auto pop_front_funcs = get_function("pop_front", m_pop_front_loc); try { const auto range_obj = call_function(range_funcs, range_expression_result); while (!boxed_cast(call_function(empty_funcs, range_obj))) { chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); t_ss.add_get_object(loop_var_name, call_function(front_funcs, range_obj)); try { this->children[2]->eval(t_ss); } catch (detail::Continue_Loop &) { // continue statement hit } call_function(pop_front_funcs, range_obj); } } catch (detail::Break_Loop &) { // loop broken } return void_var(); } } private: mutable std::atomic_uint_fast32_t m_range_loc = {0}; mutable std::atomic_uint_fast32_t m_empty_loc = {0}; mutable std::atomic_uint_fast32_t m_front_loc = {0}; mutable std::atomic_uint_fast32_t m_pop_front_loc = {0}; }; template struct For_AST_Node final : AST_Node_Impl { For_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::For, std::move(t_loc), std::move(t_children)) { assert(this->children.size() == 4); } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); try { for (this->children[0]->eval(t_ss); this->get_scoped_bool_condition(*this->children[1], t_ss); this->children[2]->eval(t_ss)) { try { // Body of Loop this->children[3]->eval(t_ss); } catch (detail::Continue_Loop &) { // we got a continue exception, which means all of the remaining // loop implementation is skipped and we just need to continue to // the next iteration step } } } catch (detail::Break_Loop &) { // loop broken } return void_var(); } }; template struct Switch_AST_Node final : AST_Node_Impl { Switch_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Switch, std::move(t_loc), std::move(t_children)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { bool breaking = false; size_t currentCase = 1; bool hasMatched = false; chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); Boxed_Value match_value(this->children[0]->eval(t_ss)); while (!breaking && (currentCase < this->children.size())) { try { if (this->children[currentCase]->identifier == AST_Node_Type::Case) { // This is a little odd, but because want to see both the switch and the case simultaneously, I do a downcast here. try { if (hasMatched || boxed_cast(Binary_Operator_AST_Node::do_oper(t_ss, Operators::Opers::equals, "==", match_value, this->children[currentCase]->children[0]->eval(t_ss), m_loc))) { this->children[currentCase]->eval(t_ss); hasMatched = true; } } catch (const exception::bad_boxed_cast &) { throw exception::eval_error("Internal error: case guard evaluation not boolean"); } } else if (this->children[currentCase]->identifier == AST_Node_Type::Default) { this->children[currentCase]->eval(t_ss); hasMatched = true; } } catch (detail::Break_Loop &) { breaking = true; } ++currentCase; } return void_var(); } mutable std::atomic_uint_fast32_t m_loc = {0}; }; template struct Case_AST_Node final : AST_Node_Impl { Case_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Case, std::move(t_loc), std::move(t_children)) { assert(this->children.size() == 2); /* how many children does it have? */ } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); this->children[1]->eval(t_ss); return void_var(); } }; template struct Default_AST_Node final : AST_Node_Impl { Default_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Default, std::move(t_loc), std::move(t_children)) { assert(this->children.size() == 1); } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); this->children[0]->eval(t_ss); return void_var(); } }; template struct Inline_Array_AST_Node final : AST_Node_Impl { Inline_Array_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Inline_Array, std::move(t_loc), std::move(t_children)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { try { std::vector vec; if (!this->children.empty()) { vec.reserve(this->children[0]->children.size()); for (const auto &child : this->children[0]->children) { vec.push_back(detail::clone_if_necessary(child->eval(t_ss), m_loc, t_ss)); } } return const_var(std::move(vec)); } catch (const exception::dispatch_error &) { throw exception::eval_error("Can not find appropriate 'clone' or copy constructor for vector elements"); } } private: mutable std::atomic_uint_fast32_t m_loc = {0}; }; template struct Inline_Map_AST_Node final : AST_Node_Impl { Inline_Map_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Inline_Map, std::move(t_loc), std::move(t_children)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { try { std::map retval; for (const auto &child : this->children[0]->children) { retval.insert(std::make_pair(t_ss->boxed_cast(child->children[0]->eval(t_ss)), detail::clone_if_necessary(child->children[1]->eval(t_ss), m_loc, t_ss))); } return const_var(std::move(retval)); } catch (const exception::dispatch_error &e) { throw exception::eval_error("Can not find appropriate copy constructor or 'clone' while inserting into Map.", e.parameters, e.functions, false, *t_ss); } } private: mutable std::atomic_uint_fast32_t m_loc = {0}; }; template struct Return_AST_Node final : AST_Node_Impl { Return_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Return, std::move(t_loc), std::move(t_children)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { if (!this->children.empty()) { throw detail::Return_Value{this->children[0]->eval(t_ss)}; } else { throw detail::Return_Value{void_var()}; } } }; template struct File_AST_Node final : AST_Node_Impl { File_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::File, std::move(t_loc), std::move(t_children)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { try { const auto num_children = this->children.size(); if (num_children > 0) { for (size_t i = 0; i < num_children - 1; ++i) { this->children[i]->eval(t_ss); } return this->children.back()->eval(t_ss); } else { return void_var(); } } catch (const detail::Continue_Loop &) { throw exception::eval_error("Unexpected `continue` statement outside of a loop"); } catch (const detail::Break_Loop &) { throw exception::eval_error("Unexpected `break` statement outside of a loop"); } } }; template struct Reference_AST_Node final : AST_Node_Impl { Reference_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Reference, std::move(t_loc), std::move(t_children)) { assert(this->children.size() == 1); } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { Boxed_Value bv; t_ss.add_object(this->children[0]->text, bv); return bv; } }; template struct Prefix_AST_Node final : AST_Node_Impl { Prefix_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Prefix, std::move(t_loc), std::move(t_children)) , m_oper(Operators::to_operator(this->text, true)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { Boxed_Value bv(this->children[0]->eval(t_ss)); try { // short circuit arithmetic operations if (m_oper != Operators::Opers::invalid && m_oper != Operators::Opers::bitwise_and && bv.get_type_info().is_arithmetic()) { if ((m_oper == Operators::Opers::pre_increment || m_oper == Operators::Opers::pre_decrement) && bv.is_const()) { throw exception::eval_error("Error with prefix operator evaluation: cannot modify constant value."); } return Boxed_Number::do_oper(m_oper, bv); } else { chaiscript::eval::detail::Function_Push_Pop fpp(t_ss); fpp.save_params(Function_Params{&bv, 1}); return t_ss->call_function(this->text, m_loc, Function_Params{&bv, 1}, t_ss.conversions()); } } catch (const exception::dispatch_error &e) { throw exception::eval_error("Error with prefix operator evaluation: '" + this->text + "'", e.parameters, e.functions, false, *t_ss); } } private: Operators::Opers m_oper = Operators::Opers::invalid; mutable std::atomic_uint_fast32_t m_loc = {0}; }; template struct Break_AST_Node final : AST_Node_Impl { Break_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Break, std::move(t_loc), std::move(t_children)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &) const override { throw detail::Break_Loop(); } }; template struct Continue_AST_Node final : AST_Node_Impl { Continue_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Continue, std::move(t_loc), std::move(t_children)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &) const override { throw detail::Continue_Loop(); } }; template struct Noop_AST_Node final : AST_Node_Impl { Noop_AST_Node() : AST_Node_Impl("", AST_Node_Type::Noop, Parse_Location()) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &) const override { // It's a no-op, that evaluates to "void" return val; } Boxed_Value val = void_var(); }; template struct Map_Pair_AST_Node final : AST_Node_Impl { Map_Pair_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Map_Pair, std::move(t_loc), std::move(t_children)) { } }; template struct Value_Range_AST_Node final : AST_Node_Impl { Value_Range_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Value_Range, std::move(t_loc), std::move(t_children)) { } }; template struct Inline_Range_AST_Node final : AST_Node_Impl { Inline_Range_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Inline_Range, std::move(t_loc), std::move(t_children)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { try { std::array params{this->children[0]->children[0]->children[0]->eval(t_ss), this->children[0]->children[0]->children[1]->eval(t_ss)}; return t_ss->call_function("generate_range", m_loc, Function_Params{params}, t_ss.conversions()); } catch (const exception::dispatch_error &e) { throw exception::eval_error("Unable to generate range vector, while calling 'generate_range'", e.parameters, e.functions, false, *t_ss); } } private: mutable std::atomic_uint_fast32_t m_loc = {0}; }; template struct Try_AST_Node final : AST_Node_Impl { Try_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Try, std::move(t_loc), std::move(t_children)) { } Boxed_Value handle_exception(const chaiscript::detail::Dispatch_State &t_ss, const Boxed_Value &t_except) const { Boxed_Value retval; bool handled = false; size_t end_point = this->children.size(); if (this->children.back()->identifier == AST_Node_Type::Finally) { assert(end_point > 0); end_point = this->children.size() - 1; } for (size_t i = 1; i < end_point; ++i) { chaiscript::eval::detail::Scope_Push_Pop catch_scope(t_ss); auto &catch_block = *this->children[i]; if (catch_block.children.size() == 1) { // No variable capture retval = catch_block.children[0]->eval(t_ss); handled = true; break; } else if (catch_block.children.size() == 2 || catch_block.children.size() == 3) { const auto name = Arg_List_AST_Node::get_arg_name(*catch_block.children[0]); if (dispatch::Param_Types( std::vector>{Arg_List_AST_Node::get_arg_type(*catch_block.children[0], t_ss)}) .match(Function_Params{&t_except, 1}, t_ss.conversions()) .first) { t_ss.add_object(name, t_except); if (catch_block.children.size() == 2) { // Variable capture retval = catch_block.children[1]->eval(t_ss); handled = true; break; } } } else { throw exception::eval_error("Internal error: catch block size unrecognized"); } } if (!handled) { throw; } return retval; } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { Boxed_Value retval; chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); try { try { retval = this->children[0]->eval(t_ss); } catch (const exception::eval_error &e) { retval = handle_exception(t_ss, Boxed_Value(std::ref(e))); } catch (const std::runtime_error &e) { retval = handle_exception(t_ss, Boxed_Value(std::ref(e))); } catch (const std::out_of_range &e) { retval = handle_exception(t_ss, Boxed_Value(std::ref(e))); } catch (const std::exception &e) { retval = handle_exception(t_ss, Boxed_Value(std::ref(e))); } catch (Boxed_Value &e) { retval = handle_exception(t_ss, e); } } catch (...) { if (this->children.back()->identifier == AST_Node_Type::Finally) { this->children.back()->children[0]->eval(t_ss); } throw; } if (this->children.back()->identifier == AST_Node_Type::Finally) { retval = this->children.back()->children[0]->eval(t_ss); } return retval; } }; template struct Catch_AST_Node final : AST_Node_Impl { Catch_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Catch, std::move(t_loc), std::move(t_children)) { } }; template struct Finally_AST_Node final : AST_Node_Impl { Finally_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Finally, std::move(t_loc), std::move(t_children)) { } }; template struct Method_AST_Node final : AST_Node_Impl { std::shared_ptr> m_body_node; std::shared_ptr> m_guard_node; Method_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Method, std::move(t_loc), std::vector>(std::make_move_iterator(t_children.begin()), std::make_move_iterator( std::prev(t_children.end(), Def_AST_Node::has_guard(t_children, 1) ? 2 : 1)))) , m_body_node(Def_AST_Node::get_body_node(std::move(t_children))) , m_guard_node(Def_AST_Node::get_guard_node(std::move(t_children), t_children.size() - this->children.size() == 2)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { AST_Node_Impl_Ptr guardnode; const std::string &class_name = this->children[0]->text; // The first param of a method is always the implied this ptr. std::vector t_param_names{"this"}; dispatch::Param_Types param_types; if ((this->children.size() > 2) && (this->children[2]->identifier == AST_Node_Type::Arg_List)) { auto args = Arg_List_AST_Node::get_arg_names(*this->children[2]); t_param_names.insert(t_param_names.end(), args.begin(), args.end()); param_types = Arg_List_AST_Node::get_arg_types(*this->children[2], t_ss); } const size_t numparams = t_param_names.size(); std::shared_ptr guard; std::reference_wrapper engine(*t_ss); if (m_guard_node) { guard = dispatch::make_dynamic_proxy_function( [engine, t_param_names, guardnode = m_guard_node](const Function_Params &t_params) { return chaiscript::eval::detail::eval_function(engine, *guardnode, t_param_names, t_params); }, static_cast(numparams), m_guard_node); } try { const std::string &function_name = this->children[1]->text; if (function_name == class_name) { param_types.push_front(class_name, Type_Info()); t_ss->add(std::make_shared( class_name, dispatch::make_dynamic_proxy_function( [engine, t_param_names, node = m_body_node](const Function_Params &t_params) { return chaiscript::eval::detail::eval_function(engine, *node, t_param_names, t_params); }, static_cast(numparams), m_body_node, param_types, guard)), function_name); } else { // if the type is unknown, then this generates a function that looks up the type // at runtime. Defining the type first before this is called is better auto type = t_ss->get_type(class_name, false); param_types.push_front(class_name, type); t_ss->add(std::make_shared( class_name, dispatch::make_dynamic_proxy_function( [engine, t_param_names, node = m_body_node](const Function_Params &t_params) { return chaiscript::eval::detail::eval_function(engine, *node, t_param_names, t_params); }, static_cast(numparams), m_body_node, param_types, guard), type), function_name); } } catch (const exception::name_conflict_error &e) { throw exception::eval_error("Method redefined '" + e.name() + "'"); } return void_var(); } }; template struct Attr_Decl_AST_Node final : AST_Node_Impl { Attr_Decl_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Attr_Decl, std::move(t_loc), std::move(t_children)) { } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { std::string class_name = this->children[0]->text; try { std::string attr_name = this->children[1]->text; t_ss->add(std::make_shared(std::move(class_name), fun([attr_name](dispatch::Dynamic_Object &t_obj) { return t_obj.get_attr(attr_name); }), true ), this->children[1]->text); } catch (const exception::name_conflict_error &e) { throw exception::eval_error("Attribute redefined '" + e.name() + "'"); } return void_var(); } }; template struct Logical_And_AST_Node final : AST_Node_Impl { Logical_And_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Logical_And, std::move(t_loc), std::move(t_children)) { assert(this->children.size() == 2); } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { return const_var(this->get_bool_condition(this->children[0]->eval(t_ss), t_ss) && this->get_bool_condition(this->children[1]->eval(t_ss), t_ss)); } }; template struct Logical_Or_AST_Node final : AST_Node_Impl { Logical_Or_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) : AST_Node_Impl(std::move(t_ast_node_text), AST_Node_Type::Logical_Or, std::move(t_loc), std::move(t_children)) { assert(this->children.size() == 2); } Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { return const_var(this->get_bool_condition(this->children[0]->eval(t_ss), t_ss) || this->get_bool_condition(this->children[1]->eval(t_ss), t_ss)); } }; } // namespace eval } // namespace chaiscript #endif /* CHAISCRIPT_EVAL_HPP_ */