Revise docs.
This commit is contained in:
@@ -53,10 +53,10 @@ impl TestStruct {
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine
|
||||
.register_type::<TestStruct>()
|
||||
.register_get_set("xyz", TestStruct::get_field, TestStruct::set_field)
|
||||
.register_fn("new_ts", TestStruct::new);
|
||||
engine
|
||||
.register_type::<TestStruct>()
|
||||
.register_get_set("xyz", TestStruct::get_field, TestStruct::set_field)
|
||||
.register_fn("new_ts", TestStruct::new);
|
||||
|
||||
let result = engine.eval::<String>(r#"let a = new_ts(); a.xyz = "42"; a.xyz"#)?;
|
||||
|
||||
|
88
doc/src/rust/modules/ast.md
Normal file
88
doc/src/rust/modules/ast.md
Normal file
@@ -0,0 +1,88 @@
|
||||
Create a Module from an AST
|
||||
==========================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
|
||||
`Module::eval_ast_as_new`
|
||||
------------------------
|
||||
|
||||
A _module_ can be created from a single script (or pre-compiled [`AST`]) containing global variables,
|
||||
functions and sub-modules via the `Module::eval_ast_as_new` method.
|
||||
|
||||
When given an [`AST`], it is first evaluated, then the following items are exposed as members of the new module:
|
||||
|
||||
* Global variables - essentially all variables that remain in the [`Scope`] at the end of a script run - that are exported. Variables not exported (via the `export` statement) remain hidden.
|
||||
|
||||
* Functions not specifically marked `private`.
|
||||
|
||||
* Global modules that remain in the [`Scope`] at the end of a script run.
|
||||
|
||||
|
||||
`merge_namespaces` Parameter
|
||||
---------------------------
|
||||
|
||||
The parameter `merge_namespaces` in `Module::eval_ast_as_new` determines the exact behavior of
|
||||
functions exposed by the module and the namespace that they can access:
|
||||
|
||||
| `merge_namespaces` value | Description | Namespace | Performance | Call global functions | Call functions in same module |
|
||||
| :----------------------: | ------------------------------------------------ | :-----------------: | :---------: | :-------------------: | :---------------------------: |
|
||||
| `true` | encapsulate entire `AST` into each function call | module, then global | slower | yes | yes |
|
||||
| `false` | register each function independently | global only | fast | yes | no |
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Don't forget the [`export`] statement, otherwise there will be no variables exposed by the module
|
||||
other than non-[`private`] functions (unless that's intentional).
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Module};
|
||||
|
||||
let engine = Engine::new();
|
||||
|
||||
// Compile a script into an 'AST'
|
||||
let ast = engine.compile(r#"
|
||||
// Functions become module functions
|
||||
fn calc(x) {
|
||||
x + 1
|
||||
}
|
||||
fn add_len(x, y) {
|
||||
x + y.len
|
||||
}
|
||||
|
||||
// Imported modules can become sub-modules
|
||||
import "another module" as extra;
|
||||
|
||||
// Variables defined at global level can become module variables
|
||||
const x = 123;
|
||||
let foo = 41;
|
||||
let hello;
|
||||
|
||||
// Variable values become constant module variable values
|
||||
foo = calc(foo);
|
||||
hello = "hello, " + foo + " worlds!";
|
||||
|
||||
// Finally, export the variables and modules
|
||||
export
|
||||
x as abc, // aliased variable name
|
||||
foo,
|
||||
hello,
|
||||
extra as foobar; // export sub-module
|
||||
"#)?;
|
||||
|
||||
// Convert the 'AST' into a module, using the 'Engine' to evaluate it first
|
||||
//
|
||||
// The second parameter ('merge_namespaces'), when set to true, will encapsulate
|
||||
// a copy of the entire 'AST' into each function, allowing functions in the module script
|
||||
// to cross-call each other.
|
||||
//
|
||||
// This incurs additional overhead, avoidable by setting 'merge_namespaces' to false.
|
||||
let module = Module::eval_ast_as_new(Scope::new(), &ast, true, &engine)?;
|
||||
|
||||
// 'module' now contains:
|
||||
// - sub-module: 'foobar' (renamed from 'extra')
|
||||
// - functions: 'calc', 'add_len'
|
||||
// - constants: 'abc' (renamed from 'x'), 'foo', 'hello'
|
||||
```
|
@@ -22,19 +22,38 @@ For the complete `Module` API, refer to the [documentation](https://docs.rs/rhai
|
||||
Make the `Module` Available to the `Engine`
|
||||
------------------------------------------
|
||||
|
||||
In order to _use_ a custom module, there must be a [module resolver], which serves the module when
|
||||
loaded via `import` statements.
|
||||
`Engine::load_package` supports loading a [module] as a [package].
|
||||
|
||||
Since it acts as a [package], all functions will be registered into the _global_ namespace
|
||||
and can be accessed without _module qualifiers_.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Module};
|
||||
|
||||
let mut module = Module::new(); // new module
|
||||
module.set_fn_1("inc", |x: i64| Ok(x+1)); // use the 'set_fn_XXX' API to add functions
|
||||
|
||||
// Load the module into the Engine as a new package.
|
||||
let mut engine = Engine::new();
|
||||
engine.load_package(module);
|
||||
|
||||
engine.eval::<i64>("inc(41)")? == 42; // no need to import module
|
||||
```
|
||||
|
||||
|
||||
Make the `Module` Dynamically Loadable
|
||||
-------------------------------------
|
||||
|
||||
In order to dynamically load a custom module, there must be a [module resolver] which serves
|
||||
the module when loaded via `import` statements.
|
||||
|
||||
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::{Engine, Scope, Module};
|
||||
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
|
||||
@@ -47,11 +66,12 @@ let mut resolver = StaticModuleResolver::new();
|
||||
resolver.insert("question", module);
|
||||
|
||||
// Set the module resolver into the 'Engine'
|
||||
let mut engine = Engine::new();
|
||||
engine.set_module_resolver(Some(resolver));
|
||||
|
||||
// Use module-qualified variables
|
||||
engine.eval::<i64>(&scope, r#"import "question" as q; q::answer + 1"#)? == 42;
|
||||
engine.eval::<i64>(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;
|
||||
engine.eval::<i64>(r#"import "question" as q; q::inc(q::answer)"#)? == 42;
|
||||
```
|
||||
|
@@ -11,8 +11,14 @@ A module resolver must implement the trait [`rhai::ModuleResolver`][traits],
|
||||
which contains only one function: `resolve`.
|
||||
|
||||
When Rhai prepares to load a module, `ModuleResolver::resolve` is called with the name
|
||||
of the _module path_ (i.e. the path specified in the [`import`] statement). Upon success, it should
|
||||
return a [`Module`]; if the module cannot be load, return `EvalAltResult::ErrorModuleNotFound`.
|
||||
of the _module path_ (i.e. the path specified in the [`import`] statement).
|
||||
|
||||
* Upon success, it should return a [`Module`].
|
||||
|
||||
* If the path does not resolve to a valid module, return `EvalAltResult::ErrorModuleNotFound`.
|
||||
|
||||
* If the module failed to load, return `EvalAltResult::ErrorInModule`.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
@@ -35,9 +41,12 @@ impl ModuleResolver for MyModuleResolver {
|
||||
// Check module path.
|
||||
if is_valid_module_path(path) {
|
||||
// Load the custom module.
|
||||
let module: Module = load_secret_module(path);
|
||||
Ok(module)
|
||||
load_secret_module(path).map_err(|err|
|
||||
// Return EvalAltResult::ErrorInModule upon loading error
|
||||
EvalAltResult::ErrorInModule(err.to_string(), pos).into()
|
||||
)
|
||||
} else {
|
||||
// Return EvalAltResult::ErrorModuleNotFound if the path is invalid
|
||||
Err(EvalAltResult::ErrorModuleNotFound(path.into(), pos).into())
|
||||
}
|
||||
}
|
||||
@@ -50,7 +59,7 @@ engine.set_module_resolver(Some(MyModuleResolver {}));
|
||||
|
||||
engine.consume(r#"
|
||||
import "hello" as foo; // this 'import' statement will call
|
||||
// 'MyModuleResolver::resolve' with "hello" as path
|
||||
// 'MyModuleResolver::resolve' with "hello" as `path`
|
||||
foo:bar();
|
||||
"#)?;
|
||||
```
|
||||
|
@@ -12,16 +12,120 @@ 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.
|
||||
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 | Namespace |
|
||||
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------: |
|
||||
| `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. | Module (cannot access global namespace) |
|
||||
| `GlobalFileModuleResolver` | A simpler but more efficient version of `FileModuleResolver`, intended for short utility modules. Not available under [`no_std`] or [WASM] builds. Loads a script file (based off the current directory) with `.rhai` extension.<br/>**Note:** All functions are assumed independent and cannot cross-call each other.<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. | Global |
|
||||
| `StaticModuleResolver` | Loads modules that are statically added. This can be used under [`no_std`]. | Global |
|
||||
| `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. | Global |
|
||||
|
||||
`FileModuleResolver` (default)
|
||||
-----------------------------
|
||||
|
||||
The _default_ module resolution service, not available for [`no_std`] or [WASM] builds.
|
||||
Loads a script file (based off the current directory) with `.rhai` extension.
|
||||
|
||||
All functions in the _global_ namespace, plus all those defined in the same module,
|
||||
are _merged_ into a _unified_ namespace.
|
||||
|
||||
```rust
|
||||
------------------
|
||||
| my_module.rhai |
|
||||
------------------
|
||||
|
||||
private fn inner_message() { "hello! from module!" }
|
||||
|
||||
fn greet(callback) { print(callback.call()); }
|
||||
|
||||
-------------
|
||||
| main.rhai |
|
||||
-------------
|
||||
|
||||
fn main_message() { "hi! from main!" }
|
||||
|
||||
import "my_module" as m;
|
||||
|
||||
m::greet(|| "hello, " + "world!"); // works - anonymous function in global
|
||||
|
||||
m::greet(|| inner_message()); // works - function in module
|
||||
|
||||
m::greet(|| main_message()); // works - function in global
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
|
||||
`GlobalFileModuleResolver`
|
||||
-------------------------
|
||||
|
||||
A simpler but more efficient version of `FileModuleResolver`, intended for short utility modules.
|
||||
Not available for [`no_std`] or [WASM] builds.
|
||||
Loads a script file (based off the current directory) with `.rhai` extension.
|
||||
|
||||
All functions are assumed **independent** and _cannot_ cross-call each other.
|
||||
Functions are searched _only_ in the _global_ namespace.
|
||||
|
||||
```rust
|
||||
------------------
|
||||
| my_module.rhai |
|
||||
------------------
|
||||
|
||||
private fn inner_message() { "hello! from module!" }
|
||||
|
||||
fn greet_inner() {
|
||||
print(inner_message()); // cross-calling a module function!
|
||||
// there will be trouble because each function
|
||||
// in the module is supposed to be independent
|
||||
// of each other
|
||||
}
|
||||
|
||||
fn greet() {
|
||||
print(main_message()); // function is searched in global namespace
|
||||
}
|
||||
|
||||
-------------
|
||||
| main.rhai |
|
||||
-------------
|
||||
|
||||
fn main_message() { "hi! from main!" }
|
||||
|
||||
import "my_module" as m;
|
||||
|
||||
m::greet_inner(); // <- function not found: 'inner_message'
|
||||
|
||||
m::greet(); // works because 'main_message' exists in
|
||||
// the global namespace
|
||||
```
|
||||
|
||||
The base directory can be changed via the `FileModuleResolver::new_with_path` constructor function.
|
||||
|
||||
`GlobalFileModuleResolver::create_module` loads a script file and returns a module.
|
||||
|
||||
|
||||
`StaticModuleResolver`
|
||||
---------------------
|
||||
|
||||
Loads modules that are statically added. This can be used under [`no_std`].
|
||||
|
||||
Functions are searched in the _global_ namespace by default.
|
||||
|
||||
```rust
|
||||
use rhai::{Module, module_resolvers::StaticModuleResolver};
|
||||
|
||||
let module: Module = create_a_module();
|
||||
|
||||
let mut resolver = StaticModuleResolver::new();
|
||||
resolver.insert("my_module", module);
|
||||
```
|
||||
|
||||
|
||||
`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`
|
||||
@@ -30,8 +134,14 @@ Set into `Engine`
|
||||
An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`:
|
||||
|
||||
```rust
|
||||
// Use the 'StaticModuleResolver'
|
||||
let resolver = rhai::module_resolvers::StaticModuleResolver::new();
|
||||
use rhai::module_resolvers::StaticModuleResolver;
|
||||
|
||||
// Create a module resolver
|
||||
let resolver = StaticModuleResolver::new();
|
||||
|
||||
// Register functions into 'resolver'...
|
||||
|
||||
// Use the module resolver
|
||||
engine.set_module_resolver(Some(resolver));
|
||||
|
||||
// Effectively disable 'import' statements by setting module resolver to 'None'
|
||||
|
@@ -34,9 +34,11 @@ Macro Parameters
|
||||
```rust
|
||||
// Import necessary types and traits.
|
||||
use rhai::{
|
||||
def_package,
|
||||
packages::Package,
|
||||
packages::{ArithmeticPackage, BasicArrayPackage, BasicMapPackage, LogicPackage}
|
||||
def_package, // 'def_package!' macro
|
||||
packages::Package, // 'Package' trait
|
||||
packages::{ // pre-defined packages
|
||||
ArithmeticPackage, BasicArrayPackage, BasicMapPackage, LogicPackage
|
||||
}
|
||||
};
|
||||
|
||||
// Define the package 'MyPackage'.
|
||||
|
@@ -9,10 +9,12 @@ Packages reside under `rhai::packages::*` and the trait `rhai::packages::Package
|
||||
packages to be used.
|
||||
|
||||
Packages typically contain Rust functions that are callable within a Rhai script.
|
||||
All functions registered in a package is loaded under the _global namespace_ (i.e. they're available without module qualifiers).
|
||||
All functions registered in a package is loaded under the _global namespace_
|
||||
(i.e. they're available without module qualifiers).
|
||||
|
||||
Once a package is created (e.g. via `Package::new`), it can be _shared_ (via `Package::get`) among multiple instances of [`Engine`],
|
||||
even across threads (under [`sync`]). Therefore, a package only has to be created _once_.
|
||||
Once a package is created (e.g. via `Package::new`), it can be _shared_ (via `Package::get`)
|
||||
among multiple instances of [`Engine`], even across threads (under [`sync`]).
|
||||
Therefore, a package only has to be created _once_.
|
||||
|
||||
```rust
|
||||
use rhai::Engine;
|
||||
|
@@ -84,12 +84,12 @@ Extract Arguments
|
||||
|
||||
To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following:
|
||||
|
||||
| Argument type | Access (`n` = argument position) | Result |
|
||||
| ------------------------------ | ------------------------------------- | --------------------------------------------------------- |
|
||||
| [Primary type][standard types] | `args[n].clone().cast::<T>()` | copy of value |
|
||||
| Custom type | `args[n].read_lock::<T>().unwrap()` | immutable reference to value |
|
||||
| Custom type (consumed) | `std::mem::take(args[n]).cast::<T>()` | the _consumed_ value.<br/>The original value becomes `()` |
|
||||
| `this` object | `args[0].write_lock::<T>().unwrap()` | mutable reference to value |
|
||||
| Argument type | Access (`n` = argument position) | Result |
|
||||
| ------------------------------ | ------------------------------------- | ----------------------------------------------------- |
|
||||
| [Primary type][standard types] | `args[n].clone().cast::<T>()` | copy of value |
|
||||
| [Custom type] | `args[n].read_lock::<T>().unwrap()` | immutable reference to value |
|
||||
| [Custom type] (consumed) | `std::mem::take(args[n]).cast::<T>()` | the _consumed_ value; the original value becomes `()` |
|
||||
| `this` object | `args[0].write_lock::<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.
|
||||
|
@@ -105,6 +105,17 @@ let x: MyStruct = from_dynamic(&result)?;
|
||||
```
|
||||
|
||||
|
||||
Cannot Deserialize Shared Values
|
||||
-------------------------------
|
||||
|
||||
A [`Dynamic`] containing a _shared_ value cannot be deserialized - i.e. it will give a type error.
|
||||
|
||||
Use `Dynamic::flatten` to obtain a cloned copy before deserialization
|
||||
(if the value is not shared, it is simply returned and not cloned).
|
||||
|
||||
Shared values are turned off via the [`no_closure`] feature.
|
||||
|
||||
|
||||
Lighter Alternative
|
||||
-------------------
|
||||
|
||||
|
Reference in New Issue
Block a user