diff --git a/include/chaiscript/dispatchkit/bootstrap.hpp b/include/chaiscript/dispatchkit/bootstrap.hpp index ba1de5ad..17965ea6 100644 --- a/include/chaiscript/dispatchkit/bootstrap.hpp +++ b/include/chaiscript/dispatchkit/bootstrap.hpp @@ -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"); diff --git a/include/chaiscript/language/chaiscript_engine.hpp b/include/chaiscript/language/chaiscript_engine.hpp index c9f22e58..8b3675d1 100644 --- a/include/chaiscript/language/chaiscript_engine.hpp +++ b/include/chaiscript/language/chaiscript_engine.hpp @@ -78,6 +78,14 @@ namespace chaiscript { std::map> m_namespace_generators; + std::function m_print_handler = [](const std::string &s) noexcept { + 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 { @@ -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 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 27a99585..d24590a9 100644 --- a/unittests/compiled_tests.cpp +++ b/unittests/compiled_tests.cpp @@ -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"); +}