mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2026-04-30 19:09:26 +08:00
* Fix #17: Add const local variable support to ChaiScript Adds `const var`, `const auto`, and `const` as variable declaration syntax that creates immutable local variables. A const_override flag on Boxed_Value enables script-level constness without changing the C++ type system integration. The parser, optimizer, and evaluator are extended with Const_Var_Decl and Const_Assign_Decl AST nodes that mirror their non-const counterparts but mark the value as const after initialization. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address review: remove const_override, set const flag directly on Type_Info Replace the m_const_override bool on Boxed_Value::Data with a Type_Info::make_const() method that sets the const bit in m_flags directly. This ensures constness is visible everywhere consistently, including places that check get_type_info().is_const() directly. Requested by @lefticus in PR #643 review. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: leftibot <leftibot@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7d3c29085d
commit
fc574c320b
@ -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<typename T>
|
||||
|
||||
@ -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 {
|
||||
};
|
||||
|
||||
@ -37,7 +37,7 @@ namespace chaiscript {
|
||||
template<typename T>
|
||||
static bool is_reserved_word(const T &s) noexcept {
|
||||
const static std::unordered_set<std::uint32_t>
|
||||
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<int>(ast_node_type)];
|
||||
}
|
||||
|
||||
@ -550,6 +550,41 @@ namespace chaiscript {
|
||||
mutable std::atomic_uint_fast32_t m_loc = {0};
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct Const_Var_Decl_AST_Node final : AST_Node_Impl<T> {
|
||||
Const_Var_Decl_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector<AST_Node_Impl_Ptr<T>> t_children)
|
||||
: AST_Node_Impl<T>(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<typename T>
|
||||
struct Const_Assign_Decl_AST_Node final : AST_Node_Impl<T> {
|
||||
Const_Assign_Decl_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector<AST_Node_Impl_Ptr<T>> t_children)
|
||||
: AST_Node_Impl<T>(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<typename T>
|
||||
struct Array_Call_AST_Node final : AST_Node_Impl<T> {
|
||||
Array_Call_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector<AST_Node_Impl_Ptr<T>> t_children)
|
||||
|
||||
@ -89,6 +89,7 @@ namespace chaiscript {
|
||||
template<typename T>
|
||||
bool contains_var_decl_in_scope(const eval::AST_Node_Impl<T> &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::AST_Node_Impl<T>, eval::Assign_Decl_AST_Node<T>>(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<eval::AST_Node_Impl_Ptr<T>> 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::AST_Node_Impl<T>, eval::Const_Assign_Decl_AST_Node<T>>(node->text,
|
||||
node->location,
|
||||
std::move(new_children));
|
||||
}
|
||||
|
||||
return node;
|
||||
|
||||
@ -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<eval::Const_Var_Decl_AST_Node<Tracer>>(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;
|
||||
|
||||
|
||||
21
unittests/const_var.chai
Normal file
21
unittests/const_var.chai
Normal file
@ -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())
|
||||
Loading…
x
Reference in New Issue
Block a user