Fix #571: Add per-instance IO redirection via set_print_handler/set_println_handler

The print_string and println_string functions were previously registered as static
functions writing directly to stdout, making it impossible to redirect ChaiScript
output to custom destinations (e.g., GUI windows, loggers, or buffers). This moves
their registration from Bootstrap::bootstrap() to ChaiScript_Basic::build_eval_system()
as lambdas that dispatch through configurable std::function handlers, allowing each
ChaiScript instance to independently redirect its output via set_print_handler() and
set_println_handler().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
leftibot 2026-04-11 11:28:15 -06:00
parent fa4b8277f5
commit 28b585d92b
3 changed files with 91 additions and 3 deletions

View File

@ -435,8 +435,8 @@ 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");
// print_string and println_string are registered in ChaiScript_Basic::build_eval_system()
// to support per-instance IO redirection via set_print_handler/set_println_handler
m.add(dispatch::make_dynamic_proxy_function(&bind_function), "bind");

View File

@ -78,6 +78,14 @@ 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_println_handler = [](const std::string &s) noexcept {
puts(s.c_str());
};
/// 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) {
try {
@ -123,6 +131,9 @@ namespace chaiscript {
add(t_lib);
}
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_println_handler(s); }), "println_string");
m_engine.add(fun([this]() { m_engine.dump_system(); }), "dump_system");
m_engine.add(fun([this](const Boxed_Value &t_bv) { m_engine.dump_object(t_bv); }), "dump_object");
m_engine.add(fun([this](const Boxed_Value &t_bv, const std::string &t_type) { return m_engine.is_type(t_bv, t_type); }), "is_type");
@ -236,7 +247,19 @@ namespace chaiscript {
}
public:
/// \brief Set a custom handler for print_string (no newline), used by ChaiScript's puts()
/// \param[in] t_handler Function to call with the string to print
void set_print_handler(std::function<void(const std::string &)> t_handler) {
m_print_handler = std::move(t_handler);
}
/// \brief Set a custom handler for println_string (with newline), used by ChaiScript's print()
/// \param[in] t_handler Function to call with the string to print (handler should append newline if desired)
void set_println_handler(std::function<void(const std::string &)> t_handler) {
m_println_handler = std::move(t_handler);
}
/// \brief Virtual destructor for ChaiScript
virtual ~ChaiScript_Basic() = default;

View File

@ -1436,3 +1436,68 @@ TEST_CASE("Issue #421 - Switch with type_conversion does not compare destroyed o
result
})") == 0);
}
TEST_CASE("IO redirection with set_print_handler") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
std::string captured_output;
// Set custom print handler (for print_string, called by puts)
chai.set_print_handler([&captured_output](const std::string &s) {
captured_output += s;
});
// Set custom println handler (for println_string, called by print)
chai.set_println_handler([&captured_output](const std::string &s) {
captured_output += s + "\n";
});
// Test that puts() uses the custom handler
captured_output.clear();
chai.eval("puts(\"hello\")");
CHECK(captured_output == "hello");
// Test that print() uses the custom handler
captured_output.clear();
chai.eval("print(\"world\")");
CHECK(captured_output == "world\n");
// Test that print_string() directly uses the custom handler
captured_output.clear();
chai.eval("print_string(\"direct\")");
CHECK(captured_output == "direct");
// Test that println_string() directly uses the custom handler
captured_output.clear();
chai.eval("println_string(\"direct_ln\")");
CHECK(captured_output == "direct_ln\n");
}
TEST_CASE("IO redirection captures numeric output") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
std::string captured_output;
chai.set_println_handler([&captured_output](const std::string &s) {
captured_output += s + "\n";
});
chai.eval("print(42)");
CHECK(captured_output == "42\n");
}
TEST_CASE("IO redirection different instances are independent") {
chaiscript::ChaiScript_Basic chai1(create_chaiscript_stdlib(), create_chaiscript_parser());
chaiscript::ChaiScript_Basic chai2(create_chaiscript_stdlib(), create_chaiscript_parser());
std::string output1;
std::string output2;
chai1.set_println_handler([&output1](const std::string &s) { output1 += s; });
chai2.set_println_handler([&output2](const std::string &s) { output2 += s; });
chai1.eval("print(\"from1\")");
chai2.eval("print(\"from2\")");
CHECK(output1 == "from1");
CHECK(output2 == "from2");
}