diff --git a/include/chaiscript/dispatchkit/boxed_value.hpp b/include/chaiscript/dispatchkit/boxed_value.hpp index 930f8766..89a0ed2f 100644 --- a/include/chaiscript/dispatchkit/boxed_value.hpp +++ b/include/chaiscript/dispatchkit/boxed_value.hpp @@ -157,6 +157,12 @@ namespace chaiscript { bool is_const() const noexcept { return m_data->m_type_info.is_const(); } + /// Mark this Boxed_Value as const (used for script-level const declarations) + void make_const() noexcept { + m_data->m_type_info.make_const(); + m_data->m_data_ptr = nullptr; + } + bool is_type(const Type_Info &ti) const noexcept { return m_data->m_type_info.bare_equal(ti); } template diff --git a/include/chaiscript/dispatchkit/type_info.hpp b/include/chaiscript/dispatchkit/type_info.hpp index 2f21ee58..c77e9fac 100644 --- a/include/chaiscript/dispatchkit/type_info.hpp +++ b/include/chaiscript/dispatchkit/type_info.hpp @@ -85,6 +85,8 @@ namespace chaiscript { constexpr const std::type_info *bare_type_info() const noexcept { return m_bare_type_info; } + void make_const() noexcept { m_flags |= (1 << is_const_flag); } + private: struct Unknown_Type { }; diff --git a/include/chaiscript/language/chaiscript_common.hpp b/include/chaiscript/language/chaiscript_common.hpp index 3b020c84..44116735 100644 --- a/include/chaiscript/language/chaiscript_common.hpp +++ b/include/chaiscript/language/chaiscript_common.hpp @@ -37,7 +37,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__")}; + 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")}; return words.count(utility::hash(s)) == 1; } @@ -108,7 +108,9 @@ namespace chaiscript { Arg, Global_Decl, Constant, - Compiled + Compiled, + Const_Var_Decl, + Const_Assign_Decl }; enum class Operator_Precedence { @@ -129,7 +131,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"}; + 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"}; 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 34eaba13..5679ac1d 100644 --- a/include/chaiscript/language/chaiscript_eval.hpp +++ b/include/chaiscript/language/chaiscript_eval.hpp @@ -550,6 +550,41 @@ namespace chaiscript { mutable std::atomic_uint_fast32_t m_loc = {0}; }; + template + struct Const_Var_Decl_AST_Node final : AST_Node_Impl { + Const_Var_Decl_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::Const_Var_Decl, std::move(t_loc), std::move(t_children)) { + } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &) const override { + throw exception::eval_error("const variables must be initialized at declaration"); + } + }; + + template + struct Const_Assign_Decl_AST_Node final : AST_Node_Impl { + Const_Assign_Decl_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::Const_Assign_Decl, std::move(t_loc), std::move(t_children)) { + } + + Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override { + const std::string &idname = this->children[0]->text; + + try { + Boxed_Value bv(detail::clone_if_necessary(this->children[1]->eval(t_ss), m_loc, t_ss)); + bv.reset_return_value(); + bv.make_const(); + t_ss.add_object(idname, bv); + return bv; + } catch (const exception::name_conflict_error &e) { + throw exception::eval_error("Variable redefined '" + e.name() + "'"); + } + } + + private: + mutable std::atomic_uint_fast32_t m_loc = {0}; + }; + template struct Array_Call_AST_Node final : AST_Node_Impl { Array_Call_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector> t_children) diff --git a/include/chaiscript/language/chaiscript_optimizer.hpp b/include/chaiscript/language/chaiscript_optimizer.hpp index d9f706b6..e8048eb3 100644 --- a/include/chaiscript/language/chaiscript_optimizer.hpp +++ b/include/chaiscript/language/chaiscript_optimizer.hpp @@ -89,6 +89,7 @@ namespace chaiscript { 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::Const_Var_Decl || node.identifier == AST_Node_Type::Const_Assign_Decl || node.identifier == AST_Node_Type::Reference) { return true; } @@ -202,6 +203,14 @@ namespace chaiscript { return chaiscript::make_unique, eval::Assign_Decl_AST_Node>(node->text, node->location, std::move(new_children)); + } else if ((node->identifier == AST_Node_Type::Equation) && node->text == "=" && node->children.size() == 2 + && node->children[0]->identifier == AST_Node_Type::Const_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::Const_Assign_Decl_AST_Node>(node->text, + node->location, + std::move(new_children)); } return node; diff --git a/include/chaiscript/language/chaiscript_parser.hpp b/include/chaiscript/language/chaiscript_parser.hpp index 06df8566..4ced5d73 100644 --- a/include/chaiscript/language/chaiscript_parser.hpp +++ b/include/chaiscript/language/chaiscript_parser.hpp @@ -2434,6 +2434,18 @@ namespace chaiscript { throw exception::eval_error("Incomplete variable declaration", File_Position(m_position.line, m_position.col), *m_filename); } + } else if (Keyword("const")) { + retval = true; + + // consume optional 'var' or 'auto' after 'const' + Keyword("var") || Keyword("auto"); + + if (Id(true)) { + build_match>(prev_stack_top); + } else { + throw exception::eval_error("Incomplete const variable declaration", File_Position(m_position.line, m_position.col), *m_filename); + } + } else if (Keyword("global")) { retval = true; diff --git a/unittests/const_var.chai b/unittests/const_var.chai new file mode 100644 index 00000000..e5a46c8c --- /dev/null +++ b/unittests/const_var.chai @@ -0,0 +1,21 @@ +// Test const variable declarations +const var x = 5 +assert_equal(5, x) + +// Test that const variables cannot be reassigned +assert_throws("Error: \"Error, cannot assign to constant value.\"", fun() { const var y = 10; y = 20 }) + +// Test const with auto keyword +const auto z = "hello" +assert_equal("hello", z) + +// Test that const auto variables cannot be reassigned +assert_throws("Error: \"Error, cannot assign to constant value.\"", fun() { const auto w = 3; w = 4 }) + +// Test is_var_const on const var +const var c = 42 +assert_true(c.is_var_const()) + +// Test that non-const var is not const +var nc = 42 +assert_false(nc.is_var_const())