When using the "sandboxed" libraries, any functions that allow
lua to communicate with some global state outside of the lua VM are removed.
This includes dofile and
loadfile, it includes accessing
the C standard rand function,
and it includes print even.
Usually, when you use the sandboxed base library, you'll want to install
a new print right away
that directs input to where you would like it.
The api::print_manager class is a relatively powerful
API feature that can handle that and some related tasks.
The print_manager feature
adds two global functions to the lua environment:
print
_pretty_print
These functions format their arguments in slightly
different ways. print uses
the same formatting as the built-in lua print.
_pretty_print tries to
display small tables in a readable format.
They both send their output to the current interpreter_context.
interpreter_context is
a C++ concept which we'll define shortly. An interpreter_context
is set by installing it in the print_manager
using the set_interpreter_context
method.
If no interpreter context is set, then formatted output is piped to std::cout.
The print_manager actually
maintains a stack of pointers to interpreter_context's.
The method set_interpreter_context
pushes a pointer onto this stack, and pop_interpreter_context
pops from this stack, unless it is empty.
Besdies redirecting output, the print_manager
can also act to provide debug-console functionality. It can take a user
input string and execute it in the lua environment, through the handle_interpreter_input method. The
string passed to it should be the text typed by the user. The results of
this are relayed to the interpreter context, by calling its methods.
The interpreter_context
concept consists of three methods:
void new_text(const std::string &); void error_text(const std::string &); void clear_input();
new_text is called
to report a line of print output.
error_text is called
to report an error message.
clear_input is called
when the user's input has been accepted. When creating a gui dialog,
one option is to clear the editbox whenever the user presses enter.
However, sometimes they made a typo and caused a syntax error or something
-- if you clear the input, they have to type it again. You can use
clear_input as the
cue to clear the editbox instead, then it will only be cleared when
the input was successfully parsed.
Besides redirecting output and providing a special method for handling
user text input, the print_manager
behavior can be further customized by providing custom implementations
of print and _pretty_print. Any function which takes
a lua_State *
and produces a std::string could be used to replace the default
formatting.
namespace api { class print_manager { std::vector<detail::interpreter_context_ptr> stack_; using format_func_t = std::string (*)(lua_State *); // Pointers for custom print / pretty print formatting functions format_func_t print_format_ = nullptr; format_func_t pretty_print_format_ = nullptr; public: // Add or remove interpreter context pointers from the stack template <typename T> void set_interpreter_context(T * t) { stack_.emplace_back(t); } void pop_interpreter_context() { if (stack_.size()) { stack_.pop_back(); } } // Set a custom print or pretty-print formatting // Function should take a `lua_State *` and return `std::string`. // Do whatever you like with the stack. Don't raise errors. void set_custom_print_format_func(format_func_t f) { print_format_ = f; } void set_custom_pretty_print_format_func(format_func_t f) { pretty_print_format_ = f; } // Interact directly with context on top of stack, or, // with stdout / stderr if stack is empty void new_text(const std::string & str) const { if (stack_.size()) { stack_.back().new_text(str); } else { std::cout << str << std::endl; } } void error_text(const std::string & str) const { if (stack_.size()) { stack_.back().error_text(str); } else { std::cerr << str << std::endl; } } void clear_input() const { if (stack_.size()) { stack_.back().clear_input(); } } // Handle interpreter input // Note: Clears the entire stack when it runs. inline void handle_interpreter_input(lua_State * L, const std::string & user_input); // Convenience function over the above. // Recover the print_manager from a lua_State * pointer, then setup an // arbitrary interpreter context, run a command, and pop the context. // // Note: There must be a print manager attached to the lua_State * or an // assertion will fail. template <typename T> static void interpreter_input(lua_State * L, T & t, const std::string & s) { print_manager * man = recover_self(L); man->set_interpreter_context(&t); man->handle_interpreter_input(L, s); man->pop_interpreter_context(); } // // API Feature // void on_init(lua_State * L) { PRIMER_ASSERT_STACK_NEUTRAL(L); registry_helper<print_manager>::store(L, this); lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS); primer::set_funcs(L, get_funcs()); lua_pop(L, 1); } void on_persist_table(lua_State * L) { primer::set_funcs_prefix_reverse(L, "print_manager__", get_funcs()); } void on_unpersist_table(lua_State * L) { primer::set_funcs_prefix(L, "print_manager__", get_funcs()); } }; } // end namespace api