Update docs regarding modules.
This commit is contained in:
parent
236ba40784
commit
150f02d8b7
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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'
|
||||||
```
|
```
|
||||||
|
@ -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;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ Import a Module
|
|||||||
|
|
||||||
{{#include ../../links.md}}
|
{{#include ../../links.md}}
|
||||||
|
|
||||||
|
|
||||||
`import` Statement
|
`import` Statement
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
@ -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;
|
|
||||||
```
|
|
@ -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. |
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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";
|
||||||
```
|
```
|
||||||
|
45
doc/src/rust/modules/index.md
Normal file
45
doc/src/rust/modules/index.md
Normal 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;
|
||||||
|
```
|
@ -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`:
|
||||||
|
|
@ -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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user