diff --git a/include/chaiscript/dispatchkit/dispatchkit.hpp b/include/chaiscript/dispatchkit/dispatchkit.hpp index 72a38fb..8c4a7f5 100644 --- a/include/chaiscript/dispatchkit/dispatchkit.hpp +++ b/include/chaiscript/dispatchkit/dispatchkit.hpp @@ -736,6 +736,19 @@ namespace chaiscript return functions.find(name) != functions.end(); } + /// \returns All values in the local thread state in the parent scope, or if it doesn't exist, + /// the current scope. + std::map get_parent_locals() const + { + StackData &stack = get_stack_data(); + if (stack.size() > 1) + { + return stack[1]; + } else { + return stack[0]; + } + } + /// \returns All values in the local thread state, added through the add() function std::map get_locals() const { diff --git a/include/chaiscript/language/chaiscript_common.hpp b/include/chaiscript/language/chaiscript_common.hpp index 63fe16c..ec006d0 100644 --- a/include/chaiscript/language/chaiscript_common.hpp +++ b/include/chaiscript/language/chaiscript_common.hpp @@ -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, Continue, 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, Reference, Switch, Case, Default, Ternary_Cond, Noop + Logical_And, Logical_Or, Reference, Switch, Case, Default, Ternary_Cond, Noop, Class }; }; @@ -510,6 +510,9 @@ namespace chaiscript /// Creates a new scope then pops it on destruction struct Scope_Push_Pop { + Scope_Push_Pop(const Scope_Push_Pop &) = delete; + Scope_Push_Pop& operator=(const Scope_Push_Pop &) = delete; + Scope_Push_Pop(chaiscript::detail::Dispatch_Engine &t_de) : m_de(t_de) { @@ -523,9 +526,6 @@ namespace chaiscript private: - // explicitly unimplemented copy and assignment - Scope_Push_Pop(const Scope_Push_Pop &); - Scope_Push_Pop& operator=(const Scope_Push_Pop &); chaiscript::detail::Dispatch_Engine &m_de; }; @@ -533,6 +533,9 @@ namespace chaiscript /// Creates a new function call and pops it on destruction struct Function_Push_Pop { + Function_Push_Pop(const Function_Push_Pop &) = delete; + Function_Push_Pop& operator=(const Function_Push_Pop &) = delete; + Function_Push_Pop(chaiscript::detail::Dispatch_Engine &t_de) : m_de(t_de) { @@ -551,9 +554,6 @@ namespace chaiscript private: - // explicitly unimplemented copy and assignment - Function_Push_Pop(const Function_Push_Pop &); - Function_Push_Pop& operator=(const Function_Push_Pop &); chaiscript::detail::Dispatch_Engine &m_de; }; @@ -561,6 +561,9 @@ namespace chaiscript /// Creates a new scope then pops it on destruction struct Stack_Push_Pop { + Stack_Push_Pop(const Stack_Push_Pop &) = delete; + Stack_Push_Pop& operator=(const Stack_Push_Pop &) = delete; + Stack_Push_Pop(chaiscript::detail::Dispatch_Engine &t_de) : m_de(t_de) { @@ -574,9 +577,6 @@ namespace chaiscript private: - // explicitly unimplemented copy and assignment - Stack_Push_Pop(const Stack_Push_Pop &); - Stack_Push_Pop& operator=(const Stack_Push_Pop &); chaiscript::detail::Dispatch_Engine &m_de; }; diff --git a/include/chaiscript/language/chaiscript_engine.hpp b/include/chaiscript/language/chaiscript_engine.hpp index 81b6cb8..0513194 100644 --- a/include/chaiscript/language/chaiscript_engine.hpp +++ b/include/chaiscript/language/chaiscript_engine.hpp @@ -330,6 +330,7 @@ namespace chaiscript m_engine.add_reserved_word("break"); m_engine.add_reserved_word("true"); m_engine.add_reserved_word("false"); + m_engine.add_reserved_word("class"); m_engine.add_reserved_word("_"); if (t_lib) diff --git a/include/chaiscript/language/chaiscript_eval.hpp b/include/chaiscript/language/chaiscript_eval.hpp index 56a20d9..f2e2391 100644 --- a/include/chaiscript/language/chaiscript_eval.hpp +++ b/include/chaiscript/language/chaiscript_eval.hpp @@ -856,7 +856,23 @@ namespace chaiscript return Boxed_Value(); } + }; + struct Class_AST_Node : public AST_Node { + public: + Class_AST_Node(const std::string &t_ast_node_text = "", const std::shared_ptr &t_fname=std::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, AST_Node_Type::Class, t_fname, t_start_line, t_start_col, t_end_line, t_end_col) { } + virtual ~Class_AST_Node() {} + virtual Boxed_Value eval_internal(chaiscript::detail::Dispatch_Engine &t_ss) CHAISCRIPT_OVERRIDE { + chaiscript::eval::detail::Scope_Push_Pop spp(t_ss); + + // 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(this->children[0]->text)); + + this->children[1]->eval(t_ss); + + return Boxed_Value(); + } }; struct Ternary_Cond_AST_Node : public AST_Node { @@ -1400,23 +1416,29 @@ namespace chaiscript std::vector t_param_names; AST_NodePtr guardnode; + auto d = t_ss.get_parent_locals(); + auto itr = d.find("_current_class_name"); + int class_offset = 0; + if (itr != d.end()) class_offset = -1; + const std::string & class_name = (itr != d.end())?std::string(boxed_cast(itr->second)):this->children[0]->text; + //The first param of a method is always the implied this ptr. t_param_names.push_back("this"); - if ((this->children.size() > 3) && (this->children[2]->identifier == AST_Node_Type::Arg_List)) { - for (size_t i = 0; i < this->children[2]->children.size(); ++i) { - t_param_names.push_back(this->children[2]->children[i]->text); + if ((this->children.size() > (3 + class_offset)) && (this->children[(2 + class_offset)]->identifier == AST_Node_Type::Arg_List)) { + for (size_t i = 0; i < this->children[(2 + class_offset)]->children.size(); ++i) { + t_param_names.push_back(this->children[(2 + class_offset)]->children[i]->text); } - if (this->children.size() > 4) { - guardnode = this->children[3]; + if (this->children.size() > (4 + class_offset)) { + guardnode = this->children[(3 + class_offset)]; } } else { //no parameters - if (this->children.size() > 3) { - guardnode = this->children[2]; + if (this->children.size() > (3 + class_offset)) { + guardnode = this->children[(2 + class_offset)]; } } @@ -1432,8 +1454,9 @@ namespace chaiscript try { const std::string & l_annotation = this->annotation?this->annotation->text:""; - const std::string & class_name = this->children[0]->text; - const std::string & function_name = this->children[1]->text; + + const std::string & function_name = this->children[(1 + class_offset)]->text; + if (function_name == class_name) { t_ss.add(Proxy_Function (new dispatch::detail::Dynamic_Object_Constructor(class_name, Proxy_Function @@ -1478,22 +1501,28 @@ namespace chaiscript Attr_Decl_AST_Node(const std::string &t_ast_node_text = "", const std::shared_ptr &t_fname=std::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, AST_Node_Type::Attr_Decl, t_fname, t_start_line, t_start_col, t_end_line, t_end_col) { } virtual ~Attr_Decl_AST_Node() {} - virtual Boxed_Value eval_internal(chaiscript::detail::Dispatch_Engine &t_ss) CHAISCRIPT_OVERRIDE{ - try { + virtual Boxed_Value eval_internal(chaiscript::detail::Dispatch_Engine &t_ss) CHAISCRIPT_OVERRIDE + { + const auto &d = t_ss.get_parent_locals(); + const auto itr = d.find("_current_class_name"); + int class_offset = 0; + if (itr != d.end()) class_offset = -1; + std::string class_name = (itr != d.end())?std::string(boxed_cast(itr->second)):this->children[0]->text; + try { t_ss.add(Proxy_Function (new dispatch::detail::Dynamic_Object_Function( - this->children[0]->text, + class_name, fun(std::function(std::bind(&dispatch::Dynamic_Object::get_attr, std::placeholders::_1, - this->children[1]->text + this->children[(1 + class_offset)]->text ))) ) - ), this->children[1]->text); + ), this->children[(1 + class_offset)]->text); } catch (const exception::reserved_word_error &) { - throw exception::eval_error("Reserved word used as attribute '" + this->children[1]->text + "'"); + throw exception::eval_error("Reserved word used as attribute '" + this->children[(1 + class_offset)]->text + "'"); } catch (const exception::name_conflict_error &e) { throw exception::eval_error("Attribute redefined '" + e.name() + "'"); } diff --git a/include/chaiscript/language/chaiscript_parser.hpp b/include/chaiscript/language/chaiscript_parser.hpp index 06d53fa..6efd8d0 100644 --- a/include/chaiscript/language/chaiscript_parser.hpp +++ b/include/chaiscript/language/chaiscript_parser.hpp @@ -1358,7 +1358,7 @@ namespace chaiscript /** * Reads a function definition from input */ - bool Def() { + bool Def(bool t_class_context = false) { bool retval = false; bool is_annotated = false; AST_NodePtr annotation; @@ -1410,7 +1410,7 @@ namespace chaiscript throw exception::eval_error("Incomplete function definition", File_Position(m_line, m_col), *m_filename); } - if (is_method) { + if (is_method || t_class_context) { build_match(AST_NodePtr(new eval::Method_AST_Node()), prev_stack_top); } else { @@ -1555,6 +1555,35 @@ namespace chaiscript return retval; } + /** + * Reads a class block from input + */ + bool Class() { + bool retval = false; + + size_t prev_stack_top = m_match_stack.size(); + + if (Keyword("class")) { + retval = true; + + if (!Id(true)) { + throw exception::eval_error("Missing class name in definition", File_Position(m_line, m_col), *m_filename); + } + + + while (Eol()) {} + + if (!Class_Block()) { + throw exception::eval_error("Incomplete 'class' block", File_Position(m_line, m_col), *m_filename); + } + + build_match(AST_NodePtr(new eval::Class_AST_Node()), prev_stack_top); + } + + return retval; + } + + /** * Reads a while block from input */ @@ -1737,6 +1766,28 @@ namespace chaiscript } + /** + * Reads a curly-brace C-style class block from input + */ + bool Class_Block() { + bool retval = false; + + size_t prev_stack_top = m_match_stack.size(); + + if (Char('{')) { + retval = true; + + Class_Statements(); + if (!Char('}')) { + throw exception::eval_error("Incomplete class block", File_Position(m_line, m_col), *m_filename); + } + + build_match(AST_NodePtr(new eval::Block_AST_Node()), prev_stack_top); + } + + return retval; + } + /** * Reads a curly-brace C-style block from input */ @@ -1875,12 +1926,20 @@ namespace chaiscript /** * Reads a variable declaration from input */ - bool Var_Decl() { + bool Var_Decl(bool t_class_context = false) { bool retval = false; size_t prev_stack_top = m_match_stack.size(); - if (Keyword("auto") || Keyword("var")) { + if (t_class_context && (Keyword("attr") || Keyword("auto") || Keyword("var"))) { + retval = true; + + if (!Id(true)) { + throw exception::eval_error("Incomplete attribute declaration", File_Position(m_line, m_col), *m_filename); + } + + build_match(AST_NodePtr(new eval::Attr_Decl_AST_Node()), prev_stack_top); + } else if (Keyword("auto") || Keyword("var")) { retval = true; if (!(Reference() || Id(true))) { @@ -1888,8 +1947,7 @@ namespace chaiscript } build_match(AST_NodePtr(new eval::Var_Decl_AST_Node()), prev_stack_top); - } - else if (Keyword("attr")) { + } else if (Keyword("attr")) { retval = true; if (!Id(true)) { @@ -2262,6 +2320,44 @@ namespace chaiscript return retval; } + /** + * Parses statements allowed inside of a class block + */ + bool Class_Statements() { + bool retval = false; + + bool has_more = true; + bool saw_eol = true; + + while (has_more) { + int prev_line = m_line; + int prev_col = m_col; + if (Def(true)) { + 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 (Var_Decl(true)) { + 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 (Eol()) { + has_more = true; + retval = true; + saw_eol = true; + } else { + has_more = false; + } + } + + return retval; + } + /** * Top level parser, starts parsing of all known parses */ @@ -2306,6 +2402,14 @@ namespace chaiscript retval = true; saw_eol = true; } + else if (Class()) { + 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 (For()) { if (!saw_eol) { throw exception::eval_error("Two function definitions missing line separator", File_Position(prev_line, prev_col), *m_filename); diff --git a/unittests/class.chai b/unittests/class.chai new file mode 100644 index 0000000..8936436 --- /dev/null +++ b/unittests/class.chai @@ -0,0 +1,27 @@ + +class Vector3 +{ + // you can use attr, auto or var in this context + attr x + auto y + var z + + def Vector3(x,y,z) + { + this.x = x + this.y = y + this.z = z + } + + def doSomething(mult) + { + return this.x * this.y * this.z * mult + } + +} + + +auto v = Vector3(1,2,3) +assert_equal(1, v.x) +assert_equal(v.doSomething(2), 12) +