diff --git a/cheatsheet.md b/cheatsheet.md index 6a28d5b4..a0bd09fe 100644 --- a/cheatsheet.md +++ b/cheatsheet.md @@ -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 ``. 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.) diff --git a/include/chaiscript/dispatchkit/bootstrap.hpp b/include/chaiscript/dispatchkit/bootstrap.hpp index 7ebb8fd3..b0fe811e 100644 --- a/include/chaiscript/dispatchkit/bootstrap.hpp +++ b/include/chaiscript/dispatchkit/bootstrap.hpp @@ -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"); diff --git a/include/chaiscript/language/chaiscript_engine.hpp b/include/chaiscript/language/chaiscript_engine.hpp index 1bbafe57..13403f86 100644 --- a/include/chaiscript/language/chaiscript_engine.hpp +++ b/include/chaiscript/language/chaiscript_engine.hpp @@ -83,10 +83,6 @@ namespace chaiscript { fwrite(s.c_str(), 1, s.size(), stdout); }; - std::function 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 &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 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 t_handler) { - m_println_handler = std::move(t_handler); - } - /// \brief Virtual destructor for ChaiScript virtual ~ChaiScript_Basic() = default; diff --git a/unittests/compiled_tests.cpp b/unittests/compiled_tests.cpp index 18095f56..d6461b5f 100644 --- a/unittests/compiled_tests.cpp +++ b/unittests/compiled_tests.cpp @@ -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(); + 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