ChaiScript/include/chaiscript/language/chaiscript_engine.hpp
leftibot bcd07e05cd
Fix #571: How to redirect "cout" and "print" to a microsoft Windows 10 window? (#657)
* 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>
2026-04-11 18:58:09 -06:00

759 lines
33 KiB
C++

// This file is distributed under the BSD License.
// See "license.txt" for details.
// Copyright 2009-2012, Jonathan Turner (jonathan@emptycrate.com)
// Copyright 2009-2018, Jason Turner (jason@emptycrate.com)
// http://www.chaiscript.com
// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#ifndef CHAISCRIPT_ENGINE_HPP_
#define CHAISCRIPT_ENGINE_HPP_
#include <cassert>
#include <cstring>
#include <exception>
#include <fstream>
#include <functional>
#include <future>
#include <map>
#include <memory>
#include <mutex>
#include <set>
#include <stdexcept>
#include <vector>
#include "../chaiscript_defines.hpp"
#include "../chaiscript_threading.hpp"
#include "../dispatchkit/boxed_cast_helper.hpp"
#include "../dispatchkit/boxed_value.hpp"
#include "../dispatchkit/dispatchkit.hpp"
#include "../dispatchkit/proxy_functions.hpp"
#include "../dispatchkit/register_function.hpp"
#include "../dispatchkit/type_conversions.hpp"
#include "chaiscript_common.hpp"
#if defined(__linux__) || defined(__unix__) || defined(__APPLE__) || defined(__HAIKU__)
#include <unistd.h>
#endif
#if !defined(CHAISCRIPT_NO_DYNLOAD) && defined(_POSIX_VERSION) && !defined(__CYGWIN__)
#include <dlfcn.h>
#endif
#if defined(CHAISCRIPT_NO_DYNLOAD)
#include "chaiscript_unknown.hpp"
#elif defined(CHAISCRIPT_WINDOWS)
#include "chaiscript_windows.hpp"
#elif _POSIX_VERSION
#include "chaiscript_posix.hpp"
#else
#include "chaiscript_unknown.hpp"
#endif
#include "../dispatchkit/exception_specification.hpp"
namespace chaiscript {
/// Namespace alias to provide cleaner and more explicit syntax to users.
using Namespace = dispatch::Dynamic_Object;
namespace detail {
using Loadable_Module_Ptr = std::shared_ptr<Loadable_Module>;
}
/// \brief The main object that the ChaiScript user will use.
class ChaiScript_Basic {
mutable chaiscript::detail::threading::shared_mutex m_mutex;
mutable chaiscript::detail::threading::recursive_mutex m_use_mutex;
std::set<std::string> m_used_files;
std::map<std::string, detail::Loadable_Module_Ptr> m_loaded_modules;
std::set<std::string> m_active_loaded_modules;
std::vector<std::string> m_module_paths;
std::vector<std::string> m_use_paths;
std::unique_ptr<parser::ChaiScript_Parser_Base> m_parser;
chaiscript::detail::Dispatch_Engine m_engine;
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
Boxed_Value do_eval(const std::string &t_input, const std::string &t_filename = "__EVAL__", bool /* t_internal*/ = false) {
try {
const auto p = m_parser->parse(t_input, t_filename);
return p->eval(chaiscript::detail::Dispatch_State(m_engine));
} catch (chaiscript::eval::detail::Return_Value &rv) {
return rv.retval;
}
}
/// Evaluates the given file and looks in the 'use' paths
Boxed_Value internal_eval_file(const std::string &t_filename) {
for (const auto &path : m_use_paths) {
try {
const auto appendedpath = path + t_filename;
return do_eval(load_file(appendedpath), appendedpath, true);
} catch (const exception::file_not_found_error &) {
// failed to load, try the next path
} catch (const exception::eval_error &t_ee) {
throw Boxed_Value(t_ee);
}
}
// failed to load by any name
throw exception::file_not_found_error(t_filename);
}
/// Evaluates the given string, used during eval() inside of a script
Boxed_Value internal_eval(const std::string &t_e) {
try {
return do_eval(t_e, "__EVAL__", true);
} catch (const exception::eval_error &t_ee) {
throw Boxed_Value(t_ee);
}
}
/// Returns the current evaluation 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.
void build_eval_system(const ModulePtr &t_lib, const std::vector<Options> &t_opts, const bool t_no_io = false) {
if (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](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) { return m_engine.type_name(t_bv); }), "type_name");
m_engine.add(fun([this](const std::string &t_f) { return m_engine.function_exists(t_f); }), "function_exists");
m_engine.add(fun([this]() { return m_engine.get_function_objects(); }), "get_functions");
m_engine.add(fun([this]() { return m_engine.get_scripting_objects(); }), "get_objects");
m_engine.add(dispatch::make_dynamic_proxy_function([this](const Function_Params &t_params) { return m_engine.call_exists(t_params); }),
"call_exists");
m_engine.add(fun([this](const dispatch::Proxy_Function_Base &t_fun, const std::vector<Boxed_Value> &t_params) -> Boxed_Value {
Type_Conversions_State s(this->m_engine.conversions(), this->m_engine.conversions().conversion_saves());
return t_fun(Function_Params{t_params}, s);
}),
"call");
m_engine.add(fun([this](const Type_Info &t_ti) { return m_engine.get_type_name(t_ti); }), "name");
m_engine.add(fun([this](const std::string &t_type_name, bool t_throw) { return m_engine.get_type(t_type_name, t_throw); }), "type");
m_engine.add(fun([this](const std::string &t_type_name) { return m_engine.get_type(t_type_name, true); }), "type");
m_engine.add(fun([this](const Type_Info &t_from, const Type_Info &t_to, const std::function<Boxed_Value(const Boxed_Value &)> &t_func) {
m_engine.add(chaiscript::type_conversion(t_from, t_to, t_func));
}),
"add_type_conversion");
if (std::find(t_opts.begin(), t_opts.end(), Options::No_Load_Modules) == t_opts.end()
&& std::find(t_opts.begin(), t_opts.end(), Options::Load_Modules) != t_opts.end()) {
m_engine.add(fun([this](const std::string &t_module, const std::string &t_file) { load_module(t_module, t_file); }), "load_module");
m_engine.add(fun([this](const std::string &t_module) { return load_module(t_module); }), "load_module");
}
if (std::find(t_opts.begin(), t_opts.end(), Options::No_External_Scripts) == t_opts.end()
&& std::find(t_opts.begin(), t_opts.end(), Options::External_Scripts) != t_opts.end()) {
m_engine.add(fun([this](const std::string &t_file) { return use(t_file); }), "use");
m_engine.add(fun([this](const std::string &t_file) { return internal_eval_file(t_file); }), "eval_file");
}
m_engine.add(fun([this](const std::string &t_str) { return internal_eval(t_str); }), "eval");
m_engine.add(fun([this](const AST_Node &t_ast) { return eval(t_ast); }), "eval");
m_engine.add(fun([this](const std::string &t_str, const bool t_dump) { return parse(t_str, t_dump); }), "parse");
m_engine.add(fun([this](const std::string &t_str) { return parse(t_str); }), "parse");
m_engine.add(fun([this](const Boxed_Value &t_bv, const std::string &t_name) { add_global_const(t_bv, t_name); }), "add_global_const");
m_engine.add(fun([this](const Boxed_Value &t_bv, const std::string &t_name) { add_global(t_bv, t_name); }), "add_global");
m_engine.add(fun([this](const Boxed_Value &t_bv, const std::string &t_name) { set_global(t_bv, t_name); }), "set_global");
// why this unused parameter to Namespace?
m_engine.add(fun([this](const std::string &t_namespace_name) {
register_namespace([](Namespace & /*space*/) noexcept {}, t_namespace_name);
import(t_namespace_name);
}),
"namespace");
m_engine.add(fun([this](const std::string &t_namespace_name) { import(t_namespace_name); }), "import");
#ifndef CHAISCRIPT_NO_THREADS
// Register async() with thread tracking so the engine can join all
// async threads before destroying shared state (issues #632, #636).
m_engine.add(chaiscript::fun(
[this](const std::function<chaiscript::Boxed_Value()> &t_func) {
auto promise_ptr = std::make_shared<std::promise<chaiscript::Boxed_Value>>();
auto future = promise_ptr->get_future();
m_engine.track_async_thread(std::thread([promise_ptr, t_func]() {
try {
promise_ptr->set_value(t_func());
} catch (...) {
promise_ptr->set_exception(std::current_exception());
}
}));
return future;
}),
"async");
#endif
}
/// Skip BOM at the beginning of file
static bool skip_bom(std::ifstream &infile) {
size_t bytes_needed = 3;
char buffer[3];
memset(buffer, '\0', bytes_needed);
infile.read(buffer, static_cast<std::streamsize>(bytes_needed));
if ((buffer[0] == '\xef') && (buffer[1] == '\xbb') && (buffer[2] == '\xbf')) {
infile.seekg(3);
return true;
}
infile.seekg(0);
return false;
}
/// Helper function for loading a file
static std::string load_file(const std::string &t_filename) {
std::ifstream infile(t_filename.c_str(), std::ios::in | std::ios::ate | std::ios::binary);
if (!infile.is_open()) {
throw chaiscript::exception::file_not_found_error(t_filename);
}
auto size = infile.tellg();
infile.seekg(0, std::ios::beg);
assert(size >= 0);
if (skip_bom(infile)) {
size -= 3; // decrement the BOM size from file size, otherwise we'll get parsing errors
assert(size >= 0); // and check if there's more text
}
if (size == std::streampos(0)) {
return std::string();
} else {
std::vector<char> v(static_cast<size_t>(size));
infile.read(&v[0], static_cast<std::streamsize>(size));
return std::string(v.begin(), v.end());
}
}
std::vector<std::string> ensure_minimum_path_vec(std::vector<std::string> paths) {
if (paths.empty()) {
return {""};
} else {
return paths;
}
}
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
virtual ~ChaiScript_Basic() = default;
/// \brief Constructor for ChaiScript
/// \param[in] t_lib Standard library to apply to this ChaiScript instance
/// \param[in] t_modulepaths Vector of paths to search when attempting to load a binary module
/// \param[in] t_usepaths Vector of paths to search when attempting to "use" an included ChaiScript file
ChaiScript_Basic(const ModulePtr &t_lib,
std::unique_ptr<parser::ChaiScript_Parser_Base> &&parser,
std::vector<std::string> t_module_paths = {},
std::vector<std::string> t_use_paths = {},
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_use_paths(ensure_minimum_path_vec(std::move(t_use_paths)))
, m_parser(std::move(parser))
, m_engine(*m_parser) {
#if !defined(CHAISCRIPT_NO_DYNLOAD) && defined(_POSIX_VERSION) && !defined(__CYGWIN__)
// If on Unix, add the path of the current executable to the module search path
// as windows would do
union cast_union {
Boxed_Value (ChaiScript_Basic::*in_ptr)(const std::string &);
void *out_ptr;
};
Dl_info rInfo;
memset(&rInfo, 0, sizeof(rInfo));
cast_union u;
u.in_ptr = &ChaiScript_Basic::use;
if ((dladdr(static_cast<void *>(u.out_ptr), &rInfo) != 0) && (rInfo.dli_fname != nullptr)) {
std::string dllpath(rInfo.dli_fname);
const size_t lastslash = dllpath.rfind('/');
if (lastslash != std::string::npos) {
dllpath.erase(lastslash);
}
// Let's see if this is a link that we should expand
std::vector<char> buf(2048);
const auto pathlen = readlink(dllpath.c_str(), &buf.front(), buf.size());
if (pathlen > 0 && static_cast<size_t>(pathlen) < buf.size()) {
dllpath = std::string(&buf.front(), static_cast<size_t>(pathlen));
}
m_module_paths.insert(m_module_paths.begin(), dllpath + "/");
}
#endif
build_eval_system(t_lib, t_opts, t_no_io);
}
#ifndef CHAISCRIPT_NO_DYNLOAD
/// \brief Constructor for ChaiScript.
///
/// This version of the ChaiScript constructor attempts to find the stdlib module to load
/// at runtime generates an error if it cannot be found.
///
/// \param[in] t_modulepaths Vector of paths to search when attempting to load a binary module
/// \param[in] t_usepaths Vector of paths to search when attempting to "use" an included ChaiScript file
explicit ChaiScript_Basic(std::unique_ptr<parser::ChaiScript_Parser_Base> &&parser,
std::vector<std::string> t_module_paths = {},
std::vector<std::string> t_use_paths = {},
const std::vector<chaiscript::Options> &t_opts = chaiscript::default_options())
: ChaiScript_Basic({}, std::move(parser), t_module_paths, t_use_paths, t_opts) {
try {
// attempt to load the stdlib
load_module("chaiscript_stdlib-" + Build_Info::version());
} catch (const exception::load_module_error &t_err) {
std::cout << "An error occurred while trying to load the chaiscript standard library.\n"
"\n"
"You must either provide a standard library, or compile it in.\n"
"For an example of compiling the standard library in,\n"
"see: https://gist.github.com/lefticus/9456197\n"
"Compiling the stdlib in is the recommended and MOST SUPPORTED method.\n"
"\n\n"
<< t_err.what();
throw;
}
}
#else // CHAISCRIPT_NO_DYNLOAD
explicit ChaiScript_Basic(std::unique_ptr<parser::ChaiScript_Parser_Base> &&parser,
std::vector<std::string> t_module_paths = {},
std::vector<std::string> t_use_paths = {},
const std::vector<chaiscript::Options> &t_opts = chaiscript::default_options())
= delete;
#endif
parser::ChaiScript_Parser_Base &get_parser() noexcept {
return *m_parser;
}
const Boxed_Value eval(const AST_Node &t_ast) {
try {
return t_ast.eval(chaiscript::detail::Dispatch_State(m_engine));
} catch (const exception::eval_error &t_ee) {
throw Boxed_Value(t_ee);
}
}
AST_NodePtr parse(const std::string &t_input, const bool t_debug_print = false) {
auto ast = m_parser->parse(t_input, "PARSE");
if (t_debug_print) {
m_parser->debug_print(*ast);
}
return ast;
}
std::string get_type_name(const Type_Info &ti) const { return m_engine.get_type_name(ti); }
template<typename T>
std::string get_type_name() const {
return get_type_name(user_type<T>());
}
/// \brief Loads and parses a file. If the file is already open, it is not
/// reloaded. The use paths specified at ChaiScript construction time are
/// searched for the requested file.
///
/// \param[in] t_filename Filename to load and evaluate
Boxed_Value use(const std::string &t_filename) {
for (const auto &path : m_use_paths) {
const auto appendedpath = path + t_filename;
try {
chaiscript::detail::threading::unique_lock<chaiscript::detail::threading::recursive_mutex> l(m_use_mutex);
chaiscript::detail::threading::unique_lock<chaiscript::detail::threading::shared_mutex> l2(m_mutex);
Boxed_Value retval;
if (m_used_files.count(appendedpath) == 0) {
l2.unlock();
retval = eval_file(appendedpath);
l2.lock();
m_used_files.insert(appendedpath);
}
return retval; // return, we loaded it, or it was already loaded
} catch (const exception::file_not_found_error &e) {
if (e.filename != appendedpath) {
// a nested file include failed
throw;
}
// failed to load, try the next path
}
}
// failed to load by any name
throw exception::file_not_found_error(t_filename);
}
/// \brief Adds a constant object that is available in all contexts and to all threads
/// \param[in] t_bv Boxed_Value to add as a global
/// \param[in] t_name Name of the value to add
/// \throw chaiscript::exception::global_non_const If t_bv is not a constant object
/// \sa Boxed_Value::is_const
ChaiScript_Basic &add_global_const(const Boxed_Value &t_bv, const std::string &t_name) {
Name_Validator::validate_object_name(t_name);
m_engine.add_global_const(t_bv, t_name);
return *this;
}
/// \brief Adds a mutable object that is available in all contexts and to all threads
/// \param[in] t_bv Boxed_Value to add as a global
/// \param[in] t_name Name of the value to add
/// \warning The user is responsible for making sure the object is thread-safe if necessary
/// ChaiScript is thread-safe but provides no threading locking mechanism to the script
ChaiScript_Basic &add_global(const Boxed_Value &t_bv, const std::string &t_name) {
Name_Validator::validate_object_name(t_name);
m_engine.add_global(t_bv, t_name);
return *this;
}
ChaiScript_Basic &set_global(const Boxed_Value &t_bv, const std::string &t_name) {
Name_Validator::validate_object_name(t_name);
m_engine.set_global(t_bv, t_name);
return *this;
}
/// \brief Represents the current state of the ChaiScript system. State and be saved and restored
/// \warning State object does not contain the user defined type conversions of the engine. They
/// are left out due to performance considerations involved in tracking the state
/// \sa ChaiScript::get_state
/// \sa ChaiScript::set_state
struct State {
std::set<std::string> used_files;
chaiscript::detail::Dispatch_Engine::State engine_state;
std::set<std::string> active_loaded_modules;
};
/// \brief Returns a state object that represents the current state of the global system
///
/// The global system includes the reserved words, global const objects, functions and types.
/// local variables are thread specific and not included.
///
/// \return Current state of the global system
///
/// \b Example:
///
/// \code
/// chaiscript::ChaiScript chai;
/// chaiscript::ChaiScript::State s = chai.get_state(); // represents bootstrapped initial state
/// \endcode
State get_state() const {
chaiscript::detail::threading::lock_guard<chaiscript::detail::threading::recursive_mutex> l(m_use_mutex);
chaiscript::detail::threading::shared_lock<chaiscript::detail::threading::shared_mutex> l2(m_mutex);
State s;
s.used_files = m_used_files;
s.engine_state = m_engine.get_state();
s.active_loaded_modules = m_active_loaded_modules;
return s;
}
/// \brief Sets the state of the system
///
/// The global system includes the reserved words, global objects, functions and types.
/// local variables are thread specific and not included.
///
/// \param[in] t_state New state to set
///
/// \b Example:
/// \code
/// chaiscript::ChaiScript chai;
/// chaiscript::ChaiScript::State s = chai.get_state(); // get initial state
/// chai.add(chaiscript::fun(&somefunction), "somefunction");
/// chai.set_state(s); // restore initial state, which does not have the recently added "somefunction"
/// \endcode
void set_state(const State &t_state) {
chaiscript::detail::threading::lock_guard<chaiscript::detail::threading::recursive_mutex> l(m_use_mutex);
chaiscript::detail::threading::shared_lock<chaiscript::detail::threading::shared_mutex> l2(m_mutex);
m_used_files = t_state.used_files;
m_active_loaded_modules = t_state.active_loaded_modules;
m_engine.set_state(t_state.engine_state);
}
/// \returns All values in the local thread state, added through the add() function
std::map<std::string, Boxed_Value> get_locals() const { return m_engine.get_locals(); }
/// \returns All accessible function objects, as a map of name to Boxed_Value
std::map<std::string, Boxed_Value> get_function_objects() const { return m_engine.get_function_objects(); }
/// \returns All accessible scripting objects (locals + globals), as a map of name to Boxed_Value
std::map<std::string, Boxed_Value> get_scripting_objects() const { return m_engine.get_scripting_objects(); }
/// \brief Sets all of the locals for the current thread state.
///
/// \param[in] t_locals The map<name, value> set of variables to replace the current state with
///
/// Any existing locals are removed and the given set of variables is added
void set_locals(const std::map<std::string, Boxed_Value> &t_locals) { m_engine.set_locals(t_locals); }
/// \brief Adds a type, function or object to ChaiScript. Objects are added to the local thread state.
/// \param[in] t_t Item to add
/// \param[in] t_name Name of item to add
/// \returns Reference to current ChaiScript object
///
/// \b Examples:
/// \code
/// chaiscript::ChaiScript chai;
/// chai.add(chaiscript::user_type<MyClass>(), "MyClass"); // Add explicit type info (not strictly necessary)
/// chai.add(chaiscript::fun(&MyClass::function), "function"); // Add a class method
/// MyClass obj;
/// chai.add(chaiscript::var(&obj), "obj"); // Add a pointer to a locally defined object
/// \endcode
///
/// \sa \ref adding_items
template<typename T>
ChaiScript_Basic &add(const T &t_t, const std::string &t_name) {
Name_Validator::validate_object_name(t_name);
m_engine.add(t_t, t_name);
return *this;
}
/// \brief Add a new conversion for upcasting to a base class
/// \sa chaiscript::base_class
/// \param[in] d Base class / parent class
///
/// \b Example:
/// \code
/// chaiscript::ChaiScript chai;
/// chai.add(chaiscript::base_class<std::runtime_error, chaiscript::dispatch_error>());
/// \endcode
ChaiScript_Basic &add(const Type_Conversion &d) {
m_engine.add(d);
return *this;
}
/// \brief Adds all elements of a module to ChaiScript runtime
/// \param[in] t_p The module to add.
/// \sa chaiscript::Module
ChaiScript_Basic &add(const ModulePtr &t_p) {
t_p->apply(*this, this->get_eval_engine());
return *this;
}
/// \brief Load a binary module from a dynamic library. Works on platforms that support
/// dynamic libraries.
/// \param[in] t_module_name Name of the module to load
///
/// The module is searched for in the registered module path folders (chaiscript::ChaiScript::ChaiScript)
/// and with standard prefixes and postfixes: ("lib"|"")\<t_module_name\>(".dll"|".so"|".bundle"|"").
///
/// Once the file is located, the system looks for the symbol "create_chaiscript_module_\<t_module_name\>".
/// If no file can be found matching the search criteria and containing the appropriate entry point
/// (the symbol mentioned above), an exception is thrown.
///
/// \throw chaiscript::exception::load_module_error In the event that no matching module can be found.
std::string load_module(const std::string &t_module_name) {
#ifdef CHAISCRIPT_NO_DYNLOAD
throw chaiscript::exception::load_module_error("Loadable module support was disabled (CHAISCRIPT_NO_DYNLOAD)");
#else
std::vector<exception::load_module_error> errors;
std::string version_stripped_name = t_module_name;
size_t version_pos = version_stripped_name.find("-" + Build_Info::version());
if (version_pos != std::string::npos) {
version_stripped_name.erase(version_pos);
}
std::vector<std::string> prefixes{"lib", "cyg", ""};
std::vector<std::string> postfixes{".dll", ".so", ".bundle", ""};
for (auto &elem : m_module_paths) {
for (auto &prefix : prefixes) {
for (auto &postfix : postfixes) {
try {
const auto name = elem + prefix + t_module_name + postfix;
// std::cerr << "trying location: " << name << '\n';
load_module(version_stripped_name, name);
return name;
} catch (const chaiscript::exception::load_module_error &e) {
// std::cerr << "error: " << e.what() << '\n';
errors.push_back(e);
// Try next set
}
}
}
}
throw chaiscript::exception::load_module_error(t_module_name, errors);
#endif
}
/// \brief Load a binary module from a dynamic library. Works on platforms that support
/// dynamic libraries.
///
/// \param[in] t_module_name Module name to load
/// \param[in] t_filename Ignore normal filename search process and use specific filename
///
/// \sa ChaiScript::load_module(const std::string &t_module_name)
void load_module(const std::string &t_module_name, const std::string &t_filename) {
chaiscript::detail::threading::lock_guard<chaiscript::detail::threading::recursive_mutex> l(m_use_mutex);
if (m_loaded_modules.count(t_module_name) == 0) {
detail::Loadable_Module_Ptr lm(new detail::Loadable_Module(t_module_name, t_filename));
m_loaded_modules[t_module_name] = lm;
m_active_loaded_modules.insert(t_module_name);
add(lm->m_moduleptr);
} else if (m_active_loaded_modules.count(t_module_name) == 0) {
m_active_loaded_modules.insert(t_module_name);
add(m_loaded_modules[t_module_name]->m_moduleptr);
}
}
/// \brief Evaluates a string. Equivalent to ChaiScript::eval.
///
/// \param[in] t_script Script to execute
/// \param[in] t_handler Optional Exception_Handler used for automatic unboxing of script thrown exceptions
///
/// \return result of the script execution
///
/// \throw chaiscript::exception::eval_error In the case that evaluation fails.
Boxed_Value operator()(const std::string &t_script, const Exception_Handler &t_handler = Exception_Handler()) {
return eval(t_script, t_handler);
}
/// \brief Evaluates a string and returns a typesafe result.
///
/// \tparam T Type to extract from the result value of the script execution
/// \param[in] t_input Script to execute
/// \param[in] t_handler Optional Exception_Handler used for automatic unboxing of script thrown exceptions
/// \param[in] t_filename Optional filename to report to the user for where the error occured. Useful
/// in special cases where you are loading a file internally instead of using eval_file
///
/// \return result of the script execution
///
/// \throw chaiscript::exception::eval_error In the case that evaluation fails.
/// \throw chaiscript::exception::bad_boxed_cast In the case that evaluation succeeds but the result value cannot be converted
/// to the requested type.
template<typename T>
T eval(const std::string &t_input, const Exception_Handler &t_handler = Exception_Handler(), const std::string &t_filename = "__EVAL__") {
return m_engine.boxed_cast<T>(eval(t_input, t_handler, t_filename));
}
/// \brief casts an object while applying any Dynamic_Conversion available
template<typename Type>
decltype(auto) boxed_cast(const Boxed_Value &bv) const {
return (m_engine.boxed_cast<Type>(bv));
}
/// \brief Evaluates a string.
///
/// \param[in] t_input Script to execute
/// \param[in] t_handler Optional Exception_Handler used for automatic unboxing of script thrown exceptions
/// \param[in] t_filename Optional filename to report to the user for where the error occurred. Useful
/// in special cases where you are loading a file internally instead of using eval_file
///
/// \return result of the script execution
///
/// \throw exception::eval_error In the case that evaluation fails.
Boxed_Value
eval(const std::string &t_input, const Exception_Handler &t_handler = Exception_Handler(), const std::string &t_filename = "__EVAL__") {
try {
return do_eval(t_input, t_filename);
} catch (Boxed_Value &bv) {
if (t_handler) {
t_handler->handle(bv, m_engine);
}
throw;
}
}
/// \brief Loads the file specified by filename, evaluates it, and returns the result.
/// \param[in] t_filename File to load and parse.
/// \param[in] t_handler Optional Exception_Handler used for automatic unboxing of script thrown exceptions
/// \return result of the script execution
/// \throw chaiscript::exception::eval_error In the case that evaluation fails.
Boxed_Value eval_file(const std::string &t_filename, const Exception_Handler &t_handler = Exception_Handler()) {
return eval(load_file(t_filename), t_handler, t_filename);
}
/// \brief Loads the file specified by filename, evaluates it, and returns the type safe result.
/// \tparam T Type to extract from the result value of the script execution
/// \param[in] t_filename File to load and parse.
/// \param[in] t_handler Optional Exception_Handler used for automatic unboxing of script thrown exceptions
/// \return result of the script execution
/// \throw chaiscript::exception::eval_error In the case that evaluation fails.
/// \throw chaiscript::exception::bad_boxed_cast In the case that evaluation succeeds but the result value cannot be converted
/// to the requested type.
template<typename T>
T eval_file(const std::string &t_filename, const Exception_Handler &t_handler = Exception_Handler()) {
return m_engine.boxed_cast<T>(eval_file(t_filename, t_handler));
}
/// \brief Imports a namespace object into the global scope of this ChaiScript instance.
/// \param[in] t_namespace_name Name of the namespace to import.
/// \throw std::runtime_error In the case that the namespace name was never registered.
void import(const std::string &t_namespace_name) {
chaiscript::detail::threading::unique_lock<chaiscript::detail::threading::recursive_mutex> l(m_use_mutex);
if (m_engine.get_scripting_objects().count(t_namespace_name)) {
throw std::runtime_error("Namespace: " + t_namespace_name + " was already defined");
} else if (m_namespace_generators.count(t_namespace_name)) {
m_engine.add_global(var(std::ref(m_namespace_generators[t_namespace_name]())), t_namespace_name);
} else {
throw std::runtime_error("No registered namespace: " + t_namespace_name);
}
}
/// \brief Registers a namespace generator, which delays generation of the namespace until it is imported, saving memory if it is never
/// used. \param[in] t_namespace_generator Namespace generator function. \param[in] t_namespace_name Name of the Namespace function
/// being registered. \throw std::runtime_error In the case that the namespace name was already registered.
void register_namespace(const std::function<void(Namespace &)> &t_namespace_generator, const std::string &t_namespace_name) {
chaiscript::detail::threading::unique_lock<chaiscript::detail::threading::recursive_mutex> l(m_use_mutex);
if (!m_namespace_generators.count(t_namespace_name)) {
// contain the namespace object memory within the m_namespace_generators map
m_namespace_generators.emplace(std::make_pair(t_namespace_name, [=, space = Namespace()]() mutable -> Namespace & {
t_namespace_generator(space);
return space;
}));
} else {
throw std::runtime_error("Namespace: " + t_namespace_name + " was already registered.");
}
}
};
} // namespace chaiscript
#endif /* CHAISCRIPT_ENGINE_HPP_ */