diff --git a/include/chaiscript/language/chaiscript_common.hpp b/include/chaiscript/language/chaiscript_common.hpp index fb75a933..81a2b810 100644 --- a/include/chaiscript/language/chaiscript_common.hpp +++ b/include/chaiscript/language/chaiscript_common.hpp @@ -33,7 +33,7 @@ namespace chaiscript { template static bool is_reserved_word(const T &s) noexcept { const static std::unordered_set - words{utility::hash("def"), utility::hash("fun"), utility::hash("while"), utility::hash("for"), utility::hash("if"), utility::hash("else"), utility::hash("&&"), utility::hash("||"), utility::hash(","), utility::hash("auto"), utility::hash("return"), utility::hash("break"), utility::hash("true"), utility::hash("false"), utility::hash("class"), utility::hash("attr"), utility::hash("var"), utility::hash("global"), utility::hash("GLOBAL"), utility::hash("_"), utility::hash("__LINE__"), utility::hash("__FILE__"), utility::hash("__FUNC__"), utility::hash("__CLASS__"), utility::hash("const")}; + words{utility::hash("def"), utility::hash("fun"), utility::hash("while"), utility::hash("for"), utility::hash("if"), utility::hash("else"), utility::hash("&&"), utility::hash("||"), utility::hash(","), utility::hash("auto"), utility::hash("return"), utility::hash("break"), utility::hash("true"), utility::hash("false"), utility::hash("class"), utility::hash("attr"), utility::hash("var"), utility::hash("global"), utility::hash("GLOBAL"), utility::hash("_"), utility::hash("__LINE__"), utility::hash("__FILE__"), utility::hash("__FUNC__"), utility::hash("__CLASS__"), utility::hash("const"), utility::hash("enum")}; return words.count(utility::hash(s)) == 1; } @@ -106,7 +106,9 @@ namespace chaiscript { Constant, Compiled, Const_Var_Decl, - Const_Assign_Decl + Const_Assign_Decl, + Enum, + Enum_Access }; enum class Operator_Precedence { @@ -127,7 +129,7 @@ namespace chaiscript { namespace { /// Helper lookup to get the name of each node type constexpr const char *ast_node_type_to_string(AST_Node_Type ast_node_type) noexcept { - constexpr const char *const ast_node_types[] = {"Id", "Fun_Call", "Unused_Return_Fun_Call", "Arg_List", "Equation", "Var_Decl", "Assign_Decl", "Array_Call", "Dot_Access", "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", "Noop", "Class", "Binary", "Arg", "Global_Decl", "Constant", "Compiled", "Const_Var_Decl", "Const_Assign_Decl"}; + constexpr const char *const ast_node_types[] = {"Id", "Fun_Call", "Unused_Return_Fun_Call", "Arg_List", "Equation", "Var_Decl", "Assign_Decl", "Array_Call", "Dot_Access", "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", "Noop", "Class", "Binary", "Arg", "Global_Decl", "Constant", "Compiled", "Const_Var_Decl", "Const_Assign_Decl", "Enum", "Enum_Access"}; return ast_node_types[static_cast(ast_node_type)]; } diff --git a/include/chaiscript/language/chaiscript_eval.hpp b/include/chaiscript/language/chaiscript_eval.hpp index 03189fb4..9159af8a 100644 --- a/include/chaiscript/language/chaiscript_eval.hpp +++ b/include/chaiscript/language/chaiscript_eval.hpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -887,6 +888,88 @@ namespace chaiscript { } }; + 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; + + std::set valid_values; + + for (size_t i = 1; i < this->children.size(); i += 2) { + const auto &val_name = this->children[i]->text; + const int val_int = Boxed_Number(this->children[i + 1]->eval(t_ss)).get_as(); + valid_values.insert(val_int); + + dispatch::Dynamic_Object dobj(enum_name); + dobj.get_attr("value") = Boxed_Value(val_int); + dobj.set_explicit(true); + t_ss->add_global_const(const_var(dobj), enum_name + "::" + val_name); + } + + auto shared_valid = std::make_shared>(std::move(valid_values)); + + t_ss->add( + std::make_shared( + enum_name, + fun([shared_valid, enum_name](dispatch::Dynamic_Object &t_obj, int t_val) { + if (shared_valid->count(t_val) == 0) { + throw exception::eval_error("Value " + std::to_string(t_val) + " is not valid for enum '" + enum_name + "'"); + } + t_obj.get_attr("value") = Boxed_Value(t_val); + t_obj.set_explicit(true); + })), + enum_name); + + t_ss->add( + std::make_shared( + enum_name, + fun([](const dispatch::Dynamic_Object &lhs, const dispatch::Dynamic_Object &rhs) { + return Boxed_Number(lhs.get_attr("value")).get_as() == Boxed_Number(rhs.get_attr("value")).get_as(); + })), + "=="); + + t_ss->add( + std::make_shared( + enum_name, + fun([](const dispatch::Dynamic_Object &lhs, const dispatch::Dynamic_Object &rhs) { + return Boxed_Number(lhs.get_attr("value")).get_as() != Boxed_Number(rhs.get_attr("value")).get_as(); + })), + "!="); + + t_ss->add( + std::make_shared( + enum_name, + fun([](const dispatch::Dynamic_Object &obj) { return Boxed_Number(obj.get_attr("value")).get_as(); })), + "to_int"); + + return void_var(); + } + }; + + template + struct Enum_Access_AST_Node final : AST_Node_Impl { + Enum_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::Enum_Access, std::move(t_loc), std::move(t_children)) + , m_key(this->children[0]->text + "::" + this->children[1]->text) { + } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + try { + return t_ss.get_object(m_key, m_loc); + } catch (std::exception &) { + throw exception::eval_error("Can not find enum value: " + m_key); + } + } + + private: + const std::string m_key; + mutable std::atomic_uint_fast32_t m_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) diff --git a/include/chaiscript/language/chaiscript_parser.hpp b/include/chaiscript/language/chaiscript_parser.hpp index be2a7525..e4e08b63 100644 --- a/include/chaiscript/language/chaiscript_parser.hpp +++ b/include/chaiscript/language/chaiscript_parser.hpp @@ -2031,6 +2031,77 @@ namespace chaiscript { return retval; } + bool Enum(const bool t_allowed) { + Depth_Counter dc{this}; + bool retval = false; + + const auto prev_stack_top = m_match_stack.size(); + + if (Keyword("enum")) { + if (!t_allowed) { + throw exception::eval_error("Enum definitions only allowed at top scope", + File_Position(m_position.line, m_position.col), + *m_filename); + } + + retval = true; + + if (!Id(true)) { + throw exception::eval_error("Missing enum name in definition", File_Position(m_position.line, m_position.col), *m_filename); + } + + if (!Char('{')) { + throw exception::eval_error("Expected '{' after enum name", File_Position(m_position.line, m_position.col), *m_filename); + } + + int next_value = 0; + + while (Eol()) { + } + + if (!Char('}')) { + do { + while (Eol()) { + } + + if (!Id(true)) { + throw exception::eval_error("Expected enum value name", File_Position(m_position.line, m_position.col), *m_filename); + } + + if (Symbol("=")) { + if (!Num()) { + throw exception::eval_error("Expected integer after '=' in enum definition", + File_Position(m_position.line, m_position.col), + *m_filename); + } + next_value = static_cast(std::stoi(m_match_stack.back()->text)); + m_match_stack.pop_back(); + } + + m_match_stack.push_back( + make_node>(std::to_string(next_value), m_position.line, m_position.col, const_var(next_value))); + ++next_value; + + while (Eol()) { + } + } while (Char(',') && !Char('}')); + + while (Eol()) { + } + + if (!Char('}')) { + throw exception::eval_error("Expected '}' to close enum definition", + File_Position(m_position.line, m_position.col), + *m_filename); + } + } + + build_match>(prev_stack_top); + } + + return retval; + } + /// Reads a while block from input bool While() { Depth_Counter dc{this}; @@ -2379,6 +2450,16 @@ namespace chaiscript { } build_match>(prev_stack_top); + } else if (!m_match_stack.empty() && m_match_stack.back()->identifier == AST_Node_Type::Id && Symbol("::")) { + has_more = true; + if (!Id(true)) { + throw exception::eval_error("Expected identifier after '::'", File_Position(m_position.line, m_position.col), *m_filename); + } + + if (std::distance(m_match_stack.begin() + static_cast(prev_stack_top), m_match_stack.end()) != 2) { + throw exception::eval_error("Incomplete enum access", File_Position(m_position.line, m_position.col), *m_filename); + } + build_match>(prev_stack_top); } else if (Symbol(".")) { has_more = true; if (!(Id(true))) { @@ -2776,7 +2857,7 @@ namespace chaiscript { while (has_more) { const auto start = m_position; - if (Def() || Try() || If() || While() || Class(t_class_allowed) || For() || Switch()) { + if (Def() || Try() || If() || While() || Class(t_class_allowed) || Enum(t_class_allowed) || For() || Switch()) { if (!saw_eol) { throw exception::eval_error("Two function definitions missing line separator", File_Position(start.line, start.col), diff --git a/unittests/enum.chai b/unittests/enum.chai new file mode 100644 index 00000000..eb1dfd38 --- /dev/null +++ b/unittests/enum.chai @@ -0,0 +1,59 @@ +// Basic enum definition +enum Color { Red, Green, Blue } + +// Access via :: syntax +auto r = Color::Red +auto g = Color::Green +auto b = Color::Blue + +// Equality and inequality +assert_true(Color::Red == Color::Red) +assert_false(Color::Red == Color::Green) +assert_true(Color::Red != Color::Green) +assert_false(Color::Red != Color::Red) + +// Construction from valid int +auto c = Color(1) +assert_true(c == Color::Green) + +// Construction from invalid int throws +try { + Color(52) + assert_true(false) +} catch(e) { + // expected +} + +// Strong typing: function with typed parameter +def takes_color(Color val) { val } +takes_color(Color::Red) +takes_color(Color::Green) +takes_color(Color(2)) + +// Cannot pass int where Color is expected +try { + takes_color(52) + assert_true(false) +} catch(e) { + // expected: dispatch error +} + +// to_int accessor +assert_equal(0, Color::Red.to_int()) +assert_equal(1, Color::Green.to_int()) +assert_equal(2, Color::Blue.to_int()) + +// Enum with explicit values +enum Priority { Low = 10, Medium = 20, High = 30 } +assert_equal(10, Priority::Low.to_int()) +assert_equal(20, Priority::Medium.to_int()) +assert_equal(30, Priority::High.to_int()) + +auto p = Priority(20) +assert_true(p == Priority::Medium) + +// Mixed auto and explicit values +enum Status { Pending, Active = 5, Done } +assert_equal(0, Status::Pending.to_int()) +assert_equal(5, Status::Active.to_int()) +assert_equal(6, Status::Done.to_int())