Address review: define println in terms of print, expose set_print_handler to ChaiScript

Remove separate println_handler — println_string now dispatches through the
single print handler with a newline appended. Only set_print_handler is
needed to redirect all output. The set_print_handler function is also
registered in the ChaiScript engine, so scripts can capture and redirect
their own output.

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 16:51:41 -06:00
parent 973f9c554c
commit e2aa3e929e
4 changed files with 44 additions and 38 deletions

View File

@ -818,18 +818,14 @@ m.is_type("MyClass") // true (checks the ChaiScript class name)
## IO Redirection
By default, ChaiScript's `print()` and `puts()` functions write to stdout. You can redirect
output on a per-instance basis using custom handlers. Each ChaiScript instance can have its
own independent handlers.
output on a per-instance basis by setting a single print handler. Both `println_string`
(used by `print()`) and `print_string` (used by `puts()`) dispatch through the same handler —
`println_string` simply appends a newline before calling it.
```cpp
chaiscript::ChaiScript chai;
// Redirect print() / println_string() output (includes newline)
chai.set_println_handler([](const std::string &s) {
my_log_window.append(s + "\n");
});
// Redirect puts() / print_string() output (no newline)
// Redirect all output (print_string and println_string both use this handler)
chai.set_print_handler([](const std::string &s) {
my_log_window.append(s);
});
@ -841,9 +837,6 @@ context where stdout is not the desired output destination.
```cpp
// Example: capture all output to a string
std::string captured;
chai.set_println_handler([&captured](const std::string &s) {
captured += s + "\n";
});
chai.set_print_handler([&captured](const std::string &s) {
captured += s;
});
@ -852,5 +845,12 @@ chai.eval("print(42)"); // captured == "42\n"
chai.eval("puts(\"hi\")"); // captured == "42\nhi"
```
The print handler can also be set from within ChaiScript itself via `set_print_handler`:
```chaiscript
// Redirect output from within a script
set_print_handler(fun(s) { my_custom_log(s) })
```
## Extras
ChaiScript itself does not provide a link to the math functions defined in `<cmath>`. You can either add them yourself, or use the [ChaiScript_Extras](https://github.com/ChaiScript/ChaiScript_Extras) helper library. (Which also provides some additional string functions.)

View File

@ -438,7 +438,7 @@ 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/set_println_handler
// to support per-instance IO redirection via set_print_handler
m.add(dispatch::make_dynamic_proxy_function(&bind_function), "bind");

View File

@ -83,10 +83,6 @@ namespace chaiscript {
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 {
@ -133,7 +129,11 @@ namespace chaiscript {
}
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](const std::string &s) { m_print_handler(s + "\n"); }), "println_string");
m_engine.add(fun([this](const std::function<void(const std::string &)> &t_handler) {
m_print_handler = t_handler;
}), "set_print_handler");
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");
@ -268,18 +268,12 @@ namespace chaiscript {
public:
/// \brief Set a custom handler for print_string (no newline), used by ChaiScript's puts()
/// \brief Set a custom handler for print output, used by both print_string and println_string
/// \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

@ -1442,22 +1442,17 @@ TEST_CASE("IO redirection with set_print_handler") {
std::string captured_output;
// Set custom print handler (for print_string, called by puts)
// Set custom print handler — both print_string and println_string dispatch through it
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
// Test that print() uses the custom handler (println_string appends newline before calling handler)
captured_output.clear();
chai.eval("print(\"world\")");
CHECK(captured_output == "world\n");
@ -1467,7 +1462,7 @@ TEST_CASE("IO redirection with set_print_handler") {
chai.eval("print_string(\"direct\")");
CHECK(captured_output == "direct");
// Test that println_string() directly uses the custom handler
// Test that println_string() directly uses the custom handler with newline
captured_output.clear();
chai.eval("println_string(\"direct_ln\")");
CHECK(captured_output == "direct_ln\n");
@ -1477,8 +1472,8 @@ 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.set_print_handler([&captured_output](const std::string &s) {
captured_output += s;
});
chai.eval("print(42)");
@ -1492,14 +1487,31 @@ TEST_CASE("IO redirection different instances are independent") {
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.set_print_handler([&output1](const std::string &s) { output1 += s; });
chai2.set_print_handler([&output2](const std::string &s) { output2 += s; });
chai1.eval("print(\"from1\")");
chai2.eval("print(\"from2\")");
CHECK(output1 == "from1");
CHECK(output2 == "from2");
CHECK(output1 == "from1\n");
CHECK(output2 == "from2\n");
}
TEST_CASE("set_print_handler accessible from ChaiScript") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
auto captured = std::make_shared<std::string>();
chai.add(chaiscript::fun([captured](const std::string &s) { *captured += s; }), "test_output_sink");
// Set the print handler from within ChaiScript
chai.eval("set_print_handler(fun(s) { test_output_sink(s) })");
chai.eval("print(\"from_script\")");
CHECK(*captured == "from_script\n");
captured->clear();
chai.eval("puts(\"no_newline\")");
CHECK(*captured == "no_newline");
}
// Regression test: push_back() on script-created vector has no effect when