diff --git a/RELEASES.md b/RELEASES.md index 8a17c106..4afe903b 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -9,6 +9,7 @@ This version adds: * [`serde`](https://crates.io/crates/serde) support for working with `Dynamic` values (particularly _object maps_). * Ability to surgically disable keywords and/or operators in the language. * Ability to define custom operators (which must be valid identifiers). +* Low-level API to register functions. Breaking changes ---------------- @@ -29,6 +30,7 @@ New features * `AST::clone_functions_only`, `AST::clone_functions_only_filtered` and `AST::clone_statements_only` to clone only part of an `AST`. * The boolean `^` (XOR) operator is added. * `FnPtr` is exposed as the function pointer type. +* `rhai::module_resolvers::ModuleResolversCollection` added to try a list of module resolvers. Version 0.16.1 diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index 6d90df0c..3274e8cb 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -79,10 +79,10 @@ The Rhai Scripting Language 16. [Modules](language/modules/index.md) 1. [Export Variables, Functions and Sub-Modules](language/modules/export.md) 2. [Import Modules](language/modules/import.md) - 3. [Create from Rust](language/modules/rust.md) + 3. [Create from Rust](rust/modules/index.md) 4. [Create from AST](language/modules/ast.md) - 5. [Module Resolvers](language/modules/resolvers.md) - 1. [Implement a Custom Module Resolver](language/modules/imp-resolver.md) + 5. [Module Resolvers](rust/modules/resolvers.md) + 1. [Custom Implementation](rust/modules/imp-resolver.md) 7. [Safety and Protection](safety/index.md) 1. [Checked Arithmetic](safety/checked.md) 2. [Sand-Boxing](safety/sandbox.md) diff --git a/doc/src/language/eval.md b/doc/src/language/eval.md index a7e5b979..0af40142 100644 --- a/doc/src/language/eval.md +++ b/doc/src/language/eval.md @@ -19,14 +19,14 @@ script += "x + y"; let result = eval(script); // <- look, JavaScript, we can also do this! -print("Answer: " + result); // prints 42 +result == 42; -print("x = " + x); // prints 10: functions call arguments are passed by value -print("y = " + y); // prints 32: variables defined in 'eval' persist! +x == 10; // prints 10: functions call arguments are passed by value +y == 32; // prints 32: variables defined in 'eval' persist! eval("{ let z = y }"); // to keep a variable local, use a statement block -print("z = " + z); // <- error: variable 'z' not found +print(z); // <- error: variable 'z' not found "print(42)".eval(); // <- nope... method-call style doesn't work with 'eval' ``` diff --git a/doc/src/language/functions.md b/doc/src/language/functions.md index 72dac0b4..b6c0a85f 100644 --- a/doc/src/language/functions.md +++ b/doc/src/language/functions.md @@ -14,8 +14,9 @@ fn sub(x, y,) { // trailing comma in parameters list is OK return x - y; } -print(add(2, 3)); // prints 5 -print(sub(2, 3,)); // prints -1 - trailing comma in arguments list is OK +add(2, 3) == 5; + +sub(2, 3,) == -1; // trailing comma in arguments list is OK ``` @@ -35,8 +36,9 @@ fn add2(x) { return x + 2; // explicit return } -print(add(2, 3)); // prints 5 -print(add2(42)); // prints 44 +add(2, 3) == 5; + +add2(42) == 44; ``` diff --git a/doc/src/language/modules/export.md b/doc/src/language/modules/export.md index dbb4ddca..f05e008c 100644 --- a/doc/src/language/modules/export.md +++ b/doc/src/language/modules/export.md @@ -22,6 +22,8 @@ The `export` statement, which can only be at global level, exposes selected vari Variables not exported are _private_ and hidden to the outside. +Everything exported from a module is **constant** (**read-only**). + ```rust // This is a module script. @@ -49,8 +51,6 @@ All functions are automatically exported, _unless_ it is explicitly opt-out with Functions declared [`private`] are hidden to the outside. -Everything exported from a module is **constant** (**read-only**). - ```rust // This is a module script. diff --git a/doc/src/language/modules/import.md b/doc/src/language/modules/import.md index 4ede1f52..dc94b5a8 100644 --- a/doc/src/language/modules/import.md +++ b/doc/src/language/modules/import.md @@ -3,6 +3,7 @@ Import a Module {{#include ../../links.md}} + `import` Statement ----------------- diff --git a/doc/src/language/modules/rust.md b/doc/src/language/modules/rust.md deleted file mode 100644 index 74f90896..00000000 --- a/doc/src/language/modules/rust.md +++ /dev/null @@ -1,30 +0,0 @@ -Create a Module from Rust -======================== - -{{#include ../../links.md}} - -To load a custom module (written in Rust) into an [`Engine`], first create a [`Module`] type, -add variables/functions into it, then finally push it into a custom [`Scope`]. - -This has the equivalent effect of putting an [`import`] statement at the beginning of any script run. - -```rust -use rhai::{Engine, Scope, Module, i64}; - -let mut engine = Engine::new(); -let mut scope = Scope::new(); - -let mut module = Module::new(); // new module -module.set_var("answer", 41_i64); // variable 'answer' under module -module.set_fn_1("inc", |x: i64| Ok(x+1)); // use the 'set_fn_XXX' API to add functions - -// Push the module into the custom scope under the name 'question' -// This is equivalent to 'import "..." as question;' -scope.push_module("question", module); - -// Use module-qualified variables -engine.eval_expression_with_scope::(&scope, "question::answer + 1")? == 42; - -// Call module-qualified functions -engine.eval_expression_with_scope::(&scope, "question::inc(question::answer)")? == 42; -``` diff --git a/doc/src/language/values-and-types.md b/doc/src/language/values-and-types.md index 70c276a9..55b03229 100644 --- a/doc/src/language/values-and-types.md +++ b/doc/src/language/values-and-types.md @@ -15,7 +15,7 @@ The following primitive types are supported natively: | **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` | | **[Object map]** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `"#{ "a": 1, "b": 2 }"` | | **[Timestamp]** (implemented in the [`BasicTimePackage`][packages], disabled with [`no_std`]) | `std::time::Instant` ([`instant::Instant`] if not [WASM] build) | `"timestamp"` | _not supported_ | -| **[Function pointer]** | _None_ | `Fn` | `"Fn(foo)"` | +| **[Function pointer]** | `rhai::FnPtr` | `Fn` | `"Fn(foo)"` | | **[`Dynamic`] value** (i.e. can be anything) | `rhai::Dynamic` | _the actual type_ | _actual value_ | | **System integer** (current configuration) | `rhai::INT` (`i32` or `i64`) | `"i32"` or `"i64"` | `"42"`, `"123"` etc. | | **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. | diff --git a/doc/src/links.md b/doc/src/links.md index a81bfd0c..85561acc 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -82,7 +82,7 @@ [`Module`]: {{rootUrl}}/language/modules/index.md [module]: {{rootUrl}}/language/modules/index.md [modules]: {{rootUrl}}/language/modules/index.md -[module resolver]: {{rootUrl}}/language/modules/imp-resolver.md +[module resolver]: {{rootUrl}}/rust/modules/resolvers.md [`export`]: {{rootUrl}}/language/modules/export.md [`import`]: {{rootUrl}}/language/modules/import.md diff --git a/doc/src/rust/custom.md b/doc/src/rust/custom.md index b87f6409..43837cd5 100644 --- a/doc/src/rust/custom.md +++ b/doc/src/rust/custom.md @@ -136,10 +136,10 @@ with a special "pretty-print" name, [`type_of()`] will return that name instead. engine.register_type::(); engine.register_fn("new_ts", TestStruct::new); let x = new_ts(); -print(x.type_of()); // prints "path::to::module::TestStruct" +x.type_of() == "path::to::module::TestStruct"; engine.register_type_with_name::("Hello"); engine.register_fn("new_ts", TestStruct::new); let x = new_ts(); -print(x.type_of()); // prints "Hello" +x.type_of() == "Hello"; ``` diff --git a/doc/src/language/modules/imp-resolver.md b/doc/src/rust/modules/imp-resolver.md similarity index 100% rename from doc/src/language/modules/imp-resolver.md rename to doc/src/rust/modules/imp-resolver.md diff --git a/doc/src/rust/modules/index.md b/doc/src/rust/modules/index.md new file mode 100644 index 00000000..be6576bb --- /dev/null +++ b/doc/src/rust/modules/index.md @@ -0,0 +1,45 @@ +Create a Module from Rust +======================== + +{{#include ../../links.md}} + +Manually creating a [`Module`] is possible via the `Module` API. + +For the complete `Module` API, refer to the [documentation](https://docs.rs/rhai/{{version}}/rhai/struct.Module.html) online. + + +Make the Module Available to the Engine +-------------------------------------- + +In order to _use_ a custom module, there must be a [module resolver]. + +The easiest way is to use, for example, the [`StaticModuleResolver`][module resolver] to hold such +a custom module. + +```rust +use rhai::{Engine, Scope, Module, i64}; +use rhai::module_resolvers::StaticModuleResolver; + +let mut engine = Engine::new(); +let mut scope = Scope::new(); + +let mut module = Module::new(); // new module +module.set_var("answer", 41_i64); // variable 'answer' under module +module.set_fn_1("inc", |x: i64| Ok(x+1)); // use the 'set_fn_XXX' API to add functions + +// Create the module resolver +let mut resolver = StaticModuleResolver::new(); + +// Add the module into the module resolver under the name 'question' +// They module can then be accessed via: 'import "question" as q;' +resolver.insert("question", module); + +// Set the module resolver into the 'Engine' +engine.set_module_resolver(Some(resolver)); + +// Use module-qualified variables +engine.eval::(&scope, r#"import "question" as q; q::answer + 1"#)? == 42; + +// Call module-qualified functions +engine.eval::(&scope, r#"import "question" as q; q::inc(q::answer)"#)? == 42; +``` diff --git a/doc/src/language/modules/resolvers.md b/doc/src/rust/modules/resolvers.md similarity index 55% rename from doc/src/language/modules/resolvers.md rename to doc/src/rust/modules/resolvers.md index ed2bf54c..4a3b97e7 100644 --- a/doc/src/language/modules/resolvers.md +++ b/doc/src/rust/modules/resolvers.md @@ -7,15 +7,24 @@ When encountering an [`import`] statement, Rhai attempts to _resolve_ the module _Module Resolvers_ are service types that implement the [`ModuleResolver`][traits] trait. + +Built-In Module Resolvers +------------------------ + There are a number of standard resolvers built into Rhai, the default being the `FileModuleResolver` which simply loads a script file based on the path (with `.rhai` extension attached) and execute it to form a module. Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace. -| Module Resolver | Description | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `FileModuleResolver` | The default module resolution service, not available under [`no_std`] or [WASM] builds. Loads a script file (based off the current directory) with `.rhai` extension.
The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.
`FileModuleResolver::create_module()` loads a script file and returns a module. | -| `StaticModuleResolver` | Loads modules that are statically added. This can be used under [`no_std`]. | +| Module Resolver | Description | +| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `FileModuleResolver` | The default module resolution service, not available under [`no_std`] or [WASM] builds. Loads a script file (based off the current directory) with `.rhai` extension.
The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.
`FileModuleResolver::create_module()` loads a script file and returns a module. | +| `StaticModuleResolver` | Loads modules that are statically added. This can be used under [`no_std`]. | +| `ModuleResolversCollection` | A collection of module resolvers. Modules will be resolved from each resolver in sequential order.
This is useful when multiple types of modules are needed simultaneously. | + + +Set into `Engine` +----------------- An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`: diff --git a/doc/src/rust/operators.md b/doc/src/rust/operators.md index 8374bfb4..e4ce9b65 100644 --- a/doc/src/rust/operators.md +++ b/doc/src/rust/operators.md @@ -39,17 +39,19 @@ engine.register_fn("+", strange_add); // overload '+' operator for let result: i64 = engine.eval("1 + 0"); // the overloading version is used -println!("result: {}", result); // prints 42 +result == 42; let result: f64 = engine.eval("1.0 + 0.0"); // '+' operator for two floats not overloaded -println!("result: {}", result); // prints 1.0 +result == 1.0; fn mixed_add(a: i64, b: f64) -> f64 { (a as f64) + b } engine.register_fn("+", mixed_add); // register '+' operator for an integer and a float -let result: i64 = engine.eval("1 + 1.0"); // prints 2.0 (normally an error) +let result: i64 = engine.eval("1 + 1.0"); // <- normally an error... + +result == 2.0; // ... but not now ``` diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md index 429dae4a..6d3f22c5 100644 --- a/doc/src/rust/register-raw.md +++ b/doc/src/rust/register-raw.md @@ -75,15 +75,18 @@ Closure Signature The closure passed to `Engine::register_raw_fn` takes the following form: -`Fn(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]) -> Result> + 'static` +`Fn(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]) -> Result> + 'static` where: -* `engine` - a reference to the current [`Engine`], with all configurations and settings. +* `T : Variant + Clone` - return type of the function. -* `lib` - a reference to the current collection of script-defined functions, as a [`Module`]. +* `engine : &Engine` - the current [`Engine`], with all configurations and settings. -* `args` - a reference to a slice containing `&mut` references to [`Dynamic`] values. +* `lib : &Module` - the current global library of script-defined functions, as a [`Module`]. + This is sometimes useful for calling a script-defined function within the same evaluation context using [`Engine::call_fn`][`call_fn`]. + +* `args : &mut [&mut Dynamic]` - a slice containing `&mut` references to [`Dynamic`] values. The slice is guaranteed to contain enough arguments _of the correct types_. Remember, in Rhai, all arguments _except_ the _first_ one are always passed by _value_ (i.e. cloned). @@ -100,13 +103,54 @@ To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use th | ------------------------------ | -------------------------------------- | ---------------------------------------------------------- | | [Primary type][standard types] | `args[n].clone().cast::()` | Copy of value. | | Custom type | `args[n].downcast_ref::().unwrap()` | Immutable reference to value. | -| Custom type (consumed) | `mem::take(args[n]).cast::()` | The _consumed_ value.
The original value becomes `()`. | +| Custom type (consumed) | `std::mem::take(args[n]).cast::()` | The _consumed_ value.
The original value becomes `()`. | | `this` object | `args[0].downcast_mut::().unwrap()` | Mutable reference to value. | When there is a mutable reference to the `this` object (i.e. the first argument), there can be no other immutable references to `args`, otherwise the Rust borrow checker will complain. +Example - Passing a Function Pointer to a Rust Function +------------------------------------------------------ + +```rust +use rhai::{Engine, Module, Dynamic, FnPtr}; + +let mut engine = Engine::new(); + +// Register a Rust function +engine.register_raw_fn( + "bar", + &[ + std::any::TypeId::of::(), // parameter types + std::any::TypeId::of::(), + std::any::TypeId::of::(), + ], + move |engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]| { + // 'args' is guaranteed to contain enough arguments of the correct types + + let fp = std::mem::take(args[1]).cast::(); // 2nd argument - function pointer + let value = args[2].clone(); // 3rd argument - function argument + let this_ptr = args.get_mut(0).unwrap(); // 1st argument - this pointer + + // Use 'call_fn_dynamic' to call the function name. + // Pass 'lib' as the current global library of functions. + engine.call_fn_dynamic(&mut Scope::new(), lib, fp.fn_name(), Some(this_ptr), [value])?; + + Ok(()) + }, +); + +let result = engine.eval::(r#" + fn foo(x) { this += x; } // script-defined function 'foo' + + let x = 41; // object + x.bar(Fn("foo"), 1); // pass 'foo' as function pointer + x +"#)?; +``` + + Hold Multiple References ------------------------