diff --git a/cheatsheet.md b/cheatsheet.md index a0bd09fe..cab7504f 100644 --- a/cheatsheet.md +++ b/cheatsheet.md @@ -16,6 +16,67 @@ chaiscript::ChaiScript chai; // initializes ChaiScript, adding the standard Chai Note that ChaiScript cannot be used as a global / static object unless it is being compiled with `CHAISCRIPT_NO_THREADS`. +## Engine Options (`Options`) + +Engine-level options control which scripting capabilities are exposed. These are passed as a `std::vector` to the `ChaiScript` or `ChaiScript_Basic` constructor. + +| Option | Effect | +|--------|--------| +| `Options::Load_Modules` | Enables `load_module()` in scripts (default) | +| `Options::No_Load_Modules` | Disables `load_module()` | +| `Options::External_Scripts` | Enables `use()` and `eval_file()` in scripts (default) | +| `Options::No_External_Scripts` | Disables `use()` and `eval_file()` | + +```cpp +// Sandboxed engine: no dynamic module loading, no external script evaluation +chaiscript::ChaiScript chai({}, {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}); +``` + +## Library Options (`Library_Options`) + +Library-level options control which parts of the standard library are registered. These are passed as a `std::vector`. + +| Option | Effect | +|--------|--------| +| `Library_Options::No_Stdlib` | Disables the entire standard library (types, I/O, prelude, JSON — everything) | +| `Library_Options::No_IO` | Disables `print_string` and `println_string` (and the prelude's `print`/`puts` wrappers) | +| `Library_Options::No_Prelude` | Disables the ChaiScript prelude (`print`, `puts`, `filter`, `map`, `foldl`, `join`, etc.) | +| `Library_Options::No_JSON` | Disables `from_json` and `to_json` | + +With the `ChaiScript` convenience class, pass library options as the fourth constructor parameter: + +```cpp +// No I/O functions +chaiscript::ChaiScript chai({}, {}, chaiscript::default_options(), + {chaiscript::Library_Options::No_IO}); + +// No JSON support +chaiscript::ChaiScript chai({}, {}, chaiscript::default_options(), + {chaiscript::Library_Options::No_JSON}); + +// Completely bare engine — no stdlib at all +chaiscript::ChaiScript chai({}, {}, chaiscript::default_options(), + {chaiscript::Library_Options::No_Stdlib}); + +// Combine both: no external scripts and no I/O +chaiscript::ChaiScript chai({}, {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}, + {chaiscript::Library_Options::No_IO}); +``` + +With `ChaiScript_Basic`, pass library options directly to `Std_Lib::library()`: + +```cpp +chaiscript::ChaiScript_Basic chai( + chaiscript::Std_Lib::library({chaiscript::Library_Options::No_IO}), + create_chaiscript_parser(), + {}, {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}); +``` + +Note: `No_Prelude` disables the prelude script which defines convenience functions like `print` (which wraps `print_string`). If you disable the prelude but not I/O, `print_string` and `println_string` are still available. + # Adding Things To The Engine ## Adding a Function / Method / Member diff --git a/include/chaiscript/chaiscript.hpp b/include/chaiscript/chaiscript.hpp index 871b353b..1be2f0b4 100644 --- a/include/chaiscript/chaiscript.hpp +++ b/include/chaiscript/chaiscript.hpp @@ -824,8 +824,9 @@ namespace chaiscript { public: ChaiScript(std::vector t_modulepaths = {}, std::vector t_usepaths = {}, - std::vector t_opts = chaiscript::default_options()) - : ChaiScript_Basic(chaiscript::Std_Lib::library(), + std::vector t_opts = chaiscript::default_options(), + std::vector t_lib_opts = {}) + : ChaiScript_Basic(chaiscript::Std_Lib::library(t_lib_opts), std::make_unique>(), std::move(t_modulepaths), std::move(t_usepaths), diff --git a/include/chaiscript/chaiscript_defines.hpp b/include/chaiscript/chaiscript_defines.hpp index e01aadd3..f50510f0 100644 --- a/include/chaiscript/chaiscript_defines.hpp +++ b/include/chaiscript/chaiscript_defines.hpp @@ -211,6 +211,13 @@ namespace chaiscript { External_Scripts }; + enum class Library_Options { + No_Stdlib, + No_IO, + No_Prelude, + No_JSON + }; + template struct is_nothrow_forward_constructible : std::bool_constant()})> { }; diff --git a/include/chaiscript/chaiscript_stdlib.hpp b/include/chaiscript/chaiscript_stdlib.hpp index 0daac71e..73b11542 100644 --- a/include/chaiscript/chaiscript_stdlib.hpp +++ b/include/chaiscript/chaiscript_stdlib.hpp @@ -38,9 +38,18 @@ namespace chaiscript { class Std_Lib { public: - [[nodiscard]] static ModulePtr library() { + [[nodiscard]] static ModulePtr library(const std::vector &t_opts = {}) { + if (std::find(t_opts.begin(), t_opts.end(), Library_Options::No_Stdlib) != t_opts.end()) { + return std::make_shared(); + } + auto lib = std::make_shared(); - bootstrap::Bootstrap::bootstrap(*lib); + + const bool no_io = std::find(t_opts.begin(), t_opts.end(), Library_Options::No_IO) != t_opts.end(); + const bool no_prelude = std::find(t_opts.begin(), t_opts.end(), Library_Options::No_Prelude) != t_opts.end(); + const bool no_json = std::find(t_opts.begin(), t_opts.end(), Library_Options::No_JSON) != t_opts.end(); + + bootstrap::Bootstrap::bootstrap(*lib, no_io); bootstrap::standard_library::vector_type>("Vector", *lib); bootstrap::standard_library::string_type("string", *lib); @@ -53,9 +62,13 @@ namespace chaiscript { // with thread tracking to prevent heap-use-after-free on engine destruction. #endif - json_wrap::library(*lib); + if (!no_json) { + json_wrap::library(*lib); + } - lib->eval(ChaiScript_Prelude::chaiscript_prelude() /*, "standard prelude"*/); + if (!no_prelude) { + lib->eval(ChaiScript_Prelude::chaiscript_prelude() /*, "standard prelude"*/); + } return lib; } diff --git a/include/chaiscript/dispatchkit/bootstrap.hpp b/include/chaiscript/dispatchkit/bootstrap.hpp index b0fe811e..e5cc873c 100644 --- a/include/chaiscript/dispatchkit/bootstrap.hpp +++ b/include/chaiscript/dispatchkit/bootstrap.hpp @@ -268,8 +268,8 @@ namespace chaiscript::bootstrap { public: /// \brief perform all common bootstrap functions for std::string, void and POD types /// \param[in,out] m Module to add bootstrapped functions to - /// \returns passed in Module - static void bootstrap(Module &m) { + /// \param[in] t_no_io If true, skip registering print_string and println_string + static void bootstrap(Module &m, const bool t_no_io = false) { m.add(user_type(), "void"); m.add(user_type(), "bool"); m.add(user_type(), "Object"); @@ -438,7 +438,9 @@ namespace chaiscript::bootstrap { m.add(fun(&Build_Info::debug_build), "debug_build"); // print_string and println_string are registered in ChaiScript_Basic::build_eval_system() - // to support per-instance IO redirection via set_print_handler + // to support per-instance IO redirection via set_print_handler. + // When No_IO is set, the handler-based functions are still registered there, + // so users can provide their own print handlers even without built-in IO. m.add(dispatch::make_dynamic_proxy_function(&bind_function), "bind"); diff --git a/include/chaiscript/dispatchkit/bootstrap_stl.hpp b/include/chaiscript/dispatchkit/bootstrap_stl.hpp index f56304d7..5bbc17e0 100644 --- a/include/chaiscript/dispatchkit/bootstrap_stl.hpp +++ b/include/chaiscript/dispatchkit/bootstrap_stl.hpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -170,18 +171,22 @@ namespace chaiscript::bootstrap::standard_library { /// http://www.sgi.com/tech/stl/Assignable.html template void assignable_type(const std::string &type, Module &m) { - copy_constructor(type, m); - operators::assign(m); + if constexpr (std::is_copy_constructible_v) { + copy_constructor(type, m); + operators::assign(m); + } } /// Add container resize concept to the given ContainerType /// http://www.cplusplus.com/reference/stl/ template void resizable_type(const std::string & /*type*/, Module &m) { - m.add(fun([](ContainerType *a, typename ContainerType::size_type n, const typename ContainerType::value_type &val) { - return a->resize(n, val); - }), - "resize"); + if constexpr (std::is_copy_constructible_v) { + m.add(fun([](ContainerType *a, typename ContainerType::size_type n, const typename ContainerType::value_type &val) { + return a->resize(n, val); + }), + "resize"); + } m.add(fun([](ContainerType *a, typename ContainerType::size_type n) { return a->resize(n); }), "resize"); } @@ -213,13 +218,15 @@ namespace chaiscript::bootstrap::standard_library { /// http://www.sgi.com/tech/stl/Sequence.html template void sequence_type(const std::string & /*type*/, Module &m) { - m.add(fun(&detail::insert_at), []() -> std::string { - if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) { - return "insert_ref_at"; - } else { - return "insert_at"; - } - }()); + if constexpr (std::is_copy_constructible_v) { + m.add(fun(&detail::insert_at), []() -> std::string { + if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) { + return "insert_ref_at"; + } else { + return "insert_at"; + } + }()); + } m.add(fun(&detail::erase_at), "erase_at"); } @@ -245,27 +252,29 @@ namespace chaiscript::bootstrap::standard_library { }), "back"); - using push_back = void (ContainerType::*)(const typename ContainerType::value_type &); - m.add(fun(static_cast(&ContainerType::push_back)), [&]() -> std::string { - if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) { - m.eval("# Pushes the second value onto the container while making a clone of the value\n" - "def push_back(" - + type - + " container, x)\n" - "{ \n" - " if (x.is_var_return_value()) {\n" - " x.reset_var_return_value() \n" - " container.push_back_ref(x) \n" - " } else { \n" - " container.push_back_ref(clone(x)); \n" - " }\n" - "} \n"); + if constexpr (std::is_copy_constructible_v) { + using push_back = void (ContainerType::*)(const typename ContainerType::value_type &); + m.add(fun(static_cast(&ContainerType::push_back)), [&]() -> std::string { + if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) { + m.eval("# Pushes the second value onto the container while making a clone of the value\n" + "def push_back(" + + type + + " container, x)\n" + "{ \n" + " if (x.is_var_return_value()) {\n" + " x.reset_var_return_value() \n" + " container.push_back_ref(x) \n" + " } else { \n" + " container.push_back_ref(clone(x)); \n" + " }\n" + "} \n"); - return "push_back_ref"; - } else { - return "push_back"; - } - }()); + return "push_back_ref"; + } else { + return "push_back"; + } + }()); + } m.add(fun(&ContainerType::pop_back), "pop_back"); } @@ -295,25 +304,27 @@ namespace chaiscript::bootstrap::standard_library { }), "front"); - m.add(fun(static_cast(&ContainerType::push_front)), [&]() -> std::string { - if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) { - m.eval("# Pushes the second value onto the front of container while making a clone of the value\n" - "def push_front(" - + type - + " container, x)\n" - "{ \n" - " if (x.is_var_return_value()) {\n" - " x.reset_var_return_value() \n" - " container.push_front_ref(x) \n" - " } else { \n" - " container.push_front_ref(clone(x)); \n" - " }\n" - "} \n"); - return "push_front_ref"; - } else { - return "push_front"; - } - }()); + if constexpr (std::is_copy_constructible_v) { + m.add(fun(static_cast(&ContainerType::push_front)), [&]() -> std::string { + if (typeid(typename ContainerType::value_type) == typeid(Boxed_Value)) { + m.eval("# Pushes the second value onto the front of container while making a clone of the value\n" + "def push_front(" + + type + + " container, x)\n" + "{ \n" + " if (x.is_var_return_value()) {\n" + " x.reset_var_return_value() \n" + " container.push_front_ref(x) \n" + " } else { \n" + " container.push_front_ref(clone(x)); \n" + " }\n" + "} \n"); + return "push_front_ref"; + } else { + return "push_front"; + } + }()); + } m.add(fun(static_cast(&ContainerType::pop_front)), "pop_front"); } diff --git a/include/chaiscript/dispatchkit/dispatchkit.hpp b/include/chaiscript/dispatchkit/dispatchkit.hpp index 95627dbc..be43905d 100644 --- a/include/chaiscript/dispatchkit/dispatchkit.hpp +++ b/include/chaiscript/dispatchkit/dispatchkit.hpp @@ -1087,13 +1087,21 @@ namespace chaiscript { // overridden methods in derived classes are tried first during dispatch const auto &lhs_dotn = lhs->dynamic_object_type_name(); const auto &rhs_dotn = rhs->dynamic_object_type_name(); - if (!lhs_dotn.empty() && !rhs_dotn.empty() && lhs_dotn != rhs_dotn) { - if (dispatch::Dynamic_Object::type_matches(lhs_dotn, rhs_dotn)) { - return true; // lhs is derived from rhs, so lhs is more specific + if (lhs_dotn != rhs_dotn) { + if (!lhs_dotn.empty() && !rhs_dotn.empty()) { + if (dispatch::Dynamic_Object::type_matches(lhs_dotn, rhs_dotn)) { + return true; // lhs is derived from rhs, so lhs is more specific + } + if (dispatch::Dynamic_Object::type_matches(rhs_dotn, lhs_dotn)) { + return false; // rhs is derived from lhs, so rhs is more specific + } } - if (dispatch::Dynamic_Object::type_matches(rhs_dotn, lhs_dotn)) { - return false; // rhs is derived from lhs, so rhs is more specific + // Impose a total order on type names to maintain strict-weak ordering: + // non-empty names sort before empty names, then lexicographically + if (lhs_dotn.empty() != rhs_dotn.empty()) { + return !lhs_dotn.empty(); } + return lhs_dotn < rhs_dotn; } const auto &lhsparamtypes = lhs->get_param_types(); @@ -1143,7 +1151,9 @@ namespace chaiscript { return lt < rt; } - return false; + // When all overlapping parameters match, order by arity to maintain + // strict-weak ordering (transitivity of equivalence) + return lhssize < rhssize; } /// Implementation detail for adding a function. diff --git a/unittests/compiled_tests.cpp b/unittests/compiled_tests.cpp index d6461b5f..cf13b0f2 100644 --- a/unittests/compiled_tests.cpp +++ b/unittests/compiled_tests.cpp @@ -1343,6 +1343,140 @@ TEST_CASE("Test if non copyable/movable types can be registered") { chai.add(chaiscript::constructor(), "Nothing"); } +// Tests for issue #146: configuration to bypass registering built-in functions +// Tests through ChaiScript_Basic (library options passed explicitly to Std_Lib::library) + +TEST_CASE("ChaiScript_Basic No_Stdlib option disables all standard library functions") { + chaiscript::ChaiScript_Basic chai(chaiscript::Std_Lib::library({chaiscript::Library_Options::No_Stdlib}), + create_chaiscript_parser(), + {}, + {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}); + + CHECK_NOTHROW(chai.eval("var x = 5")); + CHECK_THROWS(chai.eval("print(\"hello\")")); + CHECK_THROWS(chai.eval("var v = Vector()")); + CHECK_THROWS(chai.eval("\"hello\".trim()")); + CHECK_THROWS(chai.eval("from_json(\"[1,2,3]\")")); +} + +TEST_CASE("ChaiScript_Basic No_IO option still allows print_handler registration") { + chaiscript::ChaiScript_Basic chai(chaiscript::Std_Lib::library({chaiscript::Library_Options::No_IO}), + create_chaiscript_parser(), + {}, + {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}); + + // print_string and println_string should still be available via the handler mechanism + CHECK_NOTHROW(chai.eval("print_string(\"hello\")")); + CHECK_NOTHROW(chai.eval("println_string(\"hello\")")); + CHECK(chai.eval("5 + 3") == 8); + CHECK_NOTHROW(chai.eval("var v = Vector()")); + + // Users can set their own print handler even with No_IO + std::string captured; + chai.set_print_handler([&captured](const std::string &s) { captured += s; }); + chai.eval("print_string(\"redirected\")"); + CHECK(captured == "redirected"); +} + +TEST_CASE("ChaiScript_Basic No_Prelude option disables prelude functions") { + chaiscript::ChaiScript_Basic chai(chaiscript::Std_Lib::library({chaiscript::Library_Options::No_Prelude}), + create_chaiscript_parser(), + {}, + {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}); + + CHECK_THROWS(chai.eval("print(\"hello\")")); + CHECK_NOTHROW(chai.eval("print_string(\"hello\")")); + CHECK_THROWS(chai.eval("filter([1,2,3], fun(x) { x > 1 })")); + CHECK(chai.eval("5 + 3") == 8); +} + +TEST_CASE("ChaiScript_Basic No_JSON option disables JSON support") { + chaiscript::ChaiScript_Basic chai(chaiscript::Std_Lib::library({chaiscript::Library_Options::No_JSON}), + create_chaiscript_parser(), + {}, + {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}); + + CHECK_THROWS(chai.eval("from_json(\"[1,2,3]\")")); + CHECK(chai.eval("5 + 3") == 8); + CHECK_NOTHROW(chai.eval("print(\"hello\")")); +} + +TEST_CASE("ChaiScript_Basic default library has all functions") { + chaiscript::ChaiScript_Basic chai(chaiscript::Std_Lib::library(), + create_chaiscript_parser(), + {}, + {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}); + + CHECK_NOTHROW(chai.eval("print(\"hello\")")); + CHECK_NOTHROW(chai.eval("print_string(\"hello\")")); + CHECK_NOTHROW(chai.eval("var v = Vector()")); + CHECK(chai.eval("5 + 3") == 8); +} + +// Tests through ChaiScript (library options passed as constructor parameter) + +TEST_CASE("ChaiScript No_Stdlib option via library options parameter") { + chaiscript::ChaiScript chai({}, + {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}, + {chaiscript::Library_Options::No_Stdlib}); + + CHECK_NOTHROW(chai.eval("var x = 5")); + CHECK_THROWS(chai.eval("print(\"hello\")")); + CHECK_THROWS(chai.eval("var v = Vector()")); + CHECK_THROWS(chai.eval("from_json(\"[1,2,3]\")")); +} + +TEST_CASE("ChaiScript No_IO option via library options parameter") { + chaiscript::ChaiScript chai({}, + {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}, + {chaiscript::Library_Options::No_IO}); + + // print_string and println_string remain available via the handler mechanism + CHECK_NOTHROW(chai.eval("print_string(\"hello\")")); + CHECK_NOTHROW(chai.eval("println_string(\"hello\")")); + CHECK(chai.eval("5 + 3") == 8); + CHECK_NOTHROW(chai.eval("var v = Vector()")); +} + +TEST_CASE("ChaiScript No_Prelude option via library options parameter") { + chaiscript::ChaiScript chai({}, + {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}, + {chaiscript::Library_Options::No_Prelude}); + + CHECK_THROWS(chai.eval("print(\"hello\")")); + CHECK_NOTHROW(chai.eval("print_string(\"hello\")")); + CHECK_THROWS(chai.eval("filter([1,2,3], fun(x) { x > 1 })")); + CHECK(chai.eval("5 + 3") == 8); +} + +TEST_CASE("ChaiScript No_JSON option via library options parameter") { + chaiscript::ChaiScript chai({}, + {}, + {chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts}, + {chaiscript::Library_Options::No_JSON}); + + CHECK_THROWS(chai.eval("from_json(\"[1,2,3]\")")); + CHECK(chai.eval("5 + 3") == 8); + CHECK_NOTHROW(chai.eval("print(\"hello\")")); +} + +TEST_CASE("ChaiScript default has all functions") { + chaiscript::ChaiScript chai; + + CHECK_NOTHROW(chai.eval("print(\"hello\")")); + CHECK_NOTHROW(chai.eval("print_string(\"hello\")")); + CHECK_NOTHROW(chai.eval("var v = Vector()")); + CHECK(chai.eval("5 + 3") == 8); +} + // Issue #421: Class with type_conversion from int and "==" operator // causes switch statement to compare destroyed objects. // The switch case comparison must use Function_Push_Pop to properly @@ -1437,6 +1571,50 @@ TEST_CASE("Issue #421 - Switch with type_conversion does not compare destroyed o })") == 0); } +// Issue #524: A std::vector of std::unique_ptrs can't be added +// vector_type should compile with non-copyable value types by +// skipping copy-dependent operations via if constexpr. +struct Issue524_Foo { + int value = 42; +}; + +TEST_CASE("Issue #524 - vector of unique_ptr can be registered") { + using VecType = std::vector>; + + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + // This should compile and not throw - previously failed to compile + // because vector_type tried to instantiate copy constructor, assignment, + // push_back(const T&), insert_at(const T&), and resize(n, const T&) + // for the non-copyable std::unique_ptr. + chaiscript::ModulePtr m = std::make_shared(); + chaiscript::bootstrap::standard_library::vector_type("UniqueVec", *m); + CHECK_NOTHROW(chai.add(m)); + + // Verify basic operations still work + CHECK(chai.eval("var v = UniqueVec(); v.size()") == 0); + CHECK(chai.eval("var v2 = UniqueVec(); v2.empty()") == true); +} + +// Issue #625: function_less_than comparator must satisfy strict-weak ordering. +// Registering overloaded functions with different arities triggered a +// std::stable_sort assertion on macOS 15.2 (hardened libc++) because the +// comparator violated transitivity of equivalence. +TEST_CASE("Issue 625: function_less_than strict-weak ordering with different arities") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + // Register overloaded functions with varying arities under the same name. + // If the comparator doesn't order by arity when overlapping params match, + // std::stable_sort may exhibit undefined behavior. + CHECK_NOTHROW(chai.add(chaiscript::fun([](int x, const chaiscript::Boxed_Value &) { return x; }), "overloaded")); + CHECK_NOTHROW(chai.add(chaiscript::fun([](int x, double y) { return x + static_cast(y); }), "overloaded")); + CHECK_NOTHROW(chai.add(chaiscript::fun([](int x) { return x; }), "overloaded")); + + // Verify dispatch still works correctly + CHECK(chai.eval("overloaded(5)") == 5); + CHECK(chai.eval("overloaded(3, 2.0)") == 5); +} + TEST_CASE("IO redirection with set_print_handler") { chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());