mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2026-11-09 14:50:25 +08:00
Fix #552: Support nested namespaces via dotted names
Namespaces can now be nested using dotted name syntax, both from C++
(register_namespace(gen, "constants.si")) and from script
(namespace("constants.si")). Parent namespaces are auto-registered when
absent, and child namespaces are automatically nested into their parent
on import. This allows clean hierarchical organization like
constants.si.mu_B instead of flat names like constants_si.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
07d62aae99
commit
42ecde5197
@ -188,10 +188,15 @@ namespace chaiscript {
|
|||||||
m_engine.add(fun([this](const Boxed_Value &t_bv, const std::string &t_name) { add_global(t_bv, t_name); }), "add_global");
|
m_engine.add(fun([this](const Boxed_Value &t_bv, const std::string &t_name) { add_global(t_bv, t_name); }), "add_global");
|
||||||
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 Boxed_Value &t_bv, const std::string &t_name) { set_global(t_bv, t_name); }), "set_global");
|
||||||
|
|
||||||
// why this unused parameter to Namespace?
|
|
||||||
m_engine.add(fun([this](const std::string &t_namespace_name) {
|
m_engine.add(fun([this](const std::string &t_namespace_name) {
|
||||||
register_namespace([](Namespace & /*space*/) noexcept {}, t_namespace_name);
|
register_namespace([](Namespace & /*space*/) noexcept {}, t_namespace_name);
|
||||||
import(t_namespace_name);
|
const auto dot_pos = t_namespace_name.find('.');
|
||||||
|
const std::string root_name = (dot_pos != std::string::npos) ? t_namespace_name.substr(0, dot_pos) : t_namespace_name;
|
||||||
|
if (!m_engine.get_scripting_objects().count(root_name)) {
|
||||||
|
import(root_name);
|
||||||
|
} else if (m_namespace_generators.count(root_name)) {
|
||||||
|
nest_children(root_name, m_namespace_generators[root_name]());
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
"namespace");
|
"namespace");
|
||||||
m_engine.add(fun([this](const std::string &t_namespace_name) { import(t_namespace_name); }), "import");
|
m_engine.add(fun([this](const std::string &t_namespace_name) { import(t_namespace_name); }), "import");
|
||||||
@ -730,28 +735,59 @@ namespace chaiscript {
|
|||||||
if (m_engine.get_scripting_objects().count(t_namespace_name)) {
|
if (m_engine.get_scripting_objects().count(t_namespace_name)) {
|
||||||
throw std::runtime_error("Namespace: " + t_namespace_name + " was already defined");
|
throw std::runtime_error("Namespace: " + t_namespace_name + " was already defined");
|
||||||
} else if (m_namespace_generators.count(t_namespace_name)) {
|
} else if (m_namespace_generators.count(t_namespace_name)) {
|
||||||
m_engine.add_global(var(std::ref(m_namespace_generators[t_namespace_name]())), t_namespace_name);
|
auto &ns = m_namespace_generators[t_namespace_name]();
|
||||||
|
nest_children(t_namespace_name, ns);
|
||||||
|
m_engine.add_global(var(std::ref(ns)), t_namespace_name);
|
||||||
} else {
|
} else {
|
||||||
throw std::runtime_error("No registered namespace: " + t_namespace_name);
|
throw std::runtime_error("No registered namespace: " + t_namespace_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \brief Registers a namespace generator, which delays generation of the namespace until it is imported, saving memory if it is never
|
/// \brief Registers a namespace generator, which delays generation of the namespace until it is imported, saving memory if it is never
|
||||||
/// used. \param[in] t_namespace_generator Namespace generator function. \param[in] t_namespace_name Name of the Namespace function
|
/// used. Supports dotted names (e.g. "constants.si") for nested namespaces; parent namespaces are auto-registered if absent.
|
||||||
/// being registered. \throw std::runtime_error In the case that the namespace name was already registered.
|
/// \param[in] t_namespace_generator Namespace generator function.
|
||||||
|
/// \param[in] t_namespace_name Name of the Namespace function being registered (may contain dots for nesting).
|
||||||
|
/// \throw std::runtime_error In the case that the namespace name was already registered.
|
||||||
void register_namespace(const std::function<void(Namespace &)> &t_namespace_generator, const std::string &t_namespace_name) {
|
void register_namespace(const std::function<void(Namespace &)> &t_namespace_generator, const std::string &t_namespace_name) {
|
||||||
chaiscript::detail::threading::unique_lock<chaiscript::detail::threading::recursive_mutex> l(m_use_mutex);
|
chaiscript::detail::threading::unique_lock<chaiscript::detail::threading::recursive_mutex> l(m_use_mutex);
|
||||||
|
|
||||||
if (!m_namespace_generators.count(t_namespace_name)) {
|
if (m_namespace_generators.count(t_namespace_name)) {
|
||||||
// contain the namespace object memory within the m_namespace_generators map
|
|
||||||
m_namespace_generators.emplace(std::make_pair(t_namespace_name, [=, space = Namespace()]() mutable -> Namespace & {
|
|
||||||
t_namespace_generator(space);
|
|
||||||
return space;
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
throw std::runtime_error("Namespace: " + t_namespace_name + " was already registered.");
|
throw std::runtime_error("Namespace: " + t_namespace_name + " was already registered.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_namespace_generators.emplace(std::make_pair(t_namespace_name, [=, space = Namespace()]() mutable -> Namespace & {
|
||||||
|
t_namespace_generator(space);
|
||||||
|
return space;
|
||||||
|
}));
|
||||||
|
|
||||||
|
auto pos = t_namespace_name.rfind('.');
|
||||||
|
while (pos != std::string::npos) {
|
||||||
|
const std::string parent = t_namespace_name.substr(0, pos);
|
||||||
|
if (!m_namespace_generators.count(parent)) {
|
||||||
|
m_namespace_generators.emplace(std::make_pair(parent, [space = Namespace()]() mutable -> Namespace & {
|
||||||
|
return space;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
pos = parent.rfind('.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void nest_children(const std::string &t_parent_name, Namespace &t_parent) {
|
||||||
|
const std::string prefix = t_parent_name + ".";
|
||||||
|
for (auto &[name, generator] : m_namespace_generators) {
|
||||||
|
if (name.size() > prefix.size() && name.compare(0, prefix.size(), prefix) == 0) {
|
||||||
|
const std::string remainder = name.substr(prefix.size());
|
||||||
|
if (remainder.find('.') == std::string::npos) {
|
||||||
|
auto &child_ns = generator();
|
||||||
|
nest_children(name, child_ns);
|
||||||
|
t_parent[remainder] = var(std::ref(child_ns));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chaiscript
|
} // namespace chaiscript
|
||||||
|
|||||||
@ -1782,3 +1782,38 @@ TEST_CASE("eval_error with AST_Node_Trace call stack compiles in C++20") {
|
|||||||
(void)stack;
|
(void)stack;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Nested namespaces via register_namespace with dotted names") {
|
||||||
|
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||||
|
|
||||||
|
chai.register_namespace(
|
||||||
|
[](chaiscript::Namespace &si) {
|
||||||
|
si["mu_B"] = chaiscript::const_var(9.274);
|
||||||
|
},
|
||||||
|
"constants.si");
|
||||||
|
|
||||||
|
chai.register_namespace(
|
||||||
|
[](chaiscript::Namespace &mm) {
|
||||||
|
mm["mu_B"] = chaiscript::const_var(0.05788);
|
||||||
|
},
|
||||||
|
"constants.mm");
|
||||||
|
|
||||||
|
chai.import("constants");
|
||||||
|
|
||||||
|
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") {
|
||||||
|
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||||
|
|
||||||
|
chai.register_namespace(
|
||||||
|
[](chaiscript::Namespace &leaf) {
|
||||||
|
leaf["val"] = chaiscript::const_var(42);
|
||||||
|
},
|
||||||
|
"a.b.c");
|
||||||
|
|
||||||
|
chai.import("a");
|
||||||
|
|
||||||
|
CHECK(chai.eval<int>("a.b.c.val") == 42);
|
||||||
|
}
|
||||||
|
|||||||
25
unittests/nested_namespaces.chai
Normal file
25
unittests/nested_namespaces.chai
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Test nested namespaces using dotted names
|
||||||
|
namespace("constants.si")
|
||||||
|
constants.si.mu_B = 1.0
|
||||||
|
|
||||||
|
namespace("constants.mm")
|
||||||
|
constants.mm.mu_B = 2.0
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
assert_equal(42, a.b.c.val)
|
||||||
|
|
||||||
|
// Test that existing namespace can gain nested children
|
||||||
|
namespace("math")
|
||||||
|
math.square = fun(x) { x * x }
|
||||||
|
|
||||||
|
namespace("math.trig")
|
||||||
|
math.trig.double_angle = fun(x) { 2.0 * x }
|
||||||
|
|
||||||
|
assert_equal(16, math.square(4))
|
||||||
|
assert_equal(6.0, math.trig.double_angle(3.0))
|
||||||
Loading…
x
Reference in New Issue
Block a user