diff --git a/cheatsheet.md b/cheatsheet.md index 8cf3d3f4..28debbca 100644 --- a/cheatsheet.md +++ b/cheatsheet.md @@ -166,6 +166,23 @@ chai.add_global(chaiscript::var(somevar), "somevar"); // global non-const, throw chai.set_global(chaiscript::var(somevar), "somevar"); // global non-const, overwrites existing object ``` +## Adding Namespaces + +Namespaces will not be populated until `import` is called. +This saves memory and computing costs if a namespace is not imported into every ChaiScript instance. +``` +chai.register_namespace([](chaiscript::Namespace& math) { + math["pi"] = chaiscript::const_var(3.14159); + math["sin"] = chaiscript::var(chaiscript::fun([](const double x) { return sin(x); })); }, + "math"); +``` + +Import namespace in ChaiScript +``` +import("math") +print(math.pi) // prints 3.14159 +``` + # Using STL ChaiScript recognize many types from STL, but you have to add specific instantiation yourself. @@ -479,6 +496,20 @@ o.f = fun(y) { print(this.x + y); } o.f(10); // prints 13 ``` +## Namespaces + +Namespaces in ChaiScript are Dynamic Objects with global scope + +``` +namespace("math") // create a new namespace + +math.square = fun(x) { x * x } // add a function to the "math" namespace +math.sum_squares = fun(x, y) { math.square(x) + math.square(y) } + +print(math.square(4)) // prints 16 +print(math.sum_squares(2, 5)) // prints 29 +``` + ### Option Explicit If you want to disable dynamic parameter definitions, you can `set_explicit`. diff --git a/include/chaiscript/language/chaiscript_engine.hpp b/include/chaiscript/language/chaiscript_engine.hpp index 6cf22a1d..3dce4a76 100644 --- a/include/chaiscript/language/chaiscript_engine.hpp +++ b/include/chaiscript/language/chaiscript_engine.hpp @@ -54,7 +54,9 @@ #include "../dispatchkit/exception_specification.hpp" namespace chaiscript -{ +{ + /// Namespace alias to provide cleaner and more explicit syntax to users. + using Namespace = dispatch::Dynamic_Object; namespace detail { @@ -79,6 +81,8 @@ namespace chaiscript chaiscript::detail::Dispatch_Engine m_engine; + std::map> m_namespace_generators; + /// Evaluates the given string in by parsing it and running the results through the evaluator Boxed_Value do_eval(const std::string &t_input, const std::string &t_filename = "__EVAL__", bool /* t_internal*/ = false) { @@ -195,6 +199,9 @@ namespace chaiscript m_engine.add(fun([this](const Boxed_Value &t_bv, const std::string &t_name){ add_global_const(t_bv, t_name); }), "add_global_const"); 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 std::string& t_namespace_name) { register_namespace([](Namespace& space) {}, t_namespace_name); import(t_namespace_name); }), "namespace"); + m_engine.add(fun([this](const std::string& t_namespace_name) { import(t_namespace_name); }), "import"); } @@ -702,6 +709,41 @@ namespace chaiscript T eval_file(const std::string &t_filename, const Exception_Handler &t_handler = Exception_Handler()) { return m_engine.boxed_cast(eval_file(t_filename, t_handler)); } + + /// \brief Imports a namespace object into the global scope of this ChaiScript instance. + /// \param[in] t_namespace_name Name of the namespace to import. + /// \throw std::runtime_error In the case that the namespace name was never registered. + void import(const std::string& t_namespace_name) + { + chaiscript::detail::threading::unique_lock l(m_use_mutex); + + if (m_engine.get_scripting_objects().count(t_namespace_name)) { + throw std::runtime_error("Namespace: " + t_namespace_name + " was already defined"); + } + 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); + } + else { + 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 used. + /// \param[in] t_namespace_generator Namespace generator function. + /// \param[in] t_namespace_name Name of the Namespace function being registered. + /// \throw std::runtime_error In the case that the namespace name was already registered. + void register_namespace(const std::function& t_namespace_generator, const std::string& t_namespace_name) + { + chaiscript::detail::threading::unique_lock l(m_use_mutex); + + 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."); + } + } }; } diff --git a/unittests/namespaces.chai b/unittests/namespaces.chai new file mode 100644 index 00000000..51bea60f --- /dev/null +++ b/unittests/namespaces.chai @@ -0,0 +1,7 @@ +namespace("math") + +math.square = fun(x) { x * x } +math.sum_squares = fun(x, y) { math.square(x) + math.square(y) } + +assert_equal(16, math.square(4)) +assert_equal(29, math.sum_squares(2, 5)) \ No newline at end of file diff --git a/unittests/namespaces_nested_copy.chai b/unittests/namespaces_nested_copy.chai new file mode 100644 index 00000000..7ec8ff53 --- /dev/null +++ b/unittests/namespaces_nested_copy.chai @@ -0,0 +1,9 @@ +namespace("parent") +namespace("child") + +child.x = 3.0 +parent.child = child +parent.child.x = 5.0 + +assert_equal(3.0, child.x) +assert_equal(5.0, parent.child.x) \ No newline at end of file