// 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 #ifndef CHAISCRIPT_OPTIMIZER_HPP_ #define CHAISCRIPT_OPTIMIZER_HPP_ #include "chaiscript_eval.hpp" namespace chaiscript { namespace optimizer { template struct Optimizer : T... { Optimizer() = default; explicit Optimizer(T... t) : T(std::move(t))... { } template auto optimize(eval::AST_Node_Impl_Ptr p) { ((p = static_cast(*this).optimize(std::move(p))), ...); return p; } }; template eval::AST_Node_Impl &child_at(eval::AST_Node_Impl &node, const size_t offset) noexcept { if (node.children[offset]->identifier == AST_Node_Type::Compiled) { return *(dynamic_cast &>(*node.children[offset]).m_original_node); } else { return *node.children[offset]; } } template const eval::AST_Node_Impl &child_at(const eval::AST_Node_Impl &node, const size_t offset) noexcept { if (node.children[offset]->identifier == AST_Node_Type::Compiled) { return *(dynamic_cast &>(*node.children[offset]).m_original_node); } else { return *node.children[offset]; } /* if (node->identifier == AST_Node_Type::Compiled) { return dynamic_cast&>(*node).m_original_node->children[offset]; } else { return node->children[offset]; } */ } template auto child_count(const eval::AST_Node_Impl &node) noexcept { if (node.identifier == AST_Node_Type::Compiled) { return dynamic_cast &>(node).m_original_node->children.size(); } else { return node.children.size(); } } template auto make_compiled_node(eval::AST_Node_Impl_Ptr original_node, std::vector> children, Callable callable) { return chaiscript::make_unique, eval::Compiled_AST_Node>(std::move(original_node), std::move(children), std::move(callable)); } struct Return { template auto optimize(eval::AST_Node_Impl_Ptr p) { if ((p->identifier == AST_Node_Type::Def || p->identifier == AST_Node_Type::Lambda) && !p->children.empty()) { auto &last_child = p->children.back(); if (last_child->identifier == AST_Node_Type::Block) { auto &block_last_child = last_child->children.back(); if (block_last_child->identifier == AST_Node_Type::Return) { if (block_last_child->children.size() == 1) { last_child->children.back() = std::move(block_last_child->children[0]); } } } } return p; } }; template bool contains_var_decl_in_scope(const eval::AST_Node_Impl &node) noexcept { if (node.identifier == AST_Node_Type::Var_Decl || node.identifier == AST_Node_Type::Assign_Decl || node.identifier == AST_Node_Type::Reference) { return true; } const auto num = child_count(node); for (size_t i = 0; i < num; ++i) { const auto &child = child_at(node, i); if (child.identifier != AST_Node_Type::Block && child.identifier != AST_Node_Type::For && child.identifier != AST_Node_Type::Ranged_For && contains_var_decl_in_scope(child)) { return true; } } return false; } struct Block { template auto optimize(eval::AST_Node_Impl_Ptr node) { if (node->identifier == AST_Node_Type::Block) { if (!contains_var_decl_in_scope(*node)) { if (node->children.size() == 1) { return std::move(node->children[0]); } else { return chaiscript::make_unique, eval::Scopeless_Block_AST_Node>(node->text, node->location, std::move(node->children)); } } } return node; } }; struct Dead_Code { template auto optimize(eval::AST_Node_Impl_Ptr node) { if (node->identifier == AST_Node_Type::Block) { std::vector keepers; const auto num_children = node->children.size(); keepers.reserve(num_children); for (size_t i = 0; i < num_children; ++i) { const auto &child = *node->children[i]; if ((child.identifier != AST_Node_Type::Id && child.identifier != AST_Node_Type::Constant && child.identifier != AST_Node_Type::Noop) || i == num_children - 1) { keepers.push_back(i); } } if (keepers.size() == num_children) { return node; } else { const auto new_children = [&]() { std::vector> retval; for (const auto x : keepers) { retval.push_back(std::move(node->children[x])); } return retval; }; return chaiscript::make_unique, eval::Block_AST_Node>(node->text, node->location, new_children()); } } else { return node; } } }; struct Unused_Return { template auto optimize(eval::AST_Node_Impl_Ptr node) { if ((node->identifier == AST_Node_Type::Block || node->identifier == AST_Node_Type::Scopeless_Block) && !node->children.empty()) { for (size_t i = 0; i < node->children.size() - 1; ++i) { auto child = node->children[i].get(); if (child->identifier == AST_Node_Type::Fun_Call) { node->children[i] = chaiscript::make_unique, eval::Unused_Return_Fun_Call_AST_Node>(child->text, child->location, std::move(child->children)); } } } else if ((node->identifier == AST_Node_Type::For || node->identifier == AST_Node_Type::While) && child_count(*node) > 0) { auto &child = child_at(*node, child_count(*node) - 1); if (child.identifier == AST_Node_Type::Block || child.identifier == AST_Node_Type::Scopeless_Block) { auto num_sub_children = child_count(child); for (size_t i = 0; i < num_sub_children; ++i) { auto &sub_child = child_at(child, i); if (sub_child.identifier == AST_Node_Type::Fun_Call) { child.children[i] = chaiscript::make_unique, eval::Unused_Return_Fun_Call_AST_Node>( sub_child.text, sub_child.location, std::move(sub_child.children)); } } } } return node; } }; struct Assign_Decl { template auto optimize(eval::AST_Node_Impl_Ptr node) { if ((node->identifier == AST_Node_Type::Equation) && node->text == "=" && node->children.size() == 2 && node->children[0]->identifier == AST_Node_Type::Var_Decl) { std::vector> new_children; new_children.push_back(std::move(node->children[0]->children[0])); new_children.push_back(std::move(node->children[1])); return chaiscript::make_unique, eval::Assign_Decl_AST_Node>(node->text, node->location, std::move(new_children)); } return node; } }; struct If { template auto optimize(eval::AST_Node_Impl_Ptr node) { if ((node->identifier == AST_Node_Type::If) && node->children.size() >= 2 && node->children[0]->identifier == AST_Node_Type::Constant) { const auto condition = dynamic_cast *>(node->children[0].get())->m_value; if (condition.get_type_info().bare_equal_type_info(typeid(bool))) { if (boxed_cast(condition)) { return std::move(node->children[1]); } else if (node->children.size() == 3) { return std::move(node->children[2]); } } } return node; } }; struct Partial_Fold { template auto optimize(eval::AST_Node_Impl_Ptr node) { // Fold right side if (node->identifier == AST_Node_Type::Binary && node->children.size() == 2 && node->children[0]->identifier != AST_Node_Type::Constant && node->children[1]->identifier == AST_Node_Type::Constant) { try { const auto &oper = node->text; const auto parsed = Operators::to_operator(oper); if (parsed != Operators::Opers::invalid) { const auto rhs = dynamic_cast *>(node->children[1].get())->m_value; if (rhs.get_type_info().is_arithmetic()) { return chaiscript::make_unique, eval::Fold_Right_Binary_Operator_AST_Node>( node->text, node->location, std::move(node->children), rhs); } } } catch (const std::exception &) { // failure to fold, that's OK } } return node; } }; struct Constant_Fold { template auto optimize(eval::AST_Node_Impl_Ptr node) { if (node->identifier == AST_Node_Type::Prefix && node->children.size() == 1 && node->children[0]->identifier == AST_Node_Type::Constant) { try { const auto &oper = node->text; const auto parsed = Operators::to_operator(oper, true); const auto lhs = dynamic_cast *>(node->children[0].get())->m_value; const auto match = oper + node->children[0]->text; if (parsed != Operators::Opers::invalid && parsed != Operators::Opers::bitwise_and && lhs.get_type_info().is_arithmetic()) { const auto val = Boxed_Number::do_oper(parsed, lhs); return chaiscript::make_unique, eval::Constant_AST_Node>(std::move(match), node->location, std::move(val)); } else if (lhs.get_type_info().bare_equal_type_info(typeid(bool)) && oper == "!") { return chaiscript::make_unique, eval::Constant_AST_Node>(std::move(match), node->location, Boxed_Value(!boxed_cast(lhs))); } } catch (const std::exception &) { // failure to fold, that's OK } } else if ((node->identifier == AST_Node_Type::Logical_And || node->identifier == AST_Node_Type::Logical_Or) && node->children.size() == 2 && node->children[0]->identifier == AST_Node_Type::Constant && node->children[1]->identifier == AST_Node_Type::Constant) { try { const auto lhs = dynamic_cast &>(*node->children[0]).m_value; const auto rhs = dynamic_cast &>(*node->children[1]).m_value; if (lhs.get_type_info().bare_equal_type_info(typeid(bool)) && rhs.get_type_info().bare_equal_type_info(typeid(bool))) { const auto match = node->children[0]->text + " " + node->text + " " + node->children[1]->text; const auto val = [lhs_val = boxed_cast(lhs), rhs_val = boxed_cast(rhs), id = node->identifier] { if (id == AST_Node_Type::Logical_And) { return Boxed_Value(lhs_val && rhs_val); } else { return Boxed_Value(lhs_val || rhs_val); } }(); return chaiscript::make_unique, eval::Constant_AST_Node>(std::move(match), node->location, std::move(val)); } } catch (const std::exception &) { // failure to fold, that's OK } } else if (node->identifier == AST_Node_Type::Binary && node->children.size() == 2 && node->children[0]->identifier == AST_Node_Type::Constant && node->children[1]->identifier == AST_Node_Type::Constant) { try { const auto &oper = node->text; const auto parsed = Operators::to_operator(oper); if (parsed != Operators::Opers::invalid) { const auto lhs = dynamic_cast &>(*node->children[0]).m_value; const auto rhs = dynamic_cast &>(*node->children[1]).m_value; if (lhs.get_type_info().is_arithmetic() && rhs.get_type_info().is_arithmetic()) { const auto val = Boxed_Number::do_oper(parsed, lhs, rhs); const auto match = node->children[0]->text + " " + oper + " " + node->children[1]->text; return chaiscript::make_unique, eval::Constant_AST_Node>(std::move(match), node->location, std::move(val)); } } } catch (const std::exception &) { // failure to fold, that's OK } } else if (node->identifier == AST_Node_Type::Fun_Call && node->children.size() == 2 && node->children[0]->identifier == AST_Node_Type::Id && node->children[1]->identifier == AST_Node_Type::Arg_List && node->children[1]->children.size() == 1 && node->children[1]->children[0]->identifier == AST_Node_Type::Constant) { const auto arg = dynamic_cast &>(*node->children[1]->children[0]).m_value; if (arg.get_type_info().is_arithmetic()) { const auto &fun_name = node->children[0]->text; const auto make_constant = [&node, &fun_name](auto val) { const auto match = fun_name + "(" + node->children[1]->children[0]->text + ")"; return chaiscript::make_unique, eval::Constant_AST_Node>(std::move(match), node->location, const_var(val)); }; if (fun_name == "double") { return make_constant(Boxed_Number(arg).get_as()); } else if (fun_name == "int") { return make_constant(Boxed_Number(arg).get_as()); } else if (fun_name == "float") { return make_constant(Boxed_Number(arg).get_as()); } else if (fun_name == "long") { return make_constant(Boxed_Number(arg).get_as()); } else if (fun_name == "size_t") { return make_constant(Boxed_Number(arg).get_as()); } } } return node; } }; struct For_Loop { template auto optimize(eval::AST_Node_Impl_Ptr for_node) { if (for_node->identifier != AST_Node_Type::For) { return for_node; } const auto &eq_node = child_at(*for_node, 0); const auto &binary_node = child_at(*for_node, 1); const auto &prefix_node = child_at(*for_node, 2); if (child_count(*for_node) == 4 && eq_node.identifier == AST_Node_Type::Assign_Decl && child_count(eq_node) == 2 && child_at(eq_node, 0).identifier == AST_Node_Type::Id && child_at(eq_node, 1).identifier == AST_Node_Type::Constant && binary_node.identifier == AST_Node_Type::Binary && binary_node.text == "<" && child_count(binary_node) == 2 && child_at(binary_node, 0).identifier == AST_Node_Type::Id && child_at(binary_node, 0).text == child_at(eq_node, 0).text && child_at(binary_node, 1).identifier == AST_Node_Type::Constant && prefix_node.identifier == AST_Node_Type::Prefix && prefix_node.text == "++" && child_count(prefix_node) == 1 && child_at(prefix_node, 0).identifier == AST_Node_Type::Id && child_at(prefix_node, 0).text == child_at(eq_node, 0).text) { const Boxed_Value &begin = dynamic_cast &>(child_at(eq_node, 1)).m_value; const Boxed_Value &end = dynamic_cast &>(child_at(binary_node, 1)).m_value; const std::string &id = child_at(prefix_node, 0).text; if (begin.get_type_info().bare_equal(user_type()) && end.get_type_info().bare_equal(user_type())) { const auto start_int = boxed_cast(begin); const auto end_int = boxed_cast(end); // note that we are moving the last element out, then popping the empty shared_ptr // from the vector std::vector> body_vector; auto body_child = std::move(for_node->children[3]); for_node->children.pop_back(); body_vector.emplace_back(std::move(body_child)); return make_compiled_node(std::move(for_node), std::move(body_vector), [id, start_int, end_int](const std::vector> &children, const chaiscript::detail::Dispatch_State &t_ss) { assert(children.size() == 1); chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); int i = start_int; t_ss.add_object(id, var(&i)); try { for (; i < end_int; ++i) { try { // Body of Loop children[0]->eval(t_ss); } catch (eval::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 (eval::detail::Break_Loop &) { // loop broken } return void_var(); }); } else { return for_node; } } else { return for_node; } } }; using Optimizer_Default = Optimizer; } // namespace optimizer } // namespace chaiscript #endif