Skip to content
Prev Previous commit
Next Next commit
Address review: resolve merge conflicts with upstream namespace support
Merge upstream/develop (including namespace support from #552, WASM
support from #678, and grammar diagrams from #628) and adapt enum
implementation for compatibility with the :: dot-access semantics.

Enum values are now stored as attributes on a container Dynamic_Object,
aligning with how namespaces store members. The from_int() factory
method replaces direct constructor syntax since a name cannot be both
a global object (for :: access) and a callable constructor.

Requested by @lefticus in PR #679 review.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
  • Loading branch information
leftibot and claude committed Apr 14, 2026
commit 2c807d8c4af64efd949b81ab74f7b776a9830575
5 changes: 3 additions & 2 deletions include/chaiscript/language/chaiscript_common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ namespace chaiscript {
Compiled,
Const_Var_Decl,
Const_Assign_Decl,
Enum
Enum,
Namespace_Block
};

enum class Operator_Precedence {
Expand All @@ -128,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", "Enum"};
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", "Namespace_Block"};

return ast_node_types[static_cast<int>(ast_node_type)];
}
Expand Down
107 changes: 95 additions & 12 deletions include/chaiscript/language/chaiscript_eval.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,7 @@ namespace chaiscript {
Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override {
const auto &enum_name = this->children[0]->text;

dispatch::Dynamic_Object container(enum_name);
std::vector<int> valid_values;

for (size_t i = 1; i < this->children.size(); i += 2) {
Expand All @@ -910,22 +911,23 @@ namespace chaiscript {
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);
container[val_name] = const_var(dobj);
}

auto shared_valid = std::make_shared<const std::vector<int>>(std::move(valid_values));

t_ss->add(
std::make_shared<dispatch::detail::Dynamic_Object_Constructor>(
enum_name,
fun([shared_valid, enum_name](dispatch::Dynamic_Object &t_obj, int t_val) {
if (std::find(shared_valid->begin(), shared_valid->end(), t_val) == shared_valid->end()) {
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);
container["from_int"] = var(
fun([shared_valid, enum_name](int t_val) -> Boxed_Value {
if (std::find(shared_valid->begin(), shared_valid->end(), t_val) == shared_valid->end()) {
throw exception::eval_error("Value " + std::to_string(t_val) + " is not valid for enum '" + enum_name + "'");
}
dispatch::Dynamic_Object dobj(enum_name);
dobj.get_attr("value") = Boxed_Value(t_val);
dobj.set_explicit(true);
return const_var(dobj);
}));

t_ss->add_global_const(const_var(container), enum_name);

t_ss->add(
std::make_shared<dispatch::detail::Dynamic_Object_Function>(
Expand Down Expand Up @@ -953,6 +955,87 @@ namespace chaiscript {
}
};

template<typename T>
struct Namespace_Block_AST_Node final : AST_Node_Impl<T> {
Namespace_Block_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::Namespace_Block, std::move(t_loc), std::move(t_children)) {
}

Boxed_Value eval_internal(const chaiscript::detail::Dispatch_State &t_ss) const override {
const auto &ns_name = this->children[0]->text;

auto ns_name_bv = const_var(ns_name);
t_ss->call_function("namespace", m_ns_loc, Function_Params{ns_name_bv}, t_ss.conversions());

std::vector<std::string> parts;
{
std::string::size_type start = 0;
std::string::size_type pos = 0;
while ((pos = ns_name.find("::", start)) != std::string::npos) {
parts.push_back(ns_name.substr(start, pos - start));
start = pos + 2;
}
parts.push_back(ns_name.substr(start));
}

Boxed_Value ns_bv = t_ss.get_object(parts[0], m_root_loc);

for (size_t i = 1; i < parts.size(); ++i) {
auto &parent_ns = boxed_cast<dispatch::Dynamic_Object &>(ns_bv);
ns_bv = parent_ns.get_attr(parts[i]);
}

auto &target_ns = boxed_cast<dispatch::Dynamic_Object &>(ns_bv);

const auto process_statement = [&](const AST_Node_Impl<T> &stmt) {
if (stmt.identifier == AST_Node_Type::Def) {
const auto &def_node = static_cast<const Def_AST_Node<T> &>(stmt);
target_ns[def_node.children[0]->text] =
Boxed_Value(Def_AST_Node<T>::make_proxy_function(def_node, t_ss));
} else if (stmt.identifier == AST_Node_Type::Assign_Decl
|| stmt.identifier == AST_Node_Type::Const_Assign_Decl) {
const auto &var_name = stmt.children[0]->text;
auto value = detail::clone_if_necessary(stmt.children[1]->eval(t_ss), m_clone_loc, t_ss);
value.reset_return_value();
if (stmt.identifier == AST_Node_Type::Const_Assign_Decl) {
value.make_const();
}
target_ns[var_name] = std::move(value);
} else if (stmt.identifier == AST_Node_Type::Equation
&& !stmt.children.empty()
&& (stmt.children[0]->identifier == AST_Node_Type::Var_Decl
|| stmt.children[0]->identifier == AST_Node_Type::Const_Var_Decl)) {
const auto &var_name = stmt.children[0]->children[0]->text;
auto value = detail::clone_if_necessary(stmt.children[1]->eval(t_ss), m_clone_loc, t_ss);
value.reset_return_value();
target_ns[var_name] = std::move(value);
} else if (stmt.identifier == AST_Node_Type::Var_Decl) {
const auto &var_name = stmt.children[0]->text;
target_ns[var_name] = Boxed_Value();
} else {
throw exception::eval_error("Only declarations (def, var, auto, global) are allowed inside namespace blocks");
}
};

const auto &body = this->children[1];
if (body->identifier == AST_Node_Type::Block
|| body->identifier == AST_Node_Type::Scopeless_Block) {
for (const auto &child : body->children) {
process_statement(*child);
}
} else {
process_statement(*body);
}

return void_var();
}

private:
mutable std::atomic_uint_fast32_t m_ns_loc = {0};
mutable std::atomic_uint_fast32_t m_root_loc = {0};
mutable std::atomic_uint_fast32_t m_clone_loc = {0};
};

template<typename T>
struct If_AST_Node final : AST_Node_Impl<T> {
If_AST_Node(std::string t_ast_node_text, Parse_Location t_loc, std::vector<AST_Node_Impl_Ptr<T>> t_children)
Expand Down
17 changes: 2 additions & 15 deletions include/chaiscript/language/chaiscript_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2488,20 +2488,7 @@ namespace chaiscript {
}

build_match<eval::Array_Call_AST_Node<Tracer>>(prev_stack_top);
} else if (!m_match_stack.empty() && m_match_stack.back()->identifier == AST_Node_Type::Id && Symbol("::")) {
has_more = true;
const auto enum_name = m_match_stack.back()->text;
const auto start_loc = m_match_stack.back()->location;
m_match_stack.pop_back();

if (!Id(true)) {
throw exception::eval_error("Expected identifier after '::'", File_Position(m_position.line, m_position.col), *m_filename);
}

const auto val_name = m_match_stack.back()->text;
m_match_stack.pop_back();
m_match_stack.push_back(make_node<eval::Id_AST_Node<Tracer>>(enum_name + "::" + val_name, start_loc.start.line, start_loc.start.column));
} else if (Symbol(".")) {
} else if (Symbol(".") || Symbol("::")) {
has_more = true;
if (!(Id(true))) {
throw exception::eval_error("Incomplete dot access fun call", File_Position(m_position.line, m_position.col), *m_filename);
Expand Down Expand Up @@ -2898,7 +2885,7 @@ namespace chaiscript {

while (has_more) {
const auto start = m_position;
if (Def() || Try() || If() || While() || Class(t_class_allowed) || Enum(t_class_allowed) || For() || Switch()) {
if (Def() || Try() || If() || While() || Namespace_Block() || 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),
Expand Down
8 changes: 4 additions & 4 deletions unittests/enum.chai
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ assert_true(Color::Red != Color::Green)
assert_false(Color::Red != Color::Red)

// Construction from valid int
auto c = Color(1)
auto c = Color::from_int(1)
assert_true(c == Color::Green)

// Construction from invalid int throws
try {
Color(52)
Color::from_int(52)
assert_true(false)
} catch(e) {
// expected
Expand All @@ -28,7 +28,7 @@ try {
def takes_color(Color val) { val }
takes_color(Color::Red)
takes_color(Color::Green)
takes_color(Color(2))
takes_color(Color::from_int(2))

// Cannot pass int where Color is expected
try {
Expand All @@ -49,7 +49,7 @@ assert_equal(10, Priority::Low.to_underlying())
assert_equal(20, Priority::Medium.to_underlying())
assert_equal(30, Priority::High.to_underlying())

auto p = Priority(20)
auto p = Priority::from_int(20)
assert_true(p == Priority::Medium)

// Mixed auto and explicit values
Expand Down
Loading
You are viewing a condensed version of this merge commit. You can view the full changes here.