From 09bdec48828d4e223e729cef78c9e8a400244fd3 Mon Sep 17 00:00:00 2001 From: Jason Turner Date: Sun, 26 Jun 2016 20:00:01 -0600 Subject: [PATCH] Add ranged-for loops closes [#145] --- cheatsheet.md | 18 ++++ .../chaiscript/dispatchkit/dispatchkit.hpp | 28 ++++++- .../chaiscript/language/chaiscript_common.hpp | 4 +- .../chaiscript/language/chaiscript_eval.hpp | 83 +++++++++++++++++++ .../chaiscript/language/chaiscript_parser.hpp | 21 +++-- unittests/ranged_for.chai | 21 +++++ 6 files changed, 166 insertions(+), 9 deletions(-) create mode 100644 unittests/ranged_for.chai diff --git a/cheatsheet.md b/cheatsheet.md index c37045f7..dbaf5da4 100644 --- a/cheatsheet.md +++ b/cheatsheet.md @@ -111,6 +111,7 @@ chai.add_global_const(chaiscript::const_var(somevar), "somevar"); // global cons chai.add_global(chaiscript::var(somevar), "somevar"); // global non-const, throws if object exists chai.set_global(chaiscript::var(somevar), "somevar"); // global non-const, overwrites existing object ``` + # Using STL ChaiScript recognize many types from STL, but you have to add specific instantiation yourself. @@ -260,6 +261,23 @@ if (g2.is_var_undef()) { g2 = 4; } // only initialize g2 once, if global decl hi GLOBAL g3; // all upper case version also accepted ``` +## Looping + +``` +// c-style for loops +for (var i = 0; i < 100; ++i) { print(i); } +``` + +``` +// while +while (some_condition()) { /* do something */ } +``` + +``` +// ranged for +for (x : [1,2,3]) { print(i); } +``` + ## Built in Types ``` diff --git a/include/chaiscript/dispatchkit/dispatchkit.hpp b/include/chaiscript/dispatchkit/dispatchkit.hpp index 3b3bf3b3..2bed224e 100644 --- a/include/chaiscript/dispatchkit/dispatchkit.hpp +++ b/include/chaiscript/dispatchkit/dispatchkit.hpp @@ -462,6 +462,26 @@ namespace chaiscript add_object(name, std::move(obj)); } + /// Adds a named object to the current scope + /// \warning This version does not check the validity of the name + /// it is meant for internal use only + Boxed_Value &add_get_object(const std::string &t_name, Boxed_Value obj, Stack_Holder &t_holder) + { + auto &stack_elem = get_stack_data(t_holder).back(); + + if (std::any_of(stack_elem.begin(), stack_elem.end(), + [&](const std::pair &o) { + return o.first == t_name; + })) + { + throw chaiscript::exception::name_conflict_error(t_name); + } + + stack_elem.emplace_back(t_name, std::move(obj)); + return stack_elem.back().second; + } + + /// Adds a named object to the current scope /// \warning This version does not check the validity of the name /// it is meant for internal use only @@ -477,7 +497,7 @@ namespace chaiscript throw chaiscript::exception::name_conflict_error(t_name); } - get_stack_data(t_holder).back().emplace_back(t_name, std::move(obj)); + stack_elem.emplace_back(t_name, std::move(obj)); } @@ -1478,8 +1498,12 @@ namespace chaiscript return m_conversions.saves(); } + Boxed_Value &add_get_object(const std::string &t_name, Boxed_Value obj) const { + return m_engine.get().add_get_object(t_name, std::move(obj), m_stack_holder.get()); + } + void add_object(const std::string &t_name, Boxed_Value obj) const { - m_engine.get().add_object(t_name, std::move(obj), m_stack_holder.get()); + return m_engine.get().add_object(t_name, std::move(obj), m_stack_holder.get()); } Boxed_Value get_object(const std::string &t_name, std::atomic_uint_fast32_t &t_loc) const { diff --git a/include/chaiscript/language/chaiscript_common.hpp b/include/chaiscript/language/chaiscript_common.hpp index 3f093da8..59db2e5f 100644 --- a/include/chaiscript/language/chaiscript_common.hpp +++ b/include/chaiscript/language/chaiscript_common.hpp @@ -58,7 +58,7 @@ namespace chaiscript /// Types of AST nodes available to the parser and eval enum class AST_Node_Type { Id, Fun_Call, Unused_Return_Fun_Call, Arg_List, Equation, Var_Decl, Array_Call, Dot_Access, - Lambda, Block, Scopeless_Block, Def, While, If, For, Inline_Array, Inline_Map, Return, File, Prefix, Break, Continue, Map_Pair, Value_Range, + Lambda, Block, Scopeless_Block, Def, While, If, For, Ranged_For, Inline_Array, Inline_Map, Return, File, Prefix, Break, Continue, Map_Pair, Value_Range, Inline_Range, Try, Catch, Finally, Method, Attr_Decl, Logical_And, Logical_Or, Reference, Switch, Case, Default, Ternary_Cond, Noop, Class, Binary, Arg, Global_Decl, Constant, Compiled }; @@ -71,7 +71,7 @@ namespace chaiscript const char *ast_node_type_to_string(AST_Node_Type ast_node_type) { static const char * const ast_node_types[] = { "Id", "Fun_Call", "Unused_Return_Fun_Call", "Arg_List", "Equation", "Var_Decl", "Array_Call", "Dot_Access", - "Lambda", "Block", "Scopeless_Block", "Def", "While", "If", "For", "Inline_Array", "Inline_Map", "Return", "File", "Prefix", "Break", "Continue", "Map_Pair", "Value_Range", + "Lambda", "Block", "Scopeless_Block", "Def", "While", "If", "For", "Ranged_For", "Inline_Array", "Inline_Map", "Return", "File", "Prefix", "Break", "Continue", "Map_Pair", "Value_Range", "Inline_Range", "Try", "Catch", "Finally", "Method", "Attr_Decl", "Logical_And", "Logical_Or", "Reference", "Switch", "Case", "Default", "Ternary Condition", "Noop", "Class", "Binary", "Arg", "Global_Decl", "Constant", "Compiled"}; diff --git a/include/chaiscript/language/chaiscript_eval.hpp b/include/chaiscript/language/chaiscript_eval.hpp index 463f40a4..717a3b30 100644 --- a/include/chaiscript/language/chaiscript_eval.hpp +++ b/include/chaiscript/language/chaiscript_eval.hpp @@ -844,6 +844,89 @@ namespace chaiscript } }; + 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)), + m_range_loc(0), + m_empty_loc(0), + m_front_loc(0), + m_pop_front_loc(0) + { 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 = t_ss->get_function(t_name, hint); + if (funs.first != hint) t_hint = uint_fast32_t(funs.first); + return std::move(funs.second); + }; + + const auto call_function = [&t_ss](const auto &t_funcs, const Boxed_Value &t_param) { + return dispatch::dispatch(*t_funcs, {t_param}, 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 { + chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); + Boxed_Value &obj = t_ss.add_get_object(loop_var_name, void_var()); + for (auto loop_var : ranged_thing) { + obj = Boxed_Value(std::move(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); + chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); + Boxed_Value &obj = t_ss.add_get_object(loop_var_name, void_var()); + while (!boxed_cast(call_function(empty_funcs, range_obj))) { + obj = call_function(front_funcs, range_obj); + try { + this->children[2]->eval(t_ss); + } catch (detail::Continue_Loop &) { + } + 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; + mutable std::atomic_uint_fast32_t m_empty_loc; + mutable std::atomic_uint_fast32_t m_front_loc; + mutable std::atomic_uint_fast32_t m_pop_front_loc; + + }; + + 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) : diff --git a/include/chaiscript/language/chaiscript_parser.hpp b/include/chaiscript/language/chaiscript_parser.hpp index 2f136305..5fc7e278 100644 --- a/include/chaiscript/language/chaiscript_parser.hpp +++ b/include/chaiscript/language/chaiscript_parser.hpp @@ -1647,14 +1647,20 @@ namespace chaiscript return retval; } + /// Reads the ranged `for` conditions from input + bool Range_Expression() { + // the first element will have already been captured by the For_Guards() call that preceeds it + return Char(':') && Equation(); + } - /// Reads the C-style for conditions from input + + /// Reads the C-style `for` conditions from input bool For_Guards() { if (!(Equation() && Eol())) { if (!Eol()) { - throw exception::eval_error("'for' loop initial statment missing", File_Position(m_position.line, m_position.col), *m_filename); + return false; } else { m_match_stack.push_back(chaiscript::make_shared, eval::Noop_AST_Node>()); } @@ -1664,7 +1670,7 @@ namespace chaiscript { if (!Eol()) { - throw exception::eval_error("'for' loop condition missing", File_Position(m_position.line, m_position.col), *m_filename); + return false; } else { m_match_stack.push_back(chaiscript::make_shared, eval::Noop_AST_Node>()); } @@ -1692,7 +1698,8 @@ namespace chaiscript throw exception::eval_error("Incomplete 'for' expression", File_Position(m_position.line, m_position.col), *m_filename); } - if (!(For_Guards() && Char(')'))) { + const bool classic_for = For_Guards() && Char(')'); + if (!classic_for && !(Range_Expression() && Char(')'))) { throw exception::eval_error("Incomplete 'for' expression", File_Position(m_position.line, m_position.col), *m_filename); } @@ -1702,7 +1709,11 @@ namespace chaiscript throw exception::eval_error("Incomplete 'for' block", File_Position(m_position.line, m_position.col), *m_filename); } - build_match>(prev_stack_top); + if (classic_for) { + build_match>(prev_stack_top); + } else { + build_match>(prev_stack_top); + } } return retval; diff --git a/unittests/ranged_for.chai b/unittests/ranged_for.chai new file mode 100644 index 00000000..9ef34fed --- /dev/null +++ b/unittests/ranged_for.chai @@ -0,0 +1,21 @@ +var sum = 0; + +for (x : [1,2,3,4]) { + sum += x; +} + +assert_equal(sum, 10) + +var result = 0.0; + +for (x : retro(range([1,2,3,4]))) { + if (result == 0.0) { + result = x; + } else { + result /= x; + } +} + +assert_true(result > .6 && result < .7); + +