Update docs regarding modules.

This commit is contained in:
Stephen Chung 2020-07-08 09:48:25 +08:00
parent 236ba40784
commit 150f02d8b7
15 changed files with 134 additions and 59 deletions

View File

@ -9,6 +9,7 @@ This version adds:
* [`serde`](https://crates.io/crates/serde) support for working with `Dynamic` values (particularly _object maps_). * [`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 surgically disable keywords and/or operators in the language.
* Ability to define custom operators (which must be valid identifiers). * Ability to define custom operators (which must be valid identifiers).
* Low-level API to register functions.
Breaking changes 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`. * `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. * The boolean `^` (XOR) operator is added.
* `FnPtr` is exposed as the function pointer type. * `FnPtr` is exposed as the function pointer type.
* `rhai::module_resolvers::ModuleResolversCollection` added to try a list of module resolvers.
Version 0.16.1 Version 0.16.1

View File

@ -79,10 +79,10 @@ The Rhai Scripting Language
16. [Modules](language/modules/index.md) 16. [Modules](language/modules/index.md)
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md) 1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)
2. [Import Modules](language/modules/import.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) 4. [Create from AST](language/modules/ast.md)
5. [Module Resolvers](language/modules/resolvers.md) 5. [Module Resolvers](rust/modules/resolvers.md)
1. [Implement a Custom Module Resolver](language/modules/imp-resolver.md) 1. [Custom Implementation](rust/modules/imp-resolver.md)
7. [Safety and Protection](safety/index.md) 7. [Safety and Protection](safety/index.md)
1. [Checked Arithmetic](safety/checked.md) 1. [Checked Arithmetic](safety/checked.md)
2. [Sand-Boxing](safety/sandbox.md) 2. [Sand-Boxing](safety/sandbox.md)

View File

@ -19,14 +19,14 @@ script += "x + y";
let result = eval(script); // <- look, JavaScript, we can also do this! 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 x == 10; // prints 10: functions call arguments are passed by value
print("y = " + y); // prints 32: variables defined in 'eval' persist! y == 32; // prints 32: variables defined in 'eval' persist!
eval("{ let z = y }"); // to keep a variable local, use a statement block 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' "print(42)".eval(); // <- nope... method-call style doesn't work with 'eval'
``` ```

View File

@ -14,8 +14,9 @@ fn sub(x, y,) { // trailing comma in parameters list is OK
return x - y; return x - y;
} }
print(add(2, 3)); // prints 5 add(2, 3) == 5;
print(sub(2, 3,)); // prints -1 - trailing comma in arguments list is OK
sub(2, 3,) == -1; // trailing comma in arguments list is OK
``` ```
@ -35,8 +36,9 @@ fn add2(x) {
return x + 2; // explicit return return x + 2; // explicit return
} }
print(add(2, 3)); // prints 5 add(2, 3) == 5;
print(add2(42)); // prints 44
add2(42) == 44;
``` ```

View File

@ -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. Variables not exported are _private_ and hidden to the outside.
Everything exported from a module is **constant** (**read-only**).
```rust ```rust
// This is a module script. // 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. Functions declared [`private`] are hidden to the outside.
Everything exported from a module is **constant** (**read-only**).
```rust ```rust
// This is a module script. // This is a module script.

View File

@ -3,6 +3,7 @@ Import a Module
{{#include ../../links.md}} {{#include ../../links.md}}
`import` Statement `import` Statement
----------------- -----------------

View File

@ -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::<i64>(&scope, "question::answer + 1")? == 42;
// Call module-qualified functions
engine.eval_expression_with_scope::<i64>(&scope, "question::inc(question::answer)")? == 42;
```

View File

@ -15,7 +15,7 @@ The following primitive types are supported natively:
| **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` | | **[`Array`]** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` |
| **[Object map]** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `"#{ "a": 1, "b": 2 }"` | | **[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_ | | **[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_ | | **[`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 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. | | **System floating-point** (current configuration, disabled with [`no_float`]) | `rhai::FLOAT` (`f32` or `f64`) | `"f32"` or `"f64"` | `"123.456"` etc. |

View File

@ -82,7 +82,7 @@
[`Module`]: {{rootUrl}}/language/modules/index.md [`Module`]: {{rootUrl}}/language/modules/index.md
[module]: {{rootUrl}}/language/modules/index.md [module]: {{rootUrl}}/language/modules/index.md
[modules]: {{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 [`export`]: {{rootUrl}}/language/modules/export.md
[`import`]: {{rootUrl}}/language/modules/import.md [`import`]: {{rootUrl}}/language/modules/import.md

View File

@ -136,10 +136,10 @@ with a special "pretty-print" name, [`type_of()`] will return that name instead.
engine.register_type::<TestStruct>(); engine.register_type::<TestStruct>();
engine.register_fn("new_ts", TestStruct::new); engine.register_fn("new_ts", TestStruct::new);
let x = new_ts(); 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::<TestStruct>("Hello"); engine.register_type_with_name::<TestStruct>("Hello");
engine.register_fn("new_ts", TestStruct::new); engine.register_fn("new_ts", TestStruct::new);
let x = new_ts(); let x = new_ts();
print(x.type_of()); // prints "Hello" x.type_of() == "Hello";
``` ```

View File

@ -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::<i64>(&scope, r#"import "question" as q; q::answer + 1"#)? == 42;
// Call module-qualified functions
engine.eval::<i64>(&scope, r#"import "question" as q; q::inc(q::answer)"#)? == 42;
```

View File

@ -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. _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` 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. 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. Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace.
| Module Resolver | Description | | 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.<br/>The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.<br/>`FileModuleResolver::create_module()` loads a script file and returns a module. | | `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.<br/>The base directory can be changed via the `FileModuleResolver::new_with_path()` constructor function.<br/>`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`]. | | `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.<br/>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`: An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`:

View File

@ -39,17 +39,19 @@ engine.register_fn("+", strange_add); // overload '+' operator for
let result: i64 = engine.eval("1 + 0"); // the overloading version is used 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 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 } 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 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
``` ```

View File

@ -75,15 +75,18 @@ Closure Signature
The closure passed to `Engine::register_raw_fn` takes the following form: The closure passed to `Engine::register_raw_fn` takes the following form:
`Fn(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]) -> Result<Dynamic, Box<EvalAltResult>> + 'static` `Fn(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]) -> Result<T, Box<EvalAltResult>> + 'static`
where: 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_. 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). 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::<T>()` | Copy of value. | | [Primary type][standard types] | `args[n].clone().cast::<T>()` | Copy of value. |
| Custom type | `args[n].downcast_ref::<T>().unwrap()` | Immutable reference to value. | | Custom type | `args[n].downcast_ref::<T>().unwrap()` | Immutable reference to value. |
| Custom type (consumed) | `mem::take(args[n]).cast::<T>()` | The _consumed_ value.<br/>The original value becomes `()`. | | Custom type (consumed) | `std::mem::take(args[n]).cast::<T>()` | The _consumed_ value.<br/>The original value becomes `()`. |
| `this` object | `args[0].downcast_mut::<T>().unwrap()` | Mutable reference to value. | | `this` object | `args[0].downcast_mut::<T>().unwrap()` | Mutable reference to value. |
When there is a mutable reference to the `this` object (i.e. the first argument), 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. 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::<i64>(), // parameter types
std::any::TypeId::of::<FnPtr>(),
std::any::TypeId::of::<i64>(),
],
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::<FnPtr>(); // 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::<i64>(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 Hold Multiple References
------------------------ ------------------------