diff --git a/include/chaiscript/language/chaiscript_common.hpp b/include/chaiscript/language/chaiscript_common.hpp index c2f99d0..cf06a93 100644 --- a/include/chaiscript/language/chaiscript_common.hpp +++ b/include/chaiscript/language/chaiscript_common.hpp @@ -23,7 +23,7 @@ namespace chaiscript Comparison, Addition, Subtraction, Multiplication, Division, Modulus, Array_Call, Dot_Access, Quoted_String, Single_Quoted_String, Lambda, Block, Def, While, If, For, Inline_Array, Inline_Map, Return, File, Prefix, Break, Map_Pair, Value_Range, Inline_Range, Annotation, Try, Catch, Finally, Method, Attr_Decl, Shift, Equality, Bitwise_And, Bitwise_Xor, Bitwise_Or, - Logical_And, Logical_Or + Logical_And, Logical_Or, Switch, Case, Default }; }; @@ -37,7 +37,7 @@ namespace chaiscript "Comparison", "Addition", "Subtraction", "Multiplication", "Division", "Modulus", "Array_Call", "Dot_Access", "Quoted_String", "Single_Quoted_String", "Lambda", "Block", "Def", "While", "If", "For", "Inline_Array", "Inline_Map", "Return", "File", "Prefix", "Break", "Map_Pair", "Value_Range", "Inline_Range", "Annotation", "Try", "Catch", "Finally", "Method", "Attr_Decl", "Shift", "Equality", "Bitwise_And", "Bitwise_Xor", "Bitwise_Or", - "Logical_And", "Logical_Or"}; + "Logical_And", "Logical_Or", "Switch", "Case", "Default"}; return ast_node_types[ast_node_type]; } diff --git a/include/chaiscript/language/chaiscript_eval.hpp b/include/chaiscript/language/chaiscript_eval.hpp index d6a7296..cb7cd50 100644 --- a/include/chaiscript/language/chaiscript_eval.hpp +++ b/include/chaiscript/language/chaiscript_eval.hpp @@ -780,6 +780,79 @@ namespace chaiscript }; + struct Switch_AST_Node : public AST_Node { + public: + Switch_AST_Node(const std::string &t_ast_node_text = "", int t_id = AST_Node_Type::Switch, const boost::shared_ptr &t_fname=boost::shared_ptr(), int t_start_line = 0, int t_start_col = 0, int t_end_line = 0, int t_end_col = 0) : + AST_Node(t_ast_node_text, t_id, t_fname, t_start_line, t_start_col, t_end_line, t_end_col) { } + virtual ~Switch_AST_Node() {} + virtual Boxed_Value eval_internal(chaiscript::detail::Dispatch_Engine &t_ss) { + Boxed_Value match_value; + bool breaking = false; + int currentCase = 1; + bool hasMatched = false; + + chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); + + match_value = this->children[0]->eval(t_ss); + + while (!breaking) { + 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(t_ss.call_function("==", match_value, this->children[currentCase]->children[0]->eval(t_ss)))) { + 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); + breaking = true; + } + } + catch (detail::Break_Loop &) { + breaking = true; + } + ++currentCase; + if (currentCase == this->children.size()) + breaking = true; + } + return Boxed_Value(); + } + }; + + struct Case_AST_Node : public AST_Node { + public: + Case_AST_Node(const std::string &t_ast_node_text = "", int t_id = AST_Node_Type::Case, const boost::shared_ptr &t_fname=boost::shared_ptr(), int t_start_line = 0, int t_start_col = 0, int t_end_line = 0, int t_end_col = 0) : + AST_Node(t_ast_node_text, t_id, t_fname, t_start_line, t_start_col, t_end_line, t_end_col) { } + virtual ~Case_AST_Node() {} + virtual Boxed_Value eval_internal(chaiscript::detail::Dispatch_Engine &t_ss) { + chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); + + this->children[1]->eval(t_ss); + + return Boxed_Value(); + } + }; + + struct Default_AST_Node : public AST_Node { + public: + Default_AST_Node(const std::string &t_ast_node_text = "", int t_id = AST_Node_Type::Default, const boost::shared_ptr &t_fname=boost::shared_ptr(), int t_start_line = 0, int t_start_col = 0, int t_end_line = 0, int t_end_col = 0) : + AST_Node(t_ast_node_text, t_id, t_fname, t_start_line, t_start_col, t_end_line, t_end_col) { } + virtual ~Default_AST_Node() {} + virtual Boxed_Value eval_internal(chaiscript::detail::Dispatch_Engine &t_ss) { + chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); + + this->children[0]->eval(t_ss); + + return Boxed_Value(); + } + }; + struct Inline_Array_AST_Node : public AST_Node { public: Inline_Array_AST_Node(const std::string &t_ast_node_text = "", int t_id = AST_Node_Type::Inline_Array, const boost::shared_ptr &t_fname=boost::shared_ptr(), int t_start_line = 0, int t_start_col = 0, int t_end_line = 0, int t_end_col = 0) : diff --git a/include/chaiscript/language/chaiscript_parser.hpp b/include/chaiscript/language/chaiscript_parser.hpp index a6b22df..fd58ef2 100644 --- a/include/chaiscript/language/chaiscript_parser.hpp +++ b/include/chaiscript/language/chaiscript_parser.hpp @@ -1415,6 +1415,92 @@ namespace chaiscript return retval; } + /** + * Reads a case block from input + */ + bool Case() { + bool retval = false; + + size_t prev_stack_top = m_match_stack.size(); + + if (Keyword("case")) { + retval = true; + + if (!Char('(')) { + throw exception::eval_error("Incomplete 'case' expression", File_Position(m_line, m_col), *m_filename); + } + + if (!(Operator() && Char(')'))) { + throw exception::eval_error("Incomplete 'case' expression", File_Position(m_line, m_col), *m_filename); + } + + while (Eol()) {} + + if (!Block()) { + throw exception::eval_error("Incomplete 'case' block", File_Position(m_line, m_col), *m_filename); + } + + build_match(AST_NodePtr(new eval::Case_AST_Node()), prev_stack_top); + } + else if (Keyword("default")) { + while (Eol()) {} + + if (!Block()) { + throw exception::eval_error("Incomplete 'default' block", File_Position(m_line, m_col), *m_filename); + } + + build_match(AST_NodePtr(new eval::Default_AST_Node()), prev_stack_top); + } + + return retval; + } + + /** + * Reads a switch statement from input + */ + bool Switch() { + bool retval = false; + + size_t prev_stack_top = m_match_stack.size(); + + if (Keyword("switch")) { + retval = true; + + if (!Char('(')) { + throw exception::eval_error("Incomplete 'switch' expression", File_Position(m_line, m_col), *m_filename); + } + + if (!(Operator() && Char(')'))) { + throw exception::eval_error("Incomplete 'switch' expression", File_Position(m_line, m_col), *m_filename); + } + + while (Eol()) {} + + if (Char('{')) { + retval = true; + + while (Eol()) {} + + while (Case()) { + while (Eol()); + } + + while (Eol()); + + if (!Char('}')) { + throw exception::eval_error("Incomplete block", File_Position(m_line, m_col), *m_filename); + } + } + else { + throw exception::eval_error("Incomplete block", File_Position(m_line, m_col), *m_filename); + } + + build_match(AST_NodePtr(new eval::Switch_AST_Node()), prev_stack_top); + } + + return retval; + } + /** * Reads a curly-brace C-style block from input */ @@ -1928,6 +2014,14 @@ namespace chaiscript retval = true; saw_eol = true; } + else if (Switch()) { + if (!saw_eol) { + throw exception::eval_error("Two function definitions missing line separator", File_Position(prev_line, prev_col), *m_filename); + } + has_more = true; + retval = true; + saw_eol = true; + } else if (Return()) { if (!saw_eol) { throw exception::eval_error("Two expressions missing line separator", File_Position(prev_line, prev_col), *m_filename); diff --git a/unittests/switch_break.chai b/unittests/switch_break.chai new file mode 100644 index 0000000..8d36275 --- /dev/null +++ b/unittests/switch_break.chai @@ -0,0 +1,22 @@ +var total = 0; + +switch(2) { + case (1) { + total += 1; + break; + } + case (2) { + total += 2; + break; + } + case (3) { + total += 4; + break; + } + case (4) { + total += 8; + break; + } +} + +assert_equal(total, 2) diff --git a/unittests/switch_default.chai b/unittests/switch_default.chai new file mode 100644 index 0000000..8c48aa5 --- /dev/null +++ b/unittests/switch_default.chai @@ -0,0 +1,18 @@ +var total = 0; + +switch(2) { + case (1) { + total += 1; + } + case (3) { + total += 4; + } + case (4) { + total += 8; + } + default { + total += 16; + } +} + +assert_equal(total, 16) diff --git a/unittests/switch_fallthru.chai b/unittests/switch_fallthru.chai new file mode 100644 index 0000000..627b649 --- /dev/null +++ b/unittests/switch_fallthru.chai @@ -0,0 +1,18 @@ +var total = 0; + +switch(2) { + case (1) { + total += 1; + } + case (2) { + total += 2; + } + case (3) { + total += 4; + } + case (4) { + total += 8; + } +} + +assert_equal(total, 14); diff --git a/unittests/switch_fallthru_and_break.chai b/unittests/switch_fallthru_and_break.chai new file mode 100644 index 0000000..3c93071 --- /dev/null +++ b/unittests/switch_fallthru_and_break.chai @@ -0,0 +1,19 @@ +var total = 0; + +switch(2) { + case (1) { + total += 1; + } + case (2) { + total += 2; + } + case (3) { + total += 4; + break; + } + case (4) { + total += 8; + } +} + +assert_equal(total, 6)