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_).
|
||||
* 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
|
||||
|
@ -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)
|
||||
|
@ -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'
|
||||
```
|
||||
|
@ -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;
|
||||
```
|
||||
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -3,6 +3,7 @@ Import a Module
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
|
||||
`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"` | `"[ ?, ?, ? ]"` |
|
||||
| **[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. |
|
||||
|
@ -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
|
||||
|
||||
|
@ -136,10 +136,10 @@ with a special "pretty-print" name, [`type_of()`] will return that name instead.
|
||||
engine.register_type::<TestStruct>();
|
||||
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::<TestStruct>("Hello");
|
||||
engine.register_fn("new_ts", TestStruct::new);
|
||||
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.
|
||||
|
||||
|
||||
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.<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`]. |
|
||||
| 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. |
|
||||
| `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`:
|
||||
|
@ -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
|
||||
```
|
||||
|
||||
|
||||
|
@ -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<Dynamic, Box<EvalAltResult>> + 'static`
|
||||
`Fn(engine: &Engine, lib: &Module, args: &mut [&mut Dynamic]) -> Result<T, Box<EvalAltResult>> + '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::<T>()` | Copy of 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. |
|
||||
|
||||
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::<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
|
||||
------------------------
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user