Address review: populate null print handler when No_IO is set

When No_IO is active, the default m_print_handler is now a no-op instead
of writing to stdout. The stdout handler is only installed when No_IO is
not set. Users can still override the handler via set_print_handler()
even with No_IO enabled.

Requested by @lefticus in PR #657 review.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
leftibot 2026-04-11 17:21:29 -06:00
parent 258eb63fd1
commit 66e945782e
4 changed files with 26 additions and 11 deletions

View File

@ -830,7 +830,8 @@ namespace chaiscript {
std::make_unique<parser::ChaiScript_Parser<eval::Noop_Tracer, optimizer::Optimizer_Default>>(),
std::move(t_modulepaths),
std::move(t_usepaths),
std::move(t_opts)) {
std::move(t_opts),
std::find(t_lib_opts.begin(), t_lib_opts.end(), Library_Options::No_IO) != t_lib_opts.end()) {
}
};
} // namespace chaiscript

View File

@ -439,8 +439,8 @@ namespace chaiscript::bootstrap {
// print_string and println_string are registered in ChaiScript_Basic::build_eval_system()
// 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.
// When No_IO is set, the functions are still registered but the default handler
// is a no-op, so users can provide their own print handlers without any stdout output.
m.add(dispatch::make_dynamic_proxy_function(&bind_function), "bind");

View File

@ -79,9 +79,7 @@ namespace chaiscript {
std::map<std::string, std::function<Namespace &()>> m_namespace_generators;
std::function<void(const std::string &)> m_print_handler = [](const std::string &s) noexcept {
fwrite(s.c_str(), 1, s.size(), stdout);
};
std::function<void(const std::string &)> m_print_handler = [](const std::string &) noexcept {};
/// 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) {
@ -123,11 +121,17 @@ namespace chaiscript {
chaiscript::detail::Dispatch_Engine &get_eval_engine() noexcept { return m_engine; }
/// Builds all the requirements for ChaiScript, including its evaluator and a run of its prelude.
void build_eval_system(const ModulePtr &t_lib, const std::vector<Options> &t_opts) {
void build_eval_system(const ModulePtr &t_lib, const std::vector<Options> &t_opts, const bool t_no_io = false) {
if (t_lib) {
add(t_lib);
}
if (!t_no_io) {
m_print_handler = [](const std::string &s) noexcept {
fwrite(s.c_str(), 1, s.size(), stdout);
};
}
m_engine.add(fun([this](const std::string &s) { m_print_handler(s); }), "print_string");
m_engine.add(fun([this](const std::string &s) { m_print_handler(s + "\n"); }), "println_string");
@ -285,7 +289,8 @@ namespace chaiscript {
std::unique_ptr<parser::ChaiScript_Parser_Base> &&parser,
std::vector<std::string> t_module_paths = {},
std::vector<std::string> t_use_paths = {},
const std::vector<chaiscript::Options> &t_opts = chaiscript::default_options())
const std::vector<chaiscript::Options> &t_opts = chaiscript::default_options(),
const bool t_no_io = false)
: m_module_paths(ensure_minimum_path_vec(std::move(t_module_paths)))
, m_use_paths(ensure_minimum_path_vec(std::move(t_use_paths)))
, m_parser(std::move(parser))
@ -320,7 +325,7 @@ namespace chaiscript {
m_module_paths.insert(m_module_paths.begin(), dllpath + "/");
}
#endif
build_eval_system(t_lib, t_opts);
build_eval_system(t_lib, t_opts, t_no_io);
}
#ifndef CHAISCRIPT_NO_DYNLOAD

View File

@ -1360,14 +1360,16 @@ TEST_CASE("ChaiScript_Basic No_Stdlib option disables all standard library funct
CHECK_THROWS(chai.eval("from_json(\"[1,2,3]\")"));
}
TEST_CASE("ChaiScript_Basic No_IO option still allows print_handler registration") {
TEST_CASE("ChaiScript_Basic No_IO option uses null handler by default") {
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});
{chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts},
true);
// print_string and println_string should still be available via the handler mechanism
// but the default handler is a no-op (no stdout output)
CHECK_NOTHROW(chai.eval("print_string(\"hello\")"));
CHECK_NOTHROW(chai.eval("println_string(\"hello\")"));
CHECK(chai.eval<int>("5 + 3") == 8);
@ -1439,10 +1441,17 @@ TEST_CASE("ChaiScript No_IO option via library options parameter") {
{chaiscript::Library_Options::No_IO});
// print_string and println_string remain available via the handler mechanism
// but the default handler is a no-op (no stdout output)
CHECK_NOTHROW(chai.eval("print_string(\"hello\")"));
CHECK_NOTHROW(chai.eval("println_string(\"hello\")"));
CHECK(chai.eval<int>("5 + 3") == 8);
CHECK_NOTHROW(chai.eval("var v = Vector()"));
// Users can override the null handler with their own
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 No_Prelude option via library options parameter") {