Fix #146: Add configuration to bypass the registering of 'built-in' functions. (#642)

* Fix #146: Add configuration options to selectively disable built-in functions

Add new Options enum values (No_Stdlib, No_IO, No_Prelude, No_JSON) that
allow users to control which parts of the standard library are registered.
Std_Lib::library() now accepts an options vector, and the ChaiScript
convenience class forwards its options to the library builder. This enables
use cases where only custom functions should be exposed to script users.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Address review: split Options into Options and Library_Options enums

Separate system-level options (No_Load_Modules, Load_Modules, No_External_Scripts,
External_Scripts) from library-level options (No_Stdlib, No_IO, No_Prelude, No_JSON)
into two distinct enum types. Add Library_Options as a parameter to the ChaiScript
constructor. Update tests to demonstrate both ChaiScript_Basic (explicit Std_Lib::library
call) and ChaiScript (library options via constructor parameter) usage.

Requested by @lefticus in PR #642 review.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add cheatsheet documentation for Options and Library_Options

Documents the two-enum configuration system: Options (engine-level:
load_module, use, eval_file) and Library_Options (stdlib-level:
No_Stdlib, No_IO, No_Prelude, No_JSON), with usage examples for
both ChaiScript and ChaiScript_Basic constructors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: leftibot <leftibot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
leftibot 2026-04-11 16:49:13 -06:00 committed by GitHub
parent 005f18feb2
commit 08281a9d69
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 220 additions and 10 deletions

View File

@ -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`. 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<Options>` 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<Library_Options>`.
| 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 Things To The Engine
## Adding a Function / Method / Member ## Adding a Function / Method / Member

View File

@ -824,8 +824,9 @@ namespace chaiscript {
public: public:
ChaiScript(std::vector<std::string> t_modulepaths = {}, ChaiScript(std::vector<std::string> t_modulepaths = {},
std::vector<std::string> t_usepaths = {}, std::vector<std::string> t_usepaths = {},
std::vector<Options> t_opts = chaiscript::default_options()) std::vector<Options> t_opts = chaiscript::default_options(),
: ChaiScript_Basic(chaiscript::Std_Lib::library(), std::vector<Library_Options> t_lib_opts = {})
: ChaiScript_Basic(chaiscript::Std_Lib::library(t_lib_opts),
std::make_unique<parser::ChaiScript_Parser<eval::Noop_Tracer, optimizer::Optimizer_Default>>(), std::make_unique<parser::ChaiScript_Parser<eval::Noop_Tracer, optimizer::Optimizer_Default>>(),
std::move(t_modulepaths), std::move(t_modulepaths),
std::move(t_usepaths), std::move(t_usepaths),

View File

@ -211,6 +211,13 @@ namespace chaiscript {
External_Scripts External_Scripts
}; };
enum class Library_Options {
No_Stdlib,
No_IO,
No_Prelude,
No_JSON
};
template<typename From, typename To> template<typename From, typename To>
struct is_nothrow_forward_constructible : std::bool_constant<noexcept(To{std::declval<From>()})> { struct is_nothrow_forward_constructible : std::bool_constant<noexcept(To{std::declval<From>()})> {
}; };

View File

@ -38,9 +38,18 @@
namespace chaiscript { namespace chaiscript {
class Std_Lib { class Std_Lib {
public: public:
[[nodiscard]] static ModulePtr library() { [[nodiscard]] static ModulePtr library(const std::vector<Library_Options> &t_opts = {}) {
if (std::find(t_opts.begin(), t_opts.end(), Library_Options::No_Stdlib) != t_opts.end()) {
return std::make_shared<Module>();
}
auto lib = std::make_shared<Module>(); auto lib = std::make_shared<Module>();
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<std::vector<Boxed_Value>>("Vector", *lib); bootstrap::standard_library::vector_type<std::vector<Boxed_Value>>("Vector", *lib);
bootstrap::standard_library::string_type<std::string>("string", *lib); bootstrap::standard_library::string_type<std::string>("string", *lib);
@ -53,9 +62,13 @@ namespace chaiscript {
// with thread tracking to prevent heap-use-after-free on engine destruction. // with thread tracking to prevent heap-use-after-free on engine destruction.
#endif #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; return lib;
} }

View File

@ -268,8 +268,8 @@ namespace chaiscript::bootstrap {
public: public:
/// \brief perform all common bootstrap functions for std::string, void and POD types /// \brief perform all common bootstrap functions for std::string, void and POD types
/// \param[in,out] m Module to add bootstrapped functions to /// \param[in,out] m Module to add bootstrapped functions to
/// \returns passed in Module /// \param[in] t_no_io If true, skip registering print_string and println_string
static void bootstrap(Module &m) { static void bootstrap(Module &m, const bool t_no_io = false) {
m.add(user_type<void>(), "void"); m.add(user_type<void>(), "void");
m.add(user_type<bool>(), "bool"); m.add(user_type<bool>(), "bool");
m.add(user_type<Boxed_Value>(), "Object"); m.add(user_type<Boxed_Value>(), "Object");
@ -437,8 +437,10 @@ namespace chaiscript::bootstrap {
m.add(fun(&Build_Info::compiler_id), "compiler_id"); m.add(fun(&Build_Info::compiler_id), "compiler_id");
m.add(fun(&Build_Info::debug_build), "debug_build"); m.add(fun(&Build_Info::debug_build), "debug_build");
m.add(fun(&print), "print_string"); if (!t_no_io) {
m.add(fun(&println), "println_string"); m.add(fun(&print), "print_string");
m.add(fun(&println), "println_string");
}
m.add(dispatch::make_dynamic_proxy_function(&bind_function), "bind"); m.add(dispatch::make_dynamic_proxy_function(&bind_function), "bind");

View File

@ -1343,6 +1343,132 @@ TEST_CASE("Test if non copyable/movable types can be registered") {
chai.add(chaiscript::constructor<Nothing()>(), "Nothing"); chai.add(chaiscript::constructor<Nothing()>(), "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<int>("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<int>("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<int>("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<int>("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<int>("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<int>("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<int>("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<int>("5 + 3") == 8);
}
// Issue #421: Class with type_conversion from int and "==" operator // Issue #421: Class with type_conversion from int and "==" operator
// causes switch statement to compare destroyed objects. // causes switch statement to compare destroyed objects.
// The switch case comparison must use Function_Push_Pop to properly // The switch case comparison must use Function_Push_Pop to properly