mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2026-04-30 19:09:26 +08:00
Address review: C++-style namespace scoping and block declarations
Add :: scope resolution operator for member access (ns::func works
like ns.func). Add block namespace declarations:
namespace x::y { def func() { ... } }
Functions and variables declared inside a namespace block are added
as members of the namespace, accessible via :: or dot notation.
Namespaces can be reopened to add more members, matching C++ behavior.
Requested by @lefticus in PR #675 review.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
dd9afc832e
commit
8f463078f3
@ -106,7 +106,8 @@ namespace chaiscript {
|
||||
Constant,
|
||||
Compiled,
|
||||
Const_Var_Decl,
|
||||
Const_Assign_Decl
|
||||
Const_Assign_Decl,
|
||||
Namespace_Block
|
||||
};
|
||||
|
||||
enum class Operator_Precedence {
|
||||
@ -127,7 +128,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", "Namespace_Block"};
|
||||
|
||||
return ast_node_types[static_cast<int>(ast_node_type)];
|
||||
}
|
||||
|
||||
@ -189,7 +189,9 @@ namespace chaiscript {
|
||||
m_engine.add(fun([this](const Boxed_Value &t_bv, const std::string &t_name) { set_global(t_bv, t_name); }), "set_global");
|
||||
|
||||
m_engine.add(fun([this](const std::string &t_namespace_name) {
|
||||
register_namespace([](Namespace & /*space*/) noexcept {}, t_namespace_name);
|
||||
if (!m_namespace_generators.count(t_namespace_name)) {
|
||||
register_namespace([](Namespace & /*space*/) noexcept {}, t_namespace_name);
|
||||
}
|
||||
const auto sep_pos = t_namespace_name.find("::");
|
||||
const std::string root_name = (sep_pos != std::string::npos) ? t_namespace_name.substr(0, sep_pos) : t_namespace_name;
|
||||
if (!m_engine.get_scripting_objects().count(root_name)) {
|
||||
|
||||
@ -887,6 +887,118 @@ 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);
|
||||
|
||||
std::vector<std::string> param_names;
|
||||
size_t numparams = 0;
|
||||
dispatch::Param_Types param_types;
|
||||
|
||||
if ((def_node->children.size() > 1) && (def_node->children[1]->identifier == AST_Node_Type::Arg_List)) {
|
||||
numparams = def_node->children[1]->children.size();
|
||||
param_names = Arg_List_AST_Node<T>::get_arg_names(*def_node->children[1]);
|
||||
param_types = Arg_List_AST_Node<T>::get_arg_types(*def_node->children[1], t_ss);
|
||||
}
|
||||
|
||||
std::reference_wrapper<chaiscript::detail::Dispatch_Engine> engine(*t_ss);
|
||||
std::shared_ptr<dispatch::Proxy_Function_Base> guard;
|
||||
if (def_node->m_guard_node) {
|
||||
guard = dispatch::make_dynamic_proxy_function(
|
||||
[engine, guardnode = def_node->m_guard_node, param_names](const Function_Params &t_params) {
|
||||
return detail::eval_function(engine, *guardnode, param_names, t_params);
|
||||
},
|
||||
static_cast<int>(numparams),
|
||||
def_node->m_guard_node);
|
||||
}
|
||||
|
||||
const std::string &func_name = def_node->children[0]->text;
|
||||
auto proxy_func = dispatch::make_dynamic_proxy_function(
|
||||
[engine, func_node = def_node->m_body_node, param_names](const Function_Params &t_params) {
|
||||
return detail::eval_function(engine, *func_node, param_names, t_params);
|
||||
},
|
||||
static_cast<int>(numparams),
|
||||
def_node->m_body_node,
|
||||
param_types,
|
||||
guard);
|
||||
|
||||
target_ns[func_name] = Boxed_Value(std::move(proxy_func));
|
||||
} 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 {
|
||||
stmt.eval(t_ss);
|
||||
}
|
||||
};
|
||||
|
||||
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)
|
||||
|
||||
@ -1990,6 +1990,44 @@ namespace chaiscript {
|
||||
}
|
||||
|
||||
/// Reads a class block from input
|
||||
bool Namespace_Block() {
|
||||
Depth_Counter dc{this};
|
||||
const auto prev_stack_top = m_match_stack.size();
|
||||
const auto prev_pos = m_position;
|
||||
|
||||
if (Keyword("namespace")) {
|
||||
if (Id(true)) {
|
||||
std::string ns_name = m_match_stack.back()->text;
|
||||
|
||||
while (Symbol("::")) {
|
||||
if (!Id(true)) {
|
||||
throw exception::eval_error("Incomplete namespace name after '::'",
|
||||
File_Position(m_position.line, m_position.col),
|
||||
*m_filename);
|
||||
}
|
||||
ns_name += "::" + m_match_stack.back()->text;
|
||||
m_match_stack.pop_back();
|
||||
}
|
||||
|
||||
m_match_stack.back() = make_node<eval::Id_AST_Node<Tracer>>(ns_name, prev_pos.line, prev_pos.col);
|
||||
|
||||
while (Eol()) {
|
||||
}
|
||||
|
||||
if (Block()) {
|
||||
build_match<eval::Namespace_Block_AST_Node<Tracer>>(prev_stack_top);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
m_position = prev_pos;
|
||||
while (prev_stack_top != m_match_stack.size()) {
|
||||
m_match_stack.pop_back();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Class(const bool t_class_allowed) {
|
||||
Depth_Counter dc{this};
|
||||
bool retval = false;
|
||||
@ -2379,7 +2417,7 @@ namespace chaiscript {
|
||||
}
|
||||
|
||||
build_match<eval::Array_Call_AST_Node<Tracer>>(prev_stack_top);
|
||||
} 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);
|
||||
@ -2776,7 +2814,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() || Namespace_Block() || Class(t_class_allowed) || For() || Switch()) {
|
||||
if (!saw_eol) {
|
||||
throw exception::eval_error("Two function definitions missing line separator",
|
||||
File_Position(start.line, start.col),
|
||||
|
||||
@ -1802,6 +1802,10 @@ TEST_CASE("Nested namespaces via register_namespace with :: separator") {
|
||||
|
||||
CHECK(chai.eval<double>("constants.si.mu_B") == Approx(9.274));
|
||||
CHECK(chai.eval<double>("constants.mm.mu_B") == Approx(0.05788));
|
||||
|
||||
// Scope resolution via :: works the same as . for access
|
||||
CHECK(chai.eval<double>("constants::si::mu_B") == Approx(9.274));
|
||||
CHECK(chai.eval<double>("constants::mm::mu_B") == Approx(0.05788));
|
||||
}
|
||||
|
||||
TEST_CASE("Deeply nested namespaces via register_namespace") {
|
||||
@ -1816,4 +1820,60 @@ TEST_CASE("Deeply nested namespaces via register_namespace") {
|
||||
chai.import("a");
|
||||
|
||||
CHECK(chai.eval<int>("a.b.c.val") == 42);
|
||||
CHECK(chai.eval<int>("a::b::c::val") == 42);
|
||||
}
|
||||
|
||||
TEST_CASE("Block namespace declaration with ::") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.eval(R"(
|
||||
namespace math {
|
||||
def square(x) { x * x }
|
||||
}
|
||||
)");
|
||||
|
||||
CHECK(chai.eval<int>("math::square(5)") == 25);
|
||||
CHECK(chai.eval<int>("math.square(5)") == 25);
|
||||
}
|
||||
|
||||
TEST_CASE("Nested block namespace declaration") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.eval(R"(
|
||||
namespace physics::constants {
|
||||
def speed_of_light() { return 299792458 }
|
||||
}
|
||||
)");
|
||||
|
||||
CHECK(chai.eval<int>("physics::constants::speed_of_light()") == 299792458);
|
||||
}
|
||||
|
||||
TEST_CASE("Namespace block reopening") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.eval(R"(
|
||||
namespace ns {
|
||||
def foo() { return 1 }
|
||||
}
|
||||
namespace ns {
|
||||
def bar() { return 2 }
|
||||
}
|
||||
)");
|
||||
|
||||
CHECK(chai.eval<int>("ns::foo()") == 1);
|
||||
CHECK(chai.eval<int>("ns::bar()") == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Namespace block with var declarations") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.eval(R"(
|
||||
namespace config {
|
||||
var pi = 3.14
|
||||
var name = "hello"
|
||||
}
|
||||
)");
|
||||
|
||||
CHECK(chai.eval<double>("config::pi") == Approx(3.14));
|
||||
CHECK(chai.eval<std::string>("config::name") == "hello");
|
||||
}
|
||||
|
||||
@ -1,25 +1,55 @@
|
||||
// Test nested namespaces using C++-style :: separator
|
||||
namespace("constants::si")
|
||||
constants.si.mu_B = 1.0
|
||||
// Test C++-style block namespace declarations
|
||||
namespace constants::si {
|
||||
def mu_B() { return 1.0 }
|
||||
}
|
||||
|
||||
namespace("constants::mm")
|
||||
constants.mm.mu_B = 2.0
|
||||
namespace constants::mm {
|
||||
def mu_B() { return 2.0 }
|
||||
}
|
||||
|
||||
assert_equal(1.0, constants.si.mu_B)
|
||||
assert_equal(2.0, constants.mm.mu_B)
|
||||
assert_equal(1.0, constants::si::mu_B())
|
||||
assert_equal(2.0, constants::mm::mu_B())
|
||||
|
||||
// Test deeper nesting
|
||||
namespace("a::b::c")
|
||||
a.b.c.val = 42
|
||||
// Test deeper nesting with block syntax
|
||||
namespace a::b::c {
|
||||
def val() { return 42 }
|
||||
}
|
||||
|
||||
assert_equal(42, a.b.c.val)
|
||||
assert_equal(42, a::b::c::val())
|
||||
|
||||
// Test that existing namespace can gain nested children
|
||||
namespace("math")
|
||||
math.square = fun(x) { x * x }
|
||||
// Test reopening a namespace to add more members
|
||||
namespace math {
|
||||
def square(x) { x * x }
|
||||
}
|
||||
|
||||
namespace("math::trig")
|
||||
math.trig.double_angle = fun(x) { 2.0 * x }
|
||||
namespace math::trig {
|
||||
def double_angle(x) { 2.0 * x }
|
||||
}
|
||||
|
||||
assert_equal(16, math::square(4))
|
||||
assert_equal(6.0, math::trig::double_angle(3.0))
|
||||
|
||||
// Test reopening a namespace (C++ allows this)
|
||||
namespace math {
|
||||
def cube(x) { x * x * x }
|
||||
}
|
||||
|
||||
assert_equal(27, math::cube(3))
|
||||
|
||||
// Test that :: scope resolution works the same as . for access
|
||||
assert_equal(16, math.square(4))
|
||||
assert_equal(6.0, math.trig.double_angle(3.0))
|
||||
|
||||
// Test namespace with var declarations
|
||||
namespace config {
|
||||
var pi = 3.14159
|
||||
var name = "test"
|
||||
}
|
||||
|
||||
assert_equal(3.14159, config::pi)
|
||||
assert_equal("test", config::name)
|
||||
|
||||
// Test function-call style still works
|
||||
namespace("compat")
|
||||
compat.legacy = 99
|
||||
assert_equal(99, compat::legacy)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user