diff --git a/cheatsheet.md b/cheatsheet.md index 1819ddc0..d28241ec 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 d9b3e23e..e5d18255 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"); @@ -437,8 +437,10 @@ namespace chaiscript::bootstrap { m.add(fun(&Build_Info::compiler_id), "compiler_id"); m.add(fun(&Build_Info::debug_build), "debug_build"); - m.add(fun(&print), "print_string"); - m.add(fun(&println), "println_string"); + if (!t_no_io) { + m.add(fun(&print), "print_string"); + m.add(fun(&println), "println_string"); + } m.add(dispatch::make_dynamic_proxy_function(&bind_function), "bind"); diff --git a/unittests/compiled_tests.cpp b/unittests/compiled_tests.cpp index 0a2d252b..3cdef6cc 100644 --- a/unittests/compiled_tests.cpp +++ b/unittests/compiled_tests.cpp @@ -1343,6 +1343,132 @@ 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 disables print functions") { + 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}); + + CHECK_THROWS(chai.eval("print_string(\"hello\")")); + CHECK_THROWS(chai.eval("println_string(\"hello\")")); + CHECK(chai.eval("5 + 3") == 8); + CHECK_NOTHROW(chai.eval("var v = Vector()")); +} + +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}); + + CHECK_THROWS(chai.eval("print_string(\"hello\")")); + CHECK_THROWS(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