Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions cheatsheet.md
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,43 @@ The print handler can also be set from within ChaiScript itself via `set_print_h
set_print_handler(fun(s) { my_custom_log(s) })
```

## Custom File Loading

By default, ChaiScript reads files from the filesystem when `eval_file()` or `use()` is called.
You can override this behavior on a per-instance basis by setting a custom file reader callback.
This follows the same pattern as `set_print_handler` and enables use cases such as encrypted
script files, in-memory virtual filesystems, or platform-specific file access (e.g., Android assets).

```cpp
chaiscript::ChaiScript chai;

// Provide scripts from an in-memory map instead of the filesystem
std::map<std::string, std::string> virtual_fs = {
{"init.chai", "var x = 42"},
{"utils.chai", "def add(a, b) { a + b }"}
};

chai.set_file_reader([&virtual_fs](const std::string &filename) -> std::string {
const auto it = virtual_fs.find(filename);
if (it != virtual_fs.end()) {
return it->second;
}
throw chaiscript::exception::file_not_found_error(filename);
});

chai.eval_file("init.chai"); // evaluates "var x = 42"
chai.use("utils.chai"); // evaluates "def add(a, b) { a + b }"
```

The file reader can also be set from within ChaiScript itself via `set_file_reader`:

```chaiscript
// Override file loading from within a script
set_file_reader(fun(filename) { return my_custom_read(filename) })
```

When no custom file reader is set, ChaiScript uses its built-in filesystem reader.

## 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.)

Expand Down
21 changes: 20 additions & 1 deletion include/chaiscript/language/chaiscript_engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ namespace chaiscript {
std::map<std::string, std::function<Namespace &()>> m_namespace_generators;

std::function<void(const std::string &)> m_print_handler = [](const std::string &) noexcept {};
std::function<std::string(const std::string &)> m_file_reader;

/// 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) {
Expand Down Expand Up @@ -139,6 +140,10 @@ namespace chaiscript {
m_print_handler = t_handler;
}), "set_print_handler");

m_engine.add(fun([this](const std::function<std::string(const std::string &)> &t_reader) {
m_file_reader = t_reader;
}), "set_file_reader");

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");
Expand Down Expand Up @@ -243,7 +248,15 @@ namespace chaiscript {
}

/// Helper function for loading a file
static std::string load_file(const std::string &t_filename) {
std::string load_file(const std::string &t_filename) const {
if (m_file_reader) {
return m_file_reader(t_filename);
}

return load_file_default(t_filename);
}

static std::string load_file_default(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()) {
Expand Down Expand Up @@ -285,6 +298,12 @@ namespace chaiscript {
m_print_handler = std::move(t_handler);
}

/// \brief Set a custom handler for reading files, used by eval_file, use, and internal_eval_file
/// \param[in] t_reader Function to call with the filename, returning the file contents as a string
void set_file_reader(std::function<std::string(const std::string &)> t_reader) {
m_file_reader = std::move(t_reader);
}

/// \brief Virtual destructor for ChaiScript
virtual ~ChaiScript_Basic() = default;

Expand Down
1 change: 1 addition & 0 deletions releasenotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Current Version: 6.1.1

### Changes since 6.1.0

* Add `set_file_reader` callback for custom file loading #116 — allows overriding how `eval_file` and `use` read files
* Handle the returning of `&` to `*` types. This specifically comes up with `std::vector<int *>` and similar containers
* Update CMake to use `LIBDIR` instead of `lib` #502 by @guoyunhe
* Add documentation for installing ChaiScript with vcpkg #500 by @grdowns
Expand Down
38 changes: 38 additions & 0 deletions unittests/compiled_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1822,6 +1822,44 @@ TEST_CASE("eval_error with AST_Node_Trace call stack compiles in C++20") {
}
}

TEST_CASE("Test set_file_reader from C++ land") {
chaiscript::ChaiScript chai;
chai.set_file_reader([](const std::string &) {
return std::string("var file_reader_test_val = 42");
});
chai.eval_file("nonexistent_file.chai");
CHECK(chai.eval<int>("file_reader_test_val") == 42);
}

TEST_CASE("Test set_file_reader from ChaiScript land") {
chaiscript::ChaiScript chai;
chai.set_file_reader([](const std::string &) {
return std::string("var from_custom_reader = true");
});
chai.eval("set_file_reader(fun(filename) { return \"var from_chai_reader = true\"; })");
chai.eval_file("any_file.chai");
CHECK(chai.eval<bool>("from_chai_reader") == true);
}

TEST_CASE("Test set_file_reader receives correct filename") {
chaiscript::ChaiScript chai;
std::string captured_filename;
chai.set_file_reader([&captured_filename](const std::string &t_filename) {
captured_filename = t_filename;
return std::string("var dummy = 1");
});
chai.eval_file("my_special_file.chai");
CHECK(captured_filename == "my_special_file.chai");
}

TEST_CASE("Test use with set_file_reader") {
chaiscript::ChaiScript chai;
chai.set_file_reader([](const std::string &) {
return std::string("var use_reader_val = 99");
});
chai.use("virtual_file.chai");
CHECK(chai.eval<int>("use_reader_val") == 99);
}
TEST_CASE("Nested namespaces via register_namespace with :: separator") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());

Expand Down
Loading