Revise docs.
This commit is contained in:
parent
2123b0a279
commit
64c421b3d7
21
README.md
21
README.md
@ -23,22 +23,23 @@ Standard features
|
||||
-----------------
|
||||
|
||||
* Easy-to-use language similar to JavaScript+Rust with dynamic typing.
|
||||
* Fairly low compile-time overhead.
|
||||
* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM).
|
||||
* Tight integration with native Rust [functions](https://schungx.github.io/rhai/rust/functions.html) and [types]([#custom-types-and-methods](https://schungx.github.io/rhai/rust/custom.html)), including [getters/setters](https://schungx.github.io/rhai/rust/getters-setters.html), [methods](https://schungx.github.io/rhai/rust/custom.html) and [indexers](https://schungx.github.io/rhai/rust/indexers.html).
|
||||
* Freely pass Rust variables/constants into a script via an external [`Scope`](https://schungx.github.io/rhai/rust/scope.html).
|
||||
* Easily [call a script-defined function](https://schungx.github.io/rhai/engine/call-fn.html) from Rust.
|
||||
* Fairly low compile-time overhead.
|
||||
* Fairly efficient evaluation (1 million iterations in 0.3 sec on a single core, 2.3 GHz Linux VM).
|
||||
* Relatively little `unsafe` code (yes there are some for performance reasons).
|
||||
* Few dependencies (currently only [`smallvec`](https://crates.io/crates/smallvec)).
|
||||
* Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature).
|
||||
* [Function overloading](https://schungx.github.io/rhai/language/overload.html).
|
||||
* [Operator overloading](https://schungx.github.io/rhai/rust/operators.html).
|
||||
* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html) with additional support for [currying](https://schungx.github.io/rhai/language/fn-curry.html) and [closures](https://schungx.github.io/rhai/language/fn-closure.html).
|
||||
* Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html).
|
||||
* Scripts are [optimized](https://schungx.github.io/rhai/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations.
|
||||
* Easy custom API development via [plugins](https://schungx.github.io/rhai/plugins/index.html) system powered by procedural macros.
|
||||
* [Function overloading](https://schungx.github.io/rhai/language/overload.html) and [operator overloading](https://schungx.github.io/rhai/rust/operators.html).
|
||||
* Dynamic dispatch via [function pointers](https://schungx.github.io/rhai/language/fn-ptr.html) with additional support for [currying](https://schungx.github.io/rhai/language/fn-curry.html).
|
||||
* [Closures](https://schungx.github.io/rhai/language/fn-closure.html) (anonymous functions) that can capture shared values.
|
||||
* Some syntactic support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.html).
|
||||
* Organize code base with dynamically-loadable [modules](https://schungx.github.io/rhai/language/modules.html).
|
||||
* Serialization/deserialization support via [serde](https://crates.io/crates/serde) (requires the `serde` feature).
|
||||
* Scripts are [optimized](https://schungx.github.io/rhai/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations.
|
||||
* Support for [minimal builds](https://schungx.github.io/rhai/start/builds/minimal.html) by excluding unneeded language [features](https://schungx.github.io/rhai/start/features.html).
|
||||
* Easy custom API development via [plugins](https://schungx.github.io/rhai/plugins/index.html) system powered by procedural macros.
|
||||
|
||||
Protection against attacks
|
||||
--------------------------
|
||||
@ -69,13 +70,11 @@ Scripts can be evaluated directly from the editor.
|
||||
License
|
||||
-------
|
||||
|
||||
Licensed under either:
|
||||
Licensed under either of the following, at your choice:
|
||||
|
||||
* [Apache License, Version 2.0](https://github.com/jonathandturner/rhai/blob/master/LICENSE-APACHE.txt), or
|
||||
* [MIT license](https://github.com/jonathandturner/rhai/blob/master/LICENSE-MIT.txt)
|
||||
|
||||
at your choice.
|
||||
|
||||
Unless explicitly stated otherwise, any contribution intentionally submitted
|
||||
for inclusion in this crate, as defined in the Apache-2.0 license, shall
|
||||
be dual-licensed as above, without any additional terms or conditions.
|
||||
|
13
RELEASES.md
13
RELEASES.md
@ -4,6 +4,9 @@ Rhai Release Notes
|
||||
Version 0.19.0
|
||||
==============
|
||||
|
||||
The major new feature for this version is _Plugins_ support, powered by procedural macros.
|
||||
Plugins make it extremely easy to develop and register Rust functions with an `Engine`.
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
@ -17,10 +20,10 @@ Breaking changes
|
||||
----------------
|
||||
|
||||
* `Engine::register_set_result` and `Engine::register_indexer_set_result` now take a function that returns `Result<(), Box<EvalAltResult>>`.
|
||||
* `Engine::register_indexer_XXX` and `Module::set_indexer_XXX` panic when the type is `Arrary`, `Map` or `String`.
|
||||
* `Engine::register_indexer_XXX` and `Module::set_indexer_XXX` panic when the type is `Array`, `Map` or `String`.
|
||||
* `EvalAltResult` has a new variant `ErrorInModule` which holds errors when loading an external module.
|
||||
* `Module::eval_ast_as_new` now takes an extra boolean parameter, indicating whether to encapsulate the entire module into a separate namespace.
|
||||
* Functions in `FileModuleResolver` loaded modules now can cross-call each other, but cannot access the global namespace. For the old behavior, use `MergingFileModuleResolver` instead.
|
||||
* Functions in `FileModuleResolver` loaded modules now can cross-call each other in addition to functions in the global namespace. For the old behavior, use `MergingFileModuleResolver` instead.
|
||||
* New `EvalAltResult::ErrorInModule` variant capturing errors when loading a module from a script file.
|
||||
|
||||
New features
|
||||
@ -28,11 +31,11 @@ New features
|
||||
|
||||
* Plugins support via procedural macros.
|
||||
* Scripted functions are allowed in packages.
|
||||
* `parse_int` and `parse_float` functions.
|
||||
* `parse_int` and `parse_float` functions for parsing numbers; `split` function for splitting strings.
|
||||
* `AST::iter_functions` and `Module::iter_script_fn_info` to iterate functions.
|
||||
* Functions iteration functions now take `FnMut` instead of `Fn`.
|
||||
* Functions iteration functions for `AST` and `Module` now take `FnMut` instead of `Fn`.
|
||||
* New `FileModuleResolver` that encapsulates the entire `AST` of the module script, allowing function cross-calling. The old version is renamed `MergingFileModuleResolver`.
|
||||
* `split` function for splitting strings.
|
||||
* `+` and `-` operators for timestamps to increment/decrement by seconds.
|
||||
|
||||
|
||||
Version 0.18.3
|
||||
|
@ -81,15 +81,15 @@ The Rhai Scripting Language
|
||||
2. [Overloading](language/overload.md)
|
||||
3. [Namespaces](language/fn-namespaces.md)
|
||||
4. [Function Pointers](language/fn-ptr.md)
|
||||
5. [Anonymous Functions](language/fn-anon.md)
|
||||
6. [Currying](language/fn-curry.md)
|
||||
5. [Currying](language/fn-curry.md)
|
||||
6. [Anonymous Functions](language/fn-anon.md)
|
||||
7. [Closures](language/fn-closure.md)
|
||||
16. [Print and Debug](language/print-debug.md)
|
||||
17. [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](rust/modules/create.md)
|
||||
4. [Create from AST](language/modules/ast.md)
|
||||
4. [Create from AST](rust/modules/ast.md)
|
||||
5. [Module Resolvers](rust/modules/resolvers.md)
|
||||
1. [Custom Implementation](rust/modules/imp-resolver.md)
|
||||
18. [Eval Statement](language/eval.md)
|
||||
|
@ -37,7 +37,7 @@ Dynamic
|
||||
|
||||
* Dynamic dispatch via [function pointers] with additional support for [currying].
|
||||
|
||||
* Closures via [automatic currying] with capturing shared variables from the external scope.
|
||||
* [Closures] that can capture shared variables.
|
||||
|
||||
* Some support for [object-oriented programming (OOP)][OOP].
|
||||
|
||||
|
@ -4,7 +4,7 @@ Keywords List
|
||||
{{#include ../links.md}}
|
||||
|
||||
| Keyword | Description | Inactive under | Overloadable |
|
||||
| :-------------------: | ---------------------------------------- | :-------------: | :----------: |
|
||||
| :-------------------: | ------------------------------------------- | :-------------: | :----------: |
|
||||
| `true` | boolean true literal | | no |
|
||||
| `false` | boolean false literal | | no |
|
||||
| `let` | variable declaration | | no |
|
||||
@ -15,9 +15,9 @@ Keywords List
|
||||
| `while` | while loop | | no |
|
||||
| `loop` | infinite loop | | no |
|
||||
| `for` | for loop | | no |
|
||||
| `in` | containment test, part of for loop | | no |
|
||||
| `in` | 1) containment test<br/>2) part of for loop | | no |
|
||||
| `continue` | continue a loop at the next iteration | | no |
|
||||
| `break` | loop breaking | | no |
|
||||
| `break` | break out of loop iteration | | no |
|
||||
| `return` | return value | | no |
|
||||
| `throw` | throw exception | | no |
|
||||
| `import` | import module | [`no_module`] | no |
|
||||
@ -25,7 +25,7 @@ Keywords List
|
||||
| `as` | alias for variable export | [`no_module`] | no |
|
||||
| `private` | mark function private | [`no_function`] | no |
|
||||
| `fn` (lower-case `f`) | function definition | [`no_function`] | no |
|
||||
| `Fn` (capital `F`) | function to create a [function pointer] | | yes |
|
||||
| `Fn` (capital `F`) | create a [function pointer] | | yes |
|
||||
| `call` | call a [function pointer] | | no |
|
||||
| `curry` | curry a [function pointer] | | no |
|
||||
| `this` | reference to base object for method call | [`no_function`] | no |
|
||||
|
@ -4,11 +4,11 @@ Literals Syntax
|
||||
{{#include ../links.md}}
|
||||
|
||||
| Type | Literal syntax |
|
||||
| :--------------------------------: | :------------------------------------------------------------------------------: |
|
||||
| `INT` | `42`, `-123`, `0`,<br/>`0x????..` (hex), `0b????..` (binary), `0o????..` (octal) |
|
||||
| :--------------------------------: | :-----------------------------------------------------------------------------------------: |
|
||||
| `INT` | decimal: `42`, `-123`, `0`<br/>hex: `0x????..`<br/>binary: `0b????..`<br/>octal: `0o????..` |
|
||||
| `FLOAT` | `42.0`, `-123.456`, `0.0` |
|
||||
| [String] | `"... \x?? \u???? \U???????? ..."` |
|
||||
| Character | `"... \x?? \u???? \U???????? ..."` |
|
||||
| Character | single: `'?'`<br/>ASCII hex: `'\x??'`<br/>Unicode: `'\u????'`, `'\U????????'` |
|
||||
| [`Array`] | `[ ???, ???, ??? ]` |
|
||||
| [Object map] | `#{ a: ???, b: ???, c: ???, "def": ??? }` |
|
||||
| Boolean true | `true` |
|
||||
|
@ -40,13 +40,13 @@ Symbols
|
||||
| ------------ | ------------------------ |
|
||||
| `:` | property value separator |
|
||||
| `::` | module path separator |
|
||||
| `#` | _Reserved_ |
|
||||
| `=>` | _Reserved_ |
|
||||
| `->` | _Reserved_ |
|
||||
| `<-` | _Reserved_ |
|
||||
| `===` | _Reserved_ |
|
||||
| `!==` | _Reserved_ |
|
||||
| `:=` | _Reserved_ |
|
||||
| `::<` .. `>` | _Reserved_ |
|
||||
| `@` | _Reserved_ |
|
||||
| `(*` .. `*)` | _Reserved_ |
|
||||
| `#` | _reserved_ |
|
||||
| `=>` | _reserved_ |
|
||||
| `->` | _reserved_ |
|
||||
| `<-` | _reserved_ |
|
||||
| `===` | _reserved_ |
|
||||
| `!==` | _reserved_ |
|
||||
| `:=` | _reserved_ |
|
||||
| `::<` .. `>` | _reserved_ |
|
||||
| `@` | _reserved_ |
|
||||
| `(*` .. `*)` | _reserved_ |
|
||||
|
@ -4,7 +4,7 @@ Extend Rhai with Custom Syntax
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
For the ultimate advantageous, there is a built-in facility to _extend_ the Rhai language
|
||||
For the ultimate adventurous, there is a built-in facility to _extend_ the Rhai language
|
||||
with custom-defined _syntax_.
|
||||
|
||||
But before going off to define the next weird statement type, heed this warning:
|
||||
@ -28,7 +28,7 @@ Where This Might Be Useful
|
||||
|
||||
* Where a custom syntax _significantly_ simplifies the code and _significantly_ enhances understanding of the code's intent.
|
||||
|
||||
* Where certain logic cannot be easily encapsulated inside a function. This is usually the case where _closures_ are required, because Rhai does not have closures.
|
||||
* Where certain logic cannot be easily encapsulated inside a function.
|
||||
|
||||
* Where you just want to confuse your user and make their lives miserable, because you can.
|
||||
|
||||
@ -154,10 +154,10 @@ let result = engine.eval_expression_tree(context, scope, expr)?;
|
||||
|
||||
New variables maybe declared (usually with a variable name that is passed in via `$ident$).
|
||||
|
||||
It can simply be pushed into the [`scope`].
|
||||
It can simply be pushed into the [`Scope`].
|
||||
|
||||
However, beware that all new variables must be declared _prior_ to evaluating any expression tree.
|
||||
In other words, any `scope.push(...)` calls must come _before_ any `engine::eval_expression_tree(...)` calls.
|
||||
In other words, any `Scope::push` calls must come _before_ any `Engine::eval_expression_tree` calls.
|
||||
|
||||
```rust
|
||||
let var_name = inputs[0].get_variable_name().unwrap().to_string();
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
Raw `Engine`
|
||||
===========
|
||||
|
||||
|
@ -31,19 +31,20 @@ let mut scope = Scope::new();
|
||||
scope
|
||||
.push("y", 42_i64)
|
||||
.push("z", 999_i64)
|
||||
.push_constant("MY_NUMBER", 123_i64) // constants can also be added
|
||||
.set_value("s", "hello, world!".to_string()); //'set_value' adds a variable when one doesn't exist
|
||||
// remember to use 'String', not '&str'
|
||||
|
||||
// First invocation
|
||||
engine.eval_with_scope::<()>(&mut scope, r"
|
||||
let x = 4 + 5 - y + z + s.len;
|
||||
let x = 4 + 5 - y + z + MY_NUMBER + s.len;
|
||||
y = 1;
|
||||
")?;
|
||||
|
||||
// Second invocation using the same state
|
||||
let result = engine.eval_with_scope::<i64>(&mut scope, "x")?;
|
||||
|
||||
println!("result: {}", result); // prints 979
|
||||
println!("result: {}", result); // prints 1102
|
||||
|
||||
// Variable y is changed in the script - read it with 'get_value'
|
||||
assert_eq!(scope.get_value::<i64>("y").expect("variable y should exist"), 1);
|
||||
|
@ -31,19 +31,19 @@ Built-in Functions
|
||||
The following methods (mostly defined in the [`BasicArrayPackage`][packages] but excluded if using a [raw `Engine`]) operate on arrays:
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| ------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| ------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| `push` | element to insert | inserts an element at the end |
|
||||
| `+=` operator | array, element to insert (not another array) | inserts an element at the end |
|
||||
| `append` | array to append | concatenates the second array to the end of the first |
|
||||
| `+=` operator | array, array to append | concatenates the second array to the end of the first |
|
||||
| `+` operator | first array, second array | concatenates the first array with the second |
|
||||
| `insert` | element to insert, position<br/>(beginning if <= 0, end if >= length) | inserts an element at a certain index |
|
||||
| `+=` operator | 1) array<br/>2) element to insert (not another array) | inserts an element at the end |
|
||||
| `+=` operator | 1) array<br/>2) array to append | concatenates the second array to the end of the first |
|
||||
| `+` operator | 1) first array<br/>2) second array | concatenates the first array with the second |
|
||||
| `insert` | 1) element to insert<br/>2) position (beginning if <= 0, end if >= length) | inserts an element at a certain index |
|
||||
| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
|
||||
| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) |
|
||||
| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid |
|
||||
| `reverse` | _none_ | reverses the array |
|
||||
| `len` method and property | _none_ | returns the number of elements |
|
||||
| `pad` | element to pad, target length | pads the array with an element to at least a specified length |
|
||||
| `pad` | 1) target length<br/>2) element to pad | pads the array with an element to at least a specified length |
|
||||
| `clear` | _none_ | empties the array |
|
||||
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
|
||||
|
||||
|
@ -16,8 +16,45 @@ x = 123; // <- syntax error: cannot assign to constant
|
||||
```
|
||||
|
||||
Unlike variables which need not have initial values (default to [`()`]),
|
||||
constants must be assigned one, and it must be a constant _value_, not an expression.
|
||||
constants must be assigned one, and it must be a [_literal value_](../appendix/literals.md),
|
||||
not an expression.
|
||||
|
||||
```rust
|
||||
const x = 40 + 2; // <- syntax error: cannot assign expression to constant
|
||||
```
|
||||
|
||||
|
||||
Manually Add Constant into Custom Scope
|
||||
--------------------------------------
|
||||
|
||||
It is possible to add a constant into a custom [`Scope`] so it'll be available to scripts
|
||||
running with that [`Scope`].
|
||||
|
||||
When added to a custom [`Scope`], a constant can hold any value, not just a literal value.
|
||||
|
||||
It is very useful to have a constant value hold a [custom type], which essentially acts
|
||||
as a [_singleton_](../patterns/singleton.md). The singleton object can be modified via its
|
||||
registered API - being a constant only prevents it from being re-assigned or operated upon by Rhai;
|
||||
mutating it via a Rust function is still allowed.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Scope};
|
||||
|
||||
struct TestStruct(i64); // custom type
|
||||
|
||||
let engine = Engine::new()
|
||||
.register_type_with_name::<TestStruct>("TestStruct") // register custom type
|
||||
.register_get_set("value",
|
||||
|obj: &mut TestStruct| obj.0, // property getter
|
||||
|obj: &mut TestStruct, value: i64| obj.0 = value // property setter
|
||||
);
|
||||
|
||||
let mut scope = Scope::new(); // create custom scope
|
||||
|
||||
scope.push_constant("MY_NUMBER", TestStruct(123_i64)); // add constant variable
|
||||
|
||||
engine.consume_with_scope(&mut scope, r"
|
||||
MY_NUMBER.value = 42; // constant objects can be modified
|
||||
print(MY_NUMBER.value); // prints 42
|
||||
")?;
|
||||
```
|
||||
|
@ -3,6 +3,10 @@ Value Conversions
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Convert Between Integer and Floating-Point
|
||||
-----------------------------------------
|
||||
|
||||
The `to_float` function converts a supported number to `FLOAT` (defaults to `f64`).
|
||||
|
||||
The `to_int` function converts a supported number to `INT` (`i32` or `i64` depending on [`only_i32`]).
|
||||
@ -22,3 +26,31 @@ let c = 'X'; // character
|
||||
|
||||
print("c is '" + c + "' and its code is " + c.to_int()); // prints "c is 'X' and its code is 88"
|
||||
```
|
||||
|
||||
|
||||
Parse String into Number
|
||||
------------------------
|
||||
|
||||
The `parse_float` function converts a [string] into a `FLOAT` (defaults to `f64`).
|
||||
|
||||
The `parse_int` function converts a [string] into an `INT` (`i32` or `i64` depending on [`only_i32`]).
|
||||
An optional radix (2-36) can be provided to parse the [string] into a number of the specified radix.
|
||||
|
||||
```rust
|
||||
let x = parse_float("123.4"); // parse as floating-point
|
||||
x == 123.4;
|
||||
type_of(x) == "f64";
|
||||
|
||||
let dec = parse_int("42"); // parse as decimal
|
||||
let dec = parse_int("42", 10); // radix = 10 is the default
|
||||
dec == 42;
|
||||
type_of(dec) == "i64";
|
||||
|
||||
let bin = parse_int("110", 2); // parse as binary (radix = 2)
|
||||
bin == 0b110;
|
||||
type_of(bin) == "i64";
|
||||
|
||||
let hex = parse_int("ab", 16); // parse as hex (radix = 16)
|
||||
hex == 0xab;
|
||||
type_of(hex) == "i64";
|
||||
```
|
||||
|
@ -83,10 +83,14 @@ match item.type_name() { // 'type_name' returns the name
|
||||
"i64" => ...
|
||||
"alloc::string::String" => ...
|
||||
"bool" => ...
|
||||
"path::to::module::TestStruct" => ...
|
||||
"crate::path::to::module::TestStruct" => ...
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** `type_name` always returns the _full_ Rust path name of the type, even when the type
|
||||
has been registered with a friendly name via `Engine::register_type_with_name`. This behavior
|
||||
is different from that of the [`type_of`][`type_of()`] function in Rhai.
|
||||
|
||||
|
||||
Conversion Traits
|
||||
----------------
|
||||
@ -100,4 +104,5 @@ The following conversion traits are implemented for `Dynamic`:
|
||||
* `From<String>`
|
||||
* `From<char>`
|
||||
* `From<Vec<T>>` (into an [array])
|
||||
* `From<HashMap<String, T>>` (into an [object map]).
|
||||
* `From<HashMap<String, T>>` (into an [object map])
|
||||
* `From<Instant>` (into a [timestamp] if not [`no_std`])
|
||||
|
@ -23,43 +23,25 @@ Therefore, similar to closures in many languages, these captured shared values p
|
||||
reference counting, and may be read or modified even after the variables that hold them
|
||||
go out of scope and no longer exist.
|
||||
|
||||
Use the `is_shared` function to check whether a particular value is a shared value.
|
||||
Use the `Dynamic::is_shared` function to check whether a particular value is a shared value.
|
||||
|
||||
Automatic currying can be turned off via the [`no_closure`] feature.
|
||||
|
||||
|
||||
Actual Implementation
|
||||
---------------------
|
||||
|
||||
The actual implementation de-sugars to:
|
||||
|
||||
1. Keeping track of what variables are accessed inside the anonymous function,
|
||||
|
||||
2. If a variable is not defined within the anonymous function's scope, it is looked up _outside_ the function and
|
||||
in the current execution scope - where the anonymous function is created.
|
||||
|
||||
3. The variable is added to the parameters list of the anonymous function, at the front.
|
||||
|
||||
4. The variable is then converted into a **reference-counted shared value**.
|
||||
|
||||
An [anonymous function] which captures an external variable is the only way to create a reference-counted shared value in Rhai.
|
||||
|
||||
5. The shared value is then [curried][currying] into the [function pointer] itself, essentially carrying a reference to that shared value
|
||||
and inserting it into future calls of the function.
|
||||
|
||||
This process is called _Automatic Currying_, and is the mechanism through which Rhai simulates normal closures.
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
```rust
|
||||
let x = 1; // a normal variable
|
||||
|
||||
x.is_shared() == false;
|
||||
|
||||
let f = |y| x + y; // variable 'x' is auto-curried (captured) into 'f'
|
||||
|
||||
x.is_shared() == true; // 'x' is now a shared value!
|
||||
|
||||
f.call(2) == 3; // 1 + 2 == 3
|
||||
|
||||
x = 40; // changing 'x'...
|
||||
|
||||
f.call(2) == 42; // the value of 'x' is 40 because 'x' is shared
|
||||
@ -117,6 +99,8 @@ will occur and the script will terminate with an error.
|
||||
```rust
|
||||
let x = 20;
|
||||
|
||||
x.is_shared() == false; // 'x' is not shared, so no data race is possible
|
||||
|
||||
let f = |a| this += x + a; // 'x' is captured in this closure
|
||||
|
||||
x.is_shared() == true; // now 'x' is shared
|
||||
@ -152,6 +136,26 @@ x.call(f, 2);
|
||||
TL;DR
|
||||
-----
|
||||
|
||||
### Q: How is it actually implemented?
|
||||
|
||||
The actual implementation of closures de-sugars to:
|
||||
|
||||
1. Keeping track of what variables are accessed inside the anonymous function,
|
||||
|
||||
2. If a variable is not defined within the anonymous function's scope, it is looked up _outside_ the function and
|
||||
in the current execution scope - where the anonymous function is created.
|
||||
|
||||
3. The variable is added to the parameters list of the anonymous function, at the front.
|
||||
|
||||
4. The variable is then converted into a **reference-counted shared value**.
|
||||
|
||||
An [anonymous function] which captures an external variable is the only way to create a reference-counted shared value in Rhai.
|
||||
|
||||
5. The shared value is then [curried][currying] into the [function pointer] itself, essentially carrying a reference to that shared value
|
||||
and inserting it into future calls of the function.
|
||||
|
||||
This process is called _Automatic Currying_, and is the mechanism through which Rhai simulates normal closures.
|
||||
|
||||
### Q: Why are closures implemented as automatic currying?
|
||||
|
||||
In concept, a closure _closes_ over captured variables from the outer scope - that's why
|
||||
|
@ -43,7 +43,9 @@ This aspect is very similar to JavaScript before ES6 modules.
|
||||
// Compile a script into AST
|
||||
let ast1 = engine.compile(
|
||||
r#"
|
||||
fn get_message() { "Hello!" } // greeting message
|
||||
fn get_message() {
|
||||
"Hello!" // greeting message
|
||||
}
|
||||
|
||||
fn say_hello() {
|
||||
print(get_message()); // prints message
|
||||
|
@ -61,8 +61,8 @@ hello.call(0); // error: function not found - 'hello_world (i64)'
|
||||
Global Namespace Only
|
||||
--------------------
|
||||
|
||||
Because of their dynamic nature, function pointers cannot refer to functions in a _module_ [namespace][function namespace]
|
||||
(i.e. functions in [`import`]-ed modules). They can only refer to functions within the global [namespace][function namespace].
|
||||
Because of their dynamic nature, function pointers cannot refer to functions in [`import`]-ed [modules].
|
||||
They can only refer to functions within the global [namespace][function namespace].
|
||||
See [function namespaces] for more details.
|
||||
|
||||
```rust
|
||||
|
@ -44,10 +44,13 @@ Representation of Numbers
|
||||
------------------------
|
||||
|
||||
JSON numbers are all floating-point while Rhai supports integers (`INT`) and floating-point (`FLOAT`) if
|
||||
the [`no_float`] feature is not used. Most common generators of JSON data distinguish between
|
||||
integer and floating-point values by always serializing a floating-point number with a decimal point
|
||||
(i.e. `123.0` instead of `123` which is assumed to be an integer). This style can be used successfully
|
||||
with Rhai [object maps].
|
||||
the [`no_float`] feature is not used.
|
||||
|
||||
Most common generators of JSON data distinguish between integer and floating-point values by always
|
||||
serializing a floating-point number with a decimal point (i.e. `123.0` instead of `123` which is
|
||||
assumed to be an integer).
|
||||
|
||||
This style can be used successfully with Rhai [object maps].
|
||||
|
||||
|
||||
Parse JSON with Sub-Objects
|
||||
@ -68,9 +71,10 @@ A JSON object hash starting with `#{` is handled transparently by `Engine::parse
|
||||
// JSON with sub-object 'b'.
|
||||
let json = r#"{"a":1, "b":{"x":true, "y":false}}"#;
|
||||
|
||||
let new_json = json.replace("{" "#{");
|
||||
// Our JSON text does not contain the '{' character, so off we go!
|
||||
let new_json = json.replace("{", "#{");
|
||||
|
||||
// The leading '{' will also be replaced to '#{', but parse_json can handle this.
|
||||
// The leading '{' will also be replaced to '#{', but 'parse_json' handles this just fine.
|
||||
let map = engine.parse_json(&new_json, false)?;
|
||||
|
||||
map.len() == 2; // 'map' contains two properties: 'a' and 'b'
|
||||
|
@ -48,9 +48,9 @@ an equivalent method coded in script, where the object is accessed via the `this
|
||||
The following table illustrates the differences:
|
||||
|
||||
| Function type | Parameters | Object reference | Function signature |
|
||||
| :-----------: | :--------: | :--------------------: | :-----------------------------------------------------: |
|
||||
| Native Rust | _n_ + 1 | first `&mut` parameter | `fn method<T, U, V>`<br/>`(obj: &mut T, x: U, y: V) {}` |
|
||||
| Rhai script | _n_ | `this` | `fn method(x, y) {}` |
|
||||
| :-----------: | :--------: | :-----------------------: | :---------------------------: |
|
||||
| Native Rust | _N_ + 1 | first `&mut T` parameter | `Fn(obj: &mut T, x: U, y: V)` |
|
||||
| Rhai script | _N_ | `this` (of type `&mut T`) | `Fn(x: U, y: V)` |
|
||||
|
||||
|
||||
`&mut` is Efficient, Except for `ImmutableString`
|
||||
|
@ -1,59 +0,0 @@
|
||||
Create a Module from an AST
|
||||
==========================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
It is easy to convert a pre-compiled [`AST`] into a module: just use `Module::eval_ast_as_new`.
|
||||
|
||||
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 ('private_namespace'), 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. Otherwise module script functions access the global namespace.
|
||||
//
|
||||
// This incurs additional overhead, avoidable by setting 'private_namespace' to false.
|
||||
let module = Module::eval_ast_as_new(Scope::new(), &ast, true, &engine)?;
|
||||
|
||||
// 'module' now can be loaded into a custom 'Scope' for future use. It contains:
|
||||
// - sub-module: 'foobar' (renamed from 'extra')
|
||||
// - functions: 'calc', 'add_len'
|
||||
// - variables: 'abc' (renamed from 'x'), 'foo', 'hello'
|
||||
```
|
@ -3,35 +3,16 @@ Export Variables, Functions and Sub-Modules in Module
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
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.
|
||||
|
||||
The parameter `private_namespace` in `Module::eval_ast_as_new` determines the exact behavior of
|
||||
functions exposed by the module and the namespace that they can access:
|
||||
|
||||
| `private_namespace` value | Behavior of module functions | Namespace | Call global functions | Call functions in same module |
|
||||
| :-----------------------: | ---------------------------------------------------- | :-------: | :-------------------: | :---------------------------: |
|
||||
| `true` | encapsulate the entire `AST` into each function call | module | no | yes |
|
||||
| `false` | register each function independently | global | yes | no |
|
||||
|
||||
|
||||
Global Variables
|
||||
----------------
|
||||
Export Global Variables
|
||||
----------------------
|
||||
|
||||
The `export` statement, which can only be at global level, exposes selected variables as members of a module.
|
||||
|
||||
Variables not exported are _private_ and hidden. They are merely used to initialize the module,
|
||||
but cannot be accessed from outside.
|
||||
|
||||
Everything exported from a module is **constant** (**read-only**).
|
||||
Everything exported from a module is **constant** (i.e. read-only).
|
||||
|
||||
```rust
|
||||
// This is a module script.
|
||||
@ -53,8 +34,8 @@ export x as answer; // the variable 'x' is exported under the alias 'answer'
|
||||
```
|
||||
|
||||
|
||||
Functions
|
||||
---------
|
||||
Export Functions
|
||||
----------------
|
||||
|
||||
All functions are automatically exported, _unless_ it is explicitly opt-out with the [`private`] prefix.
|
||||
|
||||
|
@ -10,25 +10,23 @@ If an [object map]'s property holds a [function pointer], the property can simpl
|
||||
a normal method in method-call syntax. This is a _short-hand_ to avoid the more verbose syntax
|
||||
of using the `call` function keyword.
|
||||
|
||||
When a property holding a [function pointer] (which incudes [closures]) is called like a method,
|
||||
When a property holding a [function pointer] or a [closure] is called like a method,
|
||||
what happens next depends on whether the target function is a native Rust function or
|
||||
a script-defined function.
|
||||
|
||||
If it is a registered native Rust method function, it is called directly.
|
||||
* If it is a registered native Rust function, it is called directly in _method-call_ style with the [object map] inserted as the first argument.
|
||||
|
||||
If it is a script-defined function, the `this` variable within the function body is bound
|
||||
to the [object map] before the function is called. There is no way to simulate this behavior
|
||||
via a normal function-call syntax because all scripted function arguments are passed by value.
|
||||
* If it is a script-defined function, the `this` variable within the function body is bound to the [object map] before the function is called.
|
||||
|
||||
```rust
|
||||
let obj = #{
|
||||
data: 40,
|
||||
action: || this.data += x // 'action' holds a function pointer which is a closure
|
||||
action: || this.data += x // 'action' holds a closure
|
||||
};
|
||||
|
||||
obj.action(2); // Calls the function pointer with `this` bound to 'obj'
|
||||
obj.action(2); // calls the function pointer with `this` bound to 'obj'
|
||||
|
||||
obj.call(obj.action, 2); // The above de-sugars to this
|
||||
obj.call(obj.action, 2); // <- the above de-sugars to this
|
||||
|
||||
obj.data == 42;
|
||||
|
||||
|
@ -58,13 +58,13 @@ The following methods (defined in the [`BasicMapPackage`][packages] but excluded
|
||||
operate on object maps:
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| ---------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| ---------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `has` | property name | does the object map contain a property of a particular name? |
|
||||
| `len` | _none_ | returns the number of properties |
|
||||
| `clear` | _none_ | empties the object map |
|
||||
| `remove` | property name | removes a certain property and returns it ([`()`] if the property does not exist) |
|
||||
| `+=` operator, `mixin` | second object map | mixes in all the properties of the second object map to the first (values of properties with the same names replace the existing values) |
|
||||
| `+` operator | first object map, second object map | merges the first object map with the second |
|
||||
| `+` operator | 1) first object map<br/>2) second object map | merges the first object map with the second |
|
||||
| `fill_with` | second object map | adds in all properties of the second object map that do not exist in the object map |
|
||||
| `keys` | _none_ | returns an [array] of all the property names (in random order), not available under [`no_index`] |
|
||||
| `values` | _none_ | returns an [array] of all the property values (in random order), not available under [`no_index`] |
|
||||
|
@ -7,18 +7,18 @@ The following standard methods (mostly defined in the [`MoreStringPackage`][pack
|
||||
using a [raw `Engine`]) operate on [strings]:
|
||||
|
||||
| Function | Parameter(s) | Description |
|
||||
| ------------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| ------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| `len` method and property | _none_ | returns the number of characters (not number of bytes) in the string |
|
||||
| `pad` | character to pad, target length | pads the string with an character to at least a specified length |
|
||||
| `pad` | 1) character to pad<br/>2) target length | pads the string with an character to at least a specified length |
|
||||
| `+=` operator, `append` | character/string to append | Adds a character or a string to the end of another string |
|
||||
| `clear` | _none_ | empties the string |
|
||||
| `truncate` | target length | cuts off the string at exactly a specified number of characters |
|
||||
| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string |
|
||||
| `index_of` | character/sub-string to search for, start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found |
|
||||
| `sub_string` | start index, length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) |
|
||||
| `index_of` | 1) character/sub-string to search for<br/>2) start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found |
|
||||
| `sub_string` | 1) start index<br/>2) length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) |
|
||||
| `split` | delimiter character/string | splits the string by the specified delimiter, returning an [array] of string segments; not available under [`no_index`] |
|
||||
| `crop` | start index, length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) |
|
||||
| `replace` | target character/sub-string, replacement character/string | replaces a sub-string with another |
|
||||
| `crop` | 1) start index<br/>2) length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) |
|
||||
| `replace` | 1) target character/sub-string<br/>2) replacement character/string | replaces a sub-string with another |
|
||||
| `trim` | _none_ | trims the string of whitespace at the beginning and end |
|
||||
|
||||
Examples
|
||||
|
@ -44,16 +44,16 @@ Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicod
|
||||
Standard escape sequences:
|
||||
|
||||
| Escape sequence | Meaning |
|
||||
| --------------- | ------------------------------ |
|
||||
| --------------- | -------------------------------- |
|
||||
| `\\` | back-slash `\` |
|
||||
| `\t` | tab |
|
||||
| `\r` | carriage-return `CR` |
|
||||
| `\n` | line-feed `LF` |
|
||||
| `\"` | double-quote `"` in strings |
|
||||
| `\'` | single-quote `'` in characters |
|
||||
| `\x`_xx_ | Unicode in 2-digit hex |
|
||||
| `\u`_xxxx_ | Unicode in 4-digit hex |
|
||||
| `\U`_xxxxxxxx_ | Unicode in 8-digit hex |
|
||||
| `\"` | double-quote `"` |
|
||||
| `\'` | single-quote `'` |
|
||||
| `\x`_xx_ | ASCII character in 2-digit hex |
|
||||
| `\u`_xxxx_ | Unicode character in 4-digit hex |
|
||||
| `\U`_xxxxxxxx_ | Unicode character in 8-digit hex |
|
||||
|
||||
|
||||
Differences from Rust Strings
|
||||
|
@ -24,3 +24,24 @@ if type_of(x) == "string" {
|
||||
do_something_with_string(x);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Custom Types
|
||||
------------
|
||||
|
||||
`type_of()` a [custom type] returns:
|
||||
|
||||
* if registered via `Engine::register_type_with_name` - the registered name
|
||||
|
||||
* if registered via `Engine::register_type` - the full Rust path name
|
||||
|
||||
```rust
|
||||
struct TestStruct1;
|
||||
struct TestStruct2;
|
||||
|
||||
engine
|
||||
// type_of(struct1) == "crate::path::to::module::TestStruct1"
|
||||
.register_type::<TestStruct1>()
|
||||
// type_of(struct2) == "MyStruct"
|
||||
.register_type_with_name::<TestStruct2>("MyStruct");
|
||||
```
|
||||
|
@ -49,6 +49,8 @@
|
||||
[`Dynamic`]: {{rootUrl}}/language/dynamic.md
|
||||
[`to_int`]: {{rootUrl}}/language/convert.md
|
||||
[`to_float`]: {{rootUrl}}/language/convert.md
|
||||
[`parse_int`]: {{rootUrl}}/language/convert.md
|
||||
[`parse_float`]: {{rootUrl}}/language/convert.md
|
||||
|
||||
[custom type]: {{rootUrl}}/rust/custom.md
|
||||
[custom types]: {{rootUrl}}/rust/custom.md
|
||||
|
@ -51,6 +51,12 @@ let config: Rc<RefCell<Config>> = Rc::new(RefCell::new(Default::default()));
|
||||
|
||||
### Register Config API
|
||||
|
||||
The trick to building a Config API is to clone the shared configuration object and
|
||||
move it into each function registration as a closure.
|
||||
|
||||
It is not possible to use a [plugin module] to achieve this, so each function must
|
||||
be registered one after another.
|
||||
|
||||
```rust
|
||||
// Notice 'move' is used to move the shared configuration object into the closure.
|
||||
let cfg = config.clone();
|
||||
|
@ -64,6 +64,12 @@ let bunny: Rc<RefCell<EnergizerBunny>> = Rc::new(RefCell::(EnergizerBunny::new()
|
||||
|
||||
### Register Control API
|
||||
|
||||
The trick to building a Control API is to clone the shared API object and
|
||||
move it into each function registration as a closure.
|
||||
|
||||
It is not possible to use a [plugin module] to achieve this, so each function must
|
||||
be registered one after another.
|
||||
|
||||
```rust
|
||||
// Notice 'move' is used to move the shared API object into the closure.
|
||||
let b = bunny.clone();
|
||||
|
@ -58,7 +58,7 @@ impl EnergizerBunny {
|
||||
pub fn new () -> Self { ... }
|
||||
pub fn go (&mut self) { ... }
|
||||
pub fn stop (&mut self) { ... }
|
||||
pub fn is_going (&self) { ... }
|
||||
pub fn is_going (&self) -> bol { ... }
|
||||
pub fn get_speed (&self) -> i64 { ... }
|
||||
pub fn set_speed (&mut self, speed: i64) { ... }
|
||||
pub fn turn (&mut self, left_turn: bool) { ... }
|
||||
@ -77,13 +77,23 @@ let SharedBunnyType = Rc<RefCell<EnergizerBunny>>;
|
||||
engine.register_type_with_name::<SharedBunnyType>("EnergizerBunny");
|
||||
```
|
||||
|
||||
### Register Methods and Getters/Setters
|
||||
### Develop a Plugin with Methods and Getters/Setters
|
||||
|
||||
The easiest way to develop a complete set of API for a [custom type] is via a [plugin module].
|
||||
|
||||
```rust
|
||||
engine
|
||||
.register_get_set("power",
|
||||
|bunny: &mut SharedBunnyType| bunny.borrow().is_going(),
|
||||
|bunny: &mut SharedBunnyType, on: bool| {
|
||||
use rhai::plugins::*;
|
||||
|
||||
#[export_module]
|
||||
pub mod bunny_api {
|
||||
pub const MAX_SPEED: i64 = 100;
|
||||
|
||||
#[rhai_fn(get = "power")]
|
||||
pub fn get_power(bunny: &mut SharedBunnyType) -> bool {
|
||||
bunny.borrow().is_going()
|
||||
}
|
||||
#[rhai_fn(set = "power")]
|
||||
pub fn set_power(bunny: &mut SharedBunnyType, on: bool) {
|
||||
if on {
|
||||
if bunny.borrow().is_going() {
|
||||
println!("Still going...");
|
||||
@ -98,16 +108,21 @@ engine
|
||||
}
|
||||
}
|
||||
}
|
||||
).register_get("speed", |bunny: &mut SharedBunnyType| {
|
||||
#[rhai_fn(get = "speed")]
|
||||
pub fn get_speed(bunny: &mut SharedBunnyType) -> i64 {
|
||||
if bunny.borrow().is_going() {
|
||||
bunny.borrow().get_speed()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}).register_set_result("speed", |bunny: &mut SharedBunnyType, speed: i64| {
|
||||
}
|
||||
#[rhai_fn(set = "speed", return_raw)]
|
||||
pub fn set_speed(bunny: &mut SharedBunnyType, speed: i64)
|
||||
-> Result<Dynamic, Box<EvalAltResult>>
|
||||
{
|
||||
if speed <= 0 {
|
||||
Err("Speed must be positive!".into())
|
||||
} else if speed > 100 {
|
||||
} else if speed > MAX_SPEED {
|
||||
Err("Bunny will be going too fast!".into())
|
||||
} else if !bunny.borrow().is_going() {
|
||||
Err("Bunny is not yet going!".into())
|
||||
@ -115,15 +130,20 @@ engine
|
||||
b.borrow_mut().set_speed(speed);
|
||||
Ok(().into())
|
||||
}
|
||||
}).register_fn("turn_left", |bunny: &mut SharedBunnyType| {
|
||||
}
|
||||
pub fn turn_left(bunny: &mut SharedBunnyType) {
|
||||
if bunny.borrow().is_going() {
|
||||
bunny.borrow_mut().turn(true);
|
||||
}
|
||||
}).register_fn("turn_right", |bunny: &mut SharedBunnyType| {
|
||||
}
|
||||
pub fn turn_right(bunny: &mut SharedBunnyType) {
|
||||
if bunny.borrow().is_going() {
|
||||
bunny.borrow_mut().turn(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
engine.load_package(exported_module!(bunny_api));
|
||||
```
|
||||
|
||||
### Push Constant Command Object into Custom Scope
|
||||
@ -132,7 +152,9 @@ engine
|
||||
let bunny: SharedBunnyType = Rc::new(RefCell::(EnergizerBunny::new()));
|
||||
|
||||
let mut scope = Scope::new();
|
||||
scope.push_constant("BUNNY", bunny.clone());
|
||||
|
||||
// Add the command object into a custom Scope.
|
||||
scope.push_constant("Bunny", bunny.clone());
|
||||
|
||||
engine.consume_with_scope(&mut scope, script)?;
|
||||
```
|
||||
@ -140,11 +162,11 @@ engine.consume_with_scope(&mut scope, script)?;
|
||||
### Use the Command API in Script
|
||||
|
||||
```rust
|
||||
// Access the command object via constant variable 'BUNNY'.
|
||||
// Access the command object via constant variable 'Bunny'.
|
||||
|
||||
if !BUNNY.power { BUNNY.power = true; }
|
||||
if !Bunny.power { Bunny.power = true; }
|
||||
|
||||
if BUNNY.speed > 50 { BUNNY.speed = 50; }
|
||||
if Bunny.speed > 50 { Bunny.speed = 50; }
|
||||
|
||||
BUNNY.turn_left();
|
||||
Bunny.turn_left();
|
||||
```
|
||||
|
@ -11,11 +11,11 @@ individual functions instead of a full-blown [plugin module].
|
||||
Macros
|
||||
------
|
||||
|
||||
| Macro | Apply to | Description |
|
||||
| ----------------------- | --------------------------------------------------------------- | ------------------------------------------------------------- |
|
||||
| `#[export_fn]` | rust function defined in a Rust module | exports the function |
|
||||
| `register_exported_fn!` | [`Engine`] instance, register name string, use path to function | registers the function into an [`Engine`] under specific name |
|
||||
| `set_exported_fn!` | [`Module`] instance, register name string, use path to function | registers the function into an [`Module`] under specific name |
|
||||
| Macro | Signature | Description |
|
||||
| ----------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------- |
|
||||
| `#[export_fn]` | apply to rust function defined in a Rust module | exports the function |
|
||||
| `register_exported_fn!` | `register_exported_fn!(&mut `_engine_`, "`_name_`", `_function_`)` | registers the function into an [`Engine`] under a specific name |
|
||||
| `set_exported_fn!` | `set_exported_fn!(&mut `_module_`, "`_name_`", `_function_`)` | registers the function into a [`Module`] under a specific name |
|
||||
|
||||
|
||||
`#[export_fn]` and `register_exported_fn!`
|
||||
|
@ -29,9 +29,9 @@ use rhai::plugins::*; // a "prelude" import for macros
|
||||
|
||||
#[export_module]
|
||||
mod my_module {
|
||||
// This constant will be registered as the constant variable 'SOME_NUMBER'.
|
||||
// This constant will be registered as the constant variable 'MY_NUMBER'.
|
||||
// Ignored when loaded as a package.
|
||||
pub const SOME_NUMBER: i64 = 42;
|
||||
pub const MY_NUMBER: i64 = 42;
|
||||
|
||||
// This function will be registered as 'greet'.
|
||||
pub fn greet(name: &str) -> String {
|
||||
@ -261,11 +261,11 @@ Inner attributes can be applied to the inner items of a module to tweak the expo
|
||||
Parameters should be set on inner attributes to specify the desired behavior.
|
||||
|
||||
| Attribute Parameter | Use with | Apply to | Description |
|
||||
| ------------------- | --------------------------- | -------------------------------------------------------- | ------------------------------------------------------ |
|
||||
| ------------------- | --------------------------- | ----------------------------------------------------- | ------------------------------------------------------ |
|
||||
| `skip` | `#[rhai_fn]`, `#[rhai_mod]` | function or sub-module | do not export this function/sub-module |
|
||||
| `name = "..."` | `#[rhai_fn]`, `#[rhai_mod]` | function or sub-module | registers function/sub-module under the specified name |
|
||||
| `get = "..."` | `#[rhai_fn]` | function with `&mut` first parameter | registers a getter for the named property |
|
||||
| `set = "..."` | `#[rhai_fn]` | function with `&mut` first parameter | registers a setter for the named property |
|
||||
| `index_get` | `#[rhai_fn]` | function with `&mut` first parameter | registers an index getter |
|
||||
| `index_set` | `#[rhai_fn]` | function with `&mut` first parameter | registers an index setter |
|
||||
| `return_raw` | `#[rhai_fn]` | function returning `Result<Dynamic, Box<EvalAltResult>>` | marks this as a [fallible function] |
|
||||
| `get = "..."` | `#[rhai_fn]` | `pub fn (&mut Type) -> Value` | registers a getter for the named property |
|
||||
| `set = "..."` | `#[rhai_fn]` | `pub fn (&mut Type, Value)` | registers a setter for the named property |
|
||||
| `index_get` | `#[rhai_fn]` | `pub fn (&mut Type, INT) -> Value` | registers an index getter |
|
||||
| `index_set` | `#[rhai_fn]` | `pub fn (&mut Type, INT, Value)` | registers an index setter |
|
||||
| `return_raw` | `#[rhai_fn]` | `pub fn (...) -> Result<Dynamic, Box<EvalAltResult>>` | marks this as a [fallible function] |
|
||||
|
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;
|
||||
|
@ -85,10 +85,10 @@ 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 `()` |
|
||||
| [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),
|
||||
|
@ -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
|
||||
-------------------
|
||||
|
||||
|
@ -11,12 +11,16 @@ Furthermore, an [`Engine`] created non-`mut` cannot mutate any state, including
|
||||
It is highly recommended that [`Engine`]'s be created immutable as much as possible.
|
||||
|
||||
```rust
|
||||
// Use the fluent API to configure an 'Engine' and then keep an immutable instance.
|
||||
let engine = Engine::new()
|
||||
.register_get("field", get_field)
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Use the fluent API to configure an 'Engine'
|
||||
engine.register_get("field", get_field)
|
||||
.register_set("field", set_field)
|
||||
.register_fn("do_work", action);
|
||||
|
||||
// Then turn it into an immutable instance
|
||||
let engine = engine;
|
||||
|
||||
// 'engine' is immutable...
|
||||
```
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user