mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2026-04-30 19:09:26 +08:00
* 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> * Address review: add IO redirection section to cheatsheet Documents set_print_handler() and set_println_handler() with usage examples for GUI embedding and output capture. Requested by @lefticus in PR #657 review. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * 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> * 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> --------- Co-authored-by: leftibot <leftibot@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
dcdab50efd
commit
bcd07e05cd
@ -876,5 +876,42 @@ m.is_type("MyClass") // true (checks the ChaiScript class name)
|
|||||||
* `from_json` converts a JSON string into its strongly typed (map, vector, int, double, string) representations
|
* `from_json` converts a JSON string into its strongly typed (map, vector, int, double, string) representations
|
||||||
* `to_json` converts a ChaiScript object (either a `Object` or one of map, vector, int, double, string) tree into its JSON string representation
|
* `to_json` converts a ChaiScript object (either a `Object` or one of map, vector, int, double, string) tree into its JSON string representation
|
||||||
|
|
||||||
|
## IO Redirection
|
||||||
|
|
||||||
|
By default, ChaiScript's `print()` and `puts()` functions write to stdout. You can redirect
|
||||||
|
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 all output (print_string and println_string both use this handler)
|
||||||
|
chai.set_print_handler([](const std::string &s) {
|
||||||
|
my_log_window.append(s);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
This is useful for embedding ChaiScript in GUI applications, logging frameworks, or any
|
||||||
|
context where stdout is not the desired output destination.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Example: capture all output to a string
|
||||||
|
std::string captured;
|
||||||
|
chai.set_print_handler([&captured](const std::string &s) {
|
||||||
|
captured += s;
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
## 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.)
|
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.)
|
||||||
|
|||||||
@ -830,7 +830,8 @@ namespace chaiscript {
|
|||||||
std::make_unique<parser::ChaiScript_Parser<eval::Noop_Tracer, optimizer::Optimizer_Default>>(),
|
std::make_unique<parser::ChaiScript_Parser<eval::Noop_Tracer, optimizer::Optimizer_Default>>(),
|
||||||
std::move(t_modulepaths),
|
std::move(t_modulepaths),
|
||||||
std::move(t_usepaths),
|
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
|
} // namespace chaiscript
|
||||||
|
|||||||
@ -437,10 +437,10 @@ namespace chaiscript::bootstrap {
|
|||||||
m.add(fun(&Build_Info::compiler_id), "compiler_id");
|
m.add(fun(&Build_Info::compiler_id), "compiler_id");
|
||||||
m.add(fun(&Build_Info::debug_build), "debug_build");
|
m.add(fun(&Build_Info::debug_build), "debug_build");
|
||||||
|
|
||||||
if (!t_no_io) {
|
// print_string and println_string are registered in ChaiScript_Basic::build_eval_system()
|
||||||
m.add(fun(&print), "print_string");
|
// to support per-instance IO redirection via set_print_handler.
|
||||||
m.add(fun(&println), "println_string");
|
// 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");
|
m.add(dispatch::make_dynamic_proxy_function(&bind_function), "bind");
|
||||||
|
|
||||||
|
|||||||
@ -79,6 +79,8 @@ namespace chaiscript {
|
|||||||
|
|
||||||
std::map<std::string, std::function<Namespace &()>> m_namespace_generators;
|
std::map<std::string, std::function<Namespace &()>> m_namespace_generators;
|
||||||
|
|
||||||
|
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
|
/// 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) {
|
Boxed_Value do_eval(const std::string &t_input, const std::string &t_filename = "__EVAL__", bool /* t_internal*/ = false) {
|
||||||
try {
|
try {
|
||||||
@ -119,11 +121,24 @@ namespace chaiscript {
|
|||||||
chaiscript::detail::Dispatch_Engine &get_eval_engine() noexcept { return m_engine; }
|
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.
|
/// 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) {
|
if (t_lib) {
|
||||||
add(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");
|
||||||
|
|
||||||
|
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]() { 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) { 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");
|
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");
|
||||||
@ -256,7 +271,13 @@ namespace chaiscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
/// \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 Virtual destructor for ChaiScript
|
/// \brief Virtual destructor for ChaiScript
|
||||||
virtual ~ChaiScript_Basic() = default;
|
virtual ~ChaiScript_Basic() = default;
|
||||||
|
|
||||||
@ -268,7 +289,8 @@ namespace chaiscript {
|
|||||||
std::unique_ptr<parser::ChaiScript_Parser_Base> &&parser,
|
std::unique_ptr<parser::ChaiScript_Parser_Base> &&parser,
|
||||||
std::vector<std::string> t_module_paths = {},
|
std::vector<std::string> t_module_paths = {},
|
||||||
std::vector<std::string> t_use_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_module_paths(ensure_minimum_path_vec(std::move(t_module_paths)))
|
||||||
, m_use_paths(ensure_minimum_path_vec(std::move(t_use_paths)))
|
, m_use_paths(ensure_minimum_path_vec(std::move(t_use_paths)))
|
||||||
, m_parser(std::move(parser))
|
, m_parser(std::move(parser))
|
||||||
@ -303,7 +325,7 @@ namespace chaiscript {
|
|||||||
m_module_paths.insert(m_module_paths.begin(), dllpath + "/");
|
m_module_paths.insert(m_module_paths.begin(), dllpath + "/");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
build_eval_system(t_lib, t_opts);
|
build_eval_system(t_lib, t_opts, t_no_io);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef CHAISCRIPT_NO_DYNLOAD
|
#ifndef CHAISCRIPT_NO_DYNLOAD
|
||||||
|
|||||||
@ -1360,17 +1360,26 @@ TEST_CASE("ChaiScript_Basic No_Stdlib option disables all standard library funct
|
|||||||
CHECK_THROWS(chai.eval("from_json(\"[1,2,3]\")"));
|
CHECK_THROWS(chai.eval("from_json(\"[1,2,3]\")"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("ChaiScript_Basic No_IO option disables print functions") {
|
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}),
|
chaiscript::ChaiScript_Basic chai(chaiscript::Std_Lib::library({chaiscript::Library_Options::No_IO}),
|
||||||
create_chaiscript_parser(),
|
create_chaiscript_parser(),
|
||||||
{},
|
{},
|
||||||
{},
|
{},
|
||||||
{chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts});
|
{chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts},
|
||||||
|
true);
|
||||||
|
|
||||||
CHECK_THROWS(chai.eval("print_string(\"hello\")"));
|
// print_string and println_string should still be available via the handler mechanism
|
||||||
CHECK_THROWS(chai.eval("println_string(\"hello\")"));
|
// 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(chai.eval<int>("5 + 3") == 8);
|
||||||
CHECK_NOTHROW(chai.eval("var v = Vector()"));
|
CHECK_NOTHROW(chai.eval("var v = Vector()"));
|
||||||
|
|
||||||
|
// Users can set their own print handler even with No_IO
|
||||||
|
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_Basic No_Prelude option disables prelude functions") {
|
TEST_CASE("ChaiScript_Basic No_Prelude option disables prelude functions") {
|
||||||
@ -1431,10 +1440,18 @@ TEST_CASE("ChaiScript No_IO option via library options parameter") {
|
|||||||
{chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts},
|
{chaiscript::Options::No_Load_Modules, chaiscript::Options::No_External_Scripts},
|
||||||
{chaiscript::Library_Options::No_IO});
|
{chaiscript::Library_Options::No_IO});
|
||||||
|
|
||||||
CHECK_THROWS(chai.eval("print_string(\"hello\")"));
|
// print_string and println_string remain available via the handler mechanism
|
||||||
CHECK_THROWS(chai.eval("println_string(\"hello\")"));
|
// 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(chai.eval<int>("5 + 3") == 8);
|
||||||
CHECK_NOTHROW(chai.eval("var v = Vector()"));
|
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") {
|
TEST_CASE("ChaiScript No_Prelude option via library options parameter") {
|
||||||
@ -1607,6 +1624,83 @@ TEST_CASE("Issue 625: function_less_than strict-weak ordering with different ari
|
|||||||
CHECK(chai.eval<int>("overloaded(3, 2.0)") == 5);
|
CHECK(chai.eval<int>("overloaded(3, 2.0)") == 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 — both print_string and println_string dispatch through it
|
||||||
|
chai.set_print_handler([&captured_output](const std::string &s) {
|
||||||
|
captured_output += s;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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 (println_string appends newline before calling 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 with newline
|
||||||
|
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_print_handler([&captured_output](const std::string &s) {
|
||||||
|
captured_output += s;
|
||||||
|
});
|
||||||
|
|
||||||
|
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_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\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
|
// Regression test: push_back() on script-created vector has no effect when
|
||||||
// vector_conversion is in effect. The bug occurs because dispatch selects
|
// vector_conversion is in effect. The bug occurs because dispatch selects
|
||||||
// the C++ push_back for the converted type over the built-in one, operating
|
// the C++ push_back for the converted type over the built-in one, operating
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user