Revise docs.

This commit is contained in:
Stephen Chung 2020-09-28 22:14:19 +08:00
parent 2123b0a279
commit 64c421b3d7
47 changed files with 686 additions and 377 deletions

View File

@ -23,22 +23,23 @@ Standard features
----------------- -----------------
* Easy-to-use language similar to JavaScript+Rust with dynamic typing. * 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). * 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). * 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. * 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). * 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). * Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature).
* [Function overloading](https://schungx.github.io/rhai/language/overload.html). * Scripts are [optimized](https://schungx.github.io/rhai/engine/optimize.html) (useful for template-based machine-generated scripts) for repeated evaluations.
* [Operator overloading](https://schungx.github.io/rhai/rust/operators.html). * Easy custom API development via [plugins](https://schungx.github.io/rhai/plugins/index.html) system powered by procedural macros.
* 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). * [Function overloading](https://schungx.github.io/rhai/language/overload.html) and [operator overloading](https://schungx.github.io/rhai/rust/operators.html).
* Some support for [object-oriented programming (OOP)](https://schungx.github.io/rhai/language/oop.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). * 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). * 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). * 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 Protection against attacks
-------------------------- --------------------------
@ -69,13 +70,11 @@ Scripts can be evaluated directly from the editor.
License 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 * [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) * [MIT license](https://github.com/jonathandturner/rhai/blob/master/LICENSE-MIT.txt)
at your choice.
Unless explicitly stated otherwise, any contribution intentionally submitted Unless explicitly stated otherwise, any contribution intentionally submitted
for inclusion in this crate, as defined in the Apache-2.0 license, shall 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. be dual-licensed as above, without any additional terms or conditions.

View File

@ -4,6 +4,9 @@ Rhai Release Notes
Version 0.19.0 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 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_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. * `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. * `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 `EvalAltResult::ErrorInModule` variant capturing errors when loading a module from a script file.
New features New features
@ -28,11 +31,11 @@ New features
* Plugins support via procedural macros. * Plugins support via procedural macros.
* Scripted functions are allowed in packages. * 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. * `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`. * 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 Version 0.18.3

View File

@ -81,15 +81,15 @@ The Rhai Scripting Language
2. [Overloading](language/overload.md) 2. [Overloading](language/overload.md)
3. [Namespaces](language/fn-namespaces.md) 3. [Namespaces](language/fn-namespaces.md)
4. [Function Pointers](language/fn-ptr.md) 4. [Function Pointers](language/fn-ptr.md)
5. [Anonymous Functions](language/fn-anon.md) 5. [Currying](language/fn-curry.md)
6. [Currying](language/fn-curry.md) 6. [Anonymous Functions](language/fn-anon.md)
7. [Closures](language/fn-closure.md) 7. [Closures](language/fn-closure.md)
16. [Print and Debug](language/print-debug.md) 16. [Print and Debug](language/print-debug.md)
17. [Modules](language/modules/index.md) 17. [Modules](language/modules/index.md)
1. [Export Variables, Functions and Sub-Modules](language/modules/export.md) 1. [Export Variables, Functions and Sub-Modules](language/modules/export.md)
2. [Import Modules](language/modules/import.md) 2. [Import Modules](language/modules/import.md)
3. [Create from Rust](rust/modules/create.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) 5. [Module Resolvers](rust/modules/resolvers.md)
1. [Custom Implementation](rust/modules/imp-resolver.md) 1. [Custom Implementation](rust/modules/imp-resolver.md)
18. [Eval Statement](language/eval.md) 18. [Eval Statement](language/eval.md)

View File

@ -37,7 +37,7 @@ Dynamic
* Dynamic dispatch via [function pointers] with additional support for [currying]. * 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]. * Some support for [object-oriented programming (OOP)][OOP].

View File

@ -4,7 +4,7 @@ Keywords List
{{#include ../links.md}} {{#include ../links.md}}
| Keyword | Description | Inactive under | Overloadable | | Keyword | Description | Inactive under | Overloadable |
| :-------------------: | ---------------------------------------- | :-------------: | :----------: | | :-------------------: | ------------------------------------------- | :-------------: | :----------: |
| `true` | boolean true literal | | no | | `true` | boolean true literal | | no |
| `false` | boolean false literal | | no | | `false` | boolean false literal | | no |
| `let` | variable declaration | | no | | `let` | variable declaration | | no |
@ -15,9 +15,9 @@ Keywords List
| `while` | while loop | | no | | `while` | while loop | | no |
| `loop` | infinite loop | | no | | `loop` | infinite loop | | no |
| `for` | for 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 | | `continue` | continue a loop at the next iteration | | no |
| `break` | loop breaking | | no | | `break` | break out of loop iteration | | no |
| `return` | return value | | no | | `return` | return value | | no |
| `throw` | throw exception | | no | | `throw` | throw exception | | no |
| `import` | import module | [`no_module`] | no | | `import` | import module | [`no_module`] | no |
@ -25,7 +25,7 @@ Keywords List
| `as` | alias for variable export | [`no_module`] | no | | `as` | alias for variable export | [`no_module`] | no |
| `private` | mark function private | [`no_function`] | no | | `private` | mark function private | [`no_function`] | no |
| `fn` (lower-case `f`) | function definition | [`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 | | `call` | call a [function pointer] | | no |
| `curry` | curry a [function pointer] | | no | | `curry` | curry a [function pointer] | | no |
| `this` | reference to base object for method call | [`no_function`] | no | | `this` | reference to base object for method call | [`no_function`] | no |

View File

@ -4,11 +4,11 @@ Literals Syntax
{{#include ../links.md}} {{#include ../links.md}}
| Type | Literal syntax | | 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` | | `FLOAT` | `42.0`, `-123.456`, `0.0` |
| [String] | `"... \x?? \u???? \U???????? ..."` | | [String] | `"... \x?? \u???? \U???????? ..."` |
| Character | `"... \x?? \u???? \U???????? ..."` | | Character | single: `'?'`<br/>ASCII hex: `'\x??'`<br/>Unicode: `'\u????'`, `'\U????????'` |
| [`Array`] | `[ ???, ???, ??? ]` | | [`Array`] | `[ ???, ???, ??? ]` |
| [Object map] | `#{ a: ???, b: ???, c: ???, "def": ??? }` | | [Object map] | `#{ a: ???, b: ???, c: ???, "def": ??? }` |
| Boolean true | `true` | | Boolean true | `true` |

View File

@ -40,13 +40,13 @@ Symbols
| ------------ | ------------------------ | | ------------ | ------------------------ |
| `:` | property value separator | | `:` | property value separator |
| `::` | module path separator | | `::` | module path separator |
| `#` | _Reserved_ | | `#` | _reserved_ |
| `=>` | _Reserved_ | | `=>` | _reserved_ |
| `->` | _Reserved_ | | `->` | _reserved_ |
| `<-` | _Reserved_ | | `<-` | _reserved_ |
| `===` | _Reserved_ | | `===` | _reserved_ |
| `!==` | _Reserved_ | | `!==` | _reserved_ |
| `:=` | _Reserved_ | | `:=` | _reserved_ |
| `::<` .. `>` | _Reserved_ | | `::<` .. `>` | _reserved_ |
| `@` | _Reserved_ | | `@` | _reserved_ |
| `(*` .. `*)` | _Reserved_ | | `(*` .. `*)` | _reserved_ |

View File

@ -4,7 +4,7 @@ Extend Rhai with Custom Syntax
{{#include ../links.md}} {{#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_. with custom-defined _syntax_.
But before going off to define the next weird statement type, heed this warning: 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 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. * 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$). 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. 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 ```rust
let var_name = inputs[0].get_variable_name().unwrap().to_string(); let var_name = inputs[0].get_variable_name().unwrap().to_string();

View File

@ -1,4 +1,3 @@
Raw `Engine` Raw `Engine`
=========== ===========

View File

@ -31,19 +31,20 @@ let mut scope = Scope::new();
scope scope
.push("y", 42_i64) .push("y", 42_i64)
.push("z", 999_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 .set_value("s", "hello, world!".to_string()); //'set_value' adds a variable when one doesn't exist
// remember to use 'String', not '&str' // remember to use 'String', not '&str'
// First invocation // First invocation
engine.eval_with_scope::<()>(&mut scope, r" 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; y = 1;
")?; ")?;
// Second invocation using the same state // Second invocation using the same state
let result = engine.eval_with_scope::<i64>(&mut scope, "x")?; 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' // 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); assert_eq!(scope.get_value::<i64>("y").expect("variable y should exist"), 1);

View File

@ -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: The following methods (mostly defined in the [`BasicArrayPackage`][packages] but excluded if using a [raw `Engine`]) operate on arrays:
| Function | Parameter(s) | Description | | Function | Parameter(s) | Description |
| ------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | ------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `push` | element to insert | inserts an element at the end | | `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 | | `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 | 1) array<br/>2) element to insert (not another array) | inserts an element at the end |
| `+` operator | first array, second array | concatenates the first array with the second | | `+=` operator | 1) array<br/>2) array to append | concatenates the second array to the end of the first |
| `insert` | element to insert, position<br/>(beginning if <= 0, end if >= length) | inserts an element at a certain index | | `+` 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) | | `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
| `shift` | _none_ | removes the first 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 | | `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 | | `reverse` | _none_ | reverses the array |
| `len` method and property | _none_ | returns the number of elements | | `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 | | `clear` | _none_ | empties the array |
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) | | `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |

View File

@ -16,8 +16,45 @@ x = 123; // <- syntax error: cannot assign to constant
``` ```
Unlike variables which need not have initial values (default to [`()`]), 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 ```rust
const x = 40 + 2; // <- syntax error: cannot assign expression to constant 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
")?;
```

View File

@ -3,6 +3,10 @@ Value Conversions
{{#include ../links.md}} {{#include ../links.md}}
Convert Between Integer and Floating-Point
-----------------------------------------
The `to_float` function converts a supported number to `FLOAT` (defaults to `f64`). 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`]). 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" 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";
```

View File

@ -83,10 +83,14 @@ match item.type_name() { // 'type_name' returns the name
"i64" => ... "i64" => ...
"alloc::string::String" => ... "alloc::string::String" => ...
"bool" => ... "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 Conversion Traits
---------------- ----------------
@ -100,4 +104,5 @@ The following conversion traits are implemented for `Dynamic`:
* `From<String>` * `From<String>`
* `From<char>` * `From<char>`
* `From<Vec<T>>` (into an [array]) * `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`])

View File

@ -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 reference counting, and may be read or modified even after the variables that hold them
go out of scope and no longer exist. 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. 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 Examples
-------- --------
```rust ```rust
let x = 1; // a normal variable let x = 1; // a normal variable
x.is_shared() == false;
let f = |y| x + y; // variable 'x' is auto-curried (captured) into 'f' let f = |y| x + y; // variable 'x' is auto-curried (captured) into 'f'
x.is_shared() == true; // 'x' is now a shared value! x.is_shared() == true; // 'x' is now a shared value!
f.call(2) == 3; // 1 + 2 == 3
x = 40; // changing 'x'... x = 40; // changing 'x'...
f.call(2) == 42; // the value of 'x' is 40 because 'x' is shared 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 ```rust
let x = 20; 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 let f = |a| this += x + a; // 'x' is captured in this closure
x.is_shared() == true; // now 'x' is shared x.is_shared() == true; // now 'x' is shared
@ -152,6 +136,26 @@ x.call(f, 2);
TL;DR 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? ### Q: Why are closures implemented as automatic currying?
In concept, a closure _closes_ over captured variables from the outer scope - that's why In concept, a closure _closes_ over captured variables from the outer scope - that's why

View File

@ -43,7 +43,9 @@ This aspect is very similar to JavaScript before ES6 modules.
// Compile a script into AST // Compile a script into AST
let ast1 = engine.compile( let ast1 = engine.compile(
r#" r#"
fn get_message() { "Hello!" } // greeting message fn get_message() {
"Hello!" // greeting message
}
fn say_hello() { fn say_hello() {
print(get_message()); // prints message print(get_message()); // prints message

View File

@ -61,8 +61,8 @@ hello.call(0); // error: function not found - 'hello_world (i64)'
Global Namespace Only Global Namespace Only
-------------------- --------------------
Because of their dynamic nature, function pointers cannot refer to functions in a _module_ [namespace][function namespace] Because of their dynamic nature, function pointers cannot refer to functions in [`import`]-ed [modules].
(i.e. functions in [`import`]-ed modules). They can only refer to functions within the global [namespace][function namespace]. They can only refer to functions within the global [namespace][function namespace].
See [function namespaces] for more details. See [function namespaces] for more details.
```rust ```rust

View File

@ -44,10 +44,13 @@ Representation of Numbers
------------------------ ------------------------
JSON numbers are all floating-point while Rhai supports integers (`INT`) and floating-point (`FLOAT`) if 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 the [`no_float`] feature is not used.
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 Most common generators of JSON data distinguish between integer and floating-point values by always
with Rhai [object maps]. 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 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'. // JSON with sub-object 'b'.
let json = r#"{"a":1, "b":{"x":true, "y":false}}"#; 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)?; let map = engine.parse_json(&new_json, false)?;
map.len() == 2; // 'map' contains two properties: 'a' and 'b' map.len() == 2; // 'map' contains two properties: 'a' and 'b'

View File

@ -48,9 +48,9 @@ an equivalent method coded in script, where the object is accessed via the `this
The following table illustrates the differences: The following table illustrates the differences:
| Function type | Parameters | Object reference | Function signature | | 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) {}` | | Native Rust | _N_ + 1 | first `&mut T` parameter | `Fn(obj: &mut T, x: U, y: V)` |
| Rhai script | _n_ | `this` | `fn method(x, y) {}` | | Rhai script | _N_ | `this` (of type `&mut T`) | `Fn(x: U, y: V)` |
`&mut` is Efficient, Except for `ImmutableString` `&mut` is Efficient, Except for `ImmutableString`

View File

@ -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'
```

View File

@ -3,35 +3,16 @@ Export Variables, Functions and Sub-Modules in Module
{{#include ../../links.md}} {{#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: Export Global Variables
----------------------
* 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
----------------
The `export` statement, which can only be at global level, exposes selected variables as members of a module. 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, Variables not exported are _private_ and hidden. They are merely used to initialize the module,
but cannot be accessed from outside. 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 ```rust
// This is a module script. // 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. All functions are automatically exported, _unless_ it is explicitly opt-out with the [`private`] prefix.

View File

@ -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 a normal method in method-call syntax. This is a _short-hand_ to avoid the more verbose syntax
of using the `call` function keyword. 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 what happens next depends on whether the target function is a native Rust function or
a script-defined function. 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 * 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.
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.
```rust ```rust
let obj = #{ let obj = #{
data: 40, 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; obj.data == 42;

View File

@ -58,13 +58,13 @@ The following methods (defined in the [`BasicMapPackage`][packages] but excluded
operate on object maps: operate on object maps:
| Function | Parameter(s) | Description | | Function | Parameter(s) | Description |
| ---------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | ---------------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `has` | property name | does the object map contain a property of a particular name? | | `has` | property name | does the object map contain a property of a particular name? |
| `len` | _none_ | returns the number of properties | | `len` | _none_ | returns the number of properties |
| `clear` | _none_ | empties the object map | | `clear` | _none_ | empties the object map |
| `remove` | property name | removes a certain property and returns it ([`()`] if the property does not exist) | | `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, `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 | | `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`] | | `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`] | | `values` | _none_ | returns an [array] of all the property values (in random order), not available under [`no_index`] |

View File

@ -7,18 +7,18 @@ The following standard methods (mostly defined in the [`MoreStringPackage`][pack
using a [raw `Engine`]) operate on [strings]: using a [raw `Engine`]) operate on [strings]:
| Function | Parameter(s) | Description | | Function | Parameter(s) | Description |
| ------------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | | ------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `len` method and property | _none_ | returns the number of characters (not number of bytes) in the string | | `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 | | `+=` operator, `append` | character/string to append | Adds a character or a string to the end of another string |
| `clear` | _none_ | empties the string | | `clear` | _none_ | empties the string |
| `truncate` | target length | cuts off the string at exactly a specified number of characters | | `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 | | `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 | | `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` | start index, length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) | | `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`] | | `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) | | `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` | target character/sub-string, replacement character/string | replaces a sub-string with another | | `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 | | `trim` | _none_ | trims the string of whitespace at the beginning and end |
Examples Examples

View File

@ -44,16 +44,16 @@ Hex sequences map to ASCII characters, while '`\u`' maps to 16-bit common Unicod
Standard escape sequences: Standard escape sequences:
| Escape sequence | Meaning | | Escape sequence | Meaning |
| --------------- | ------------------------------ | | --------------- | -------------------------------- |
| `\\` | back-slash `\` | | `\\` | back-slash `\` |
| `\t` | tab | | `\t` | tab |
| `\r` | carriage-return `CR` | | `\r` | carriage-return `CR` |
| `\n` | line-feed `LF` | | `\n` | line-feed `LF` |
| `\"` | double-quote `"` in strings | | `\"` | double-quote `"` |
| `\'` | single-quote `'` in characters | | `\'` | single-quote `'` |
| `\x`_xx_ | Unicode in 2-digit hex | | `\x`_xx_ | ASCII character in 2-digit hex |
| `\u`_xxxx_ | Unicode in 4-digit hex | | `\u`_xxxx_ | Unicode character in 4-digit hex |
| `\U`_xxxxxxxx_ | Unicode in 8-digit hex | | `\U`_xxxxxxxx_ | Unicode character in 8-digit hex |
Differences from Rust Strings Differences from Rust Strings

View File

@ -24,3 +24,24 @@ if type_of(x) == "string" {
do_something_with_string(x); 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");
```

View File

@ -49,6 +49,8 @@
[`Dynamic`]: {{rootUrl}}/language/dynamic.md [`Dynamic`]: {{rootUrl}}/language/dynamic.md
[`to_int`]: {{rootUrl}}/language/convert.md [`to_int`]: {{rootUrl}}/language/convert.md
[`to_float`]: {{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 type]: {{rootUrl}}/rust/custom.md
[custom types]: {{rootUrl}}/rust/custom.md [custom types]: {{rootUrl}}/rust/custom.md

View File

@ -51,6 +51,12 @@ let config: Rc<RefCell<Config>> = Rc::new(RefCell::new(Default::default()));
### Register Config API ### 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 ```rust
// Notice 'move' is used to move the shared configuration object into the closure. // Notice 'move' is used to move the shared configuration object into the closure.
let cfg = config.clone(); let cfg = config.clone();

View File

@ -64,6 +64,12 @@ let bunny: Rc<RefCell<EnergizerBunny>> = Rc::new(RefCell::(EnergizerBunny::new()
### Register Control API ### 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 ```rust
// Notice 'move' is used to move the shared API object into the closure. // Notice 'move' is used to move the shared API object into the closure.
let b = bunny.clone(); let b = bunny.clone();

View File

@ -58,7 +58,7 @@ impl EnergizerBunny {
pub fn new () -> Self { ... } pub fn new () -> Self { ... }
pub fn go (&mut self) { ... } pub fn go (&mut self) { ... }
pub fn stop (&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 get_speed (&self) -> i64 { ... }
pub fn set_speed (&mut self, speed: i64) { ... } pub fn set_speed (&mut self, speed: i64) { ... }
pub fn turn (&mut self, left_turn: bool) { ... } pub fn turn (&mut self, left_turn: bool) { ... }
@ -77,13 +77,23 @@ let SharedBunnyType = Rc<RefCell<EnergizerBunny>>;
engine.register_type_with_name::<SharedBunnyType>("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 ```rust
engine use rhai::plugins::*;
.register_get_set("power",
|bunny: &mut SharedBunnyType| bunny.borrow().is_going(), #[export_module]
|bunny: &mut SharedBunnyType, on: bool| { 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 on {
if bunny.borrow().is_going() { if bunny.borrow().is_going() {
println!("Still 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() { if bunny.borrow().is_going() {
bunny.borrow().get_speed() bunny.borrow().get_speed()
} else { } else {
0 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 { if speed <= 0 {
Err("Speed must be positive!".into()) Err("Speed must be positive!".into())
} else if speed > 100 { } else if speed > MAX_SPEED {
Err("Bunny will be going too fast!".into()) Err("Bunny will be going too fast!".into())
} else if !bunny.borrow().is_going() { } else if !bunny.borrow().is_going() {
Err("Bunny is not yet going!".into()) Err("Bunny is not yet going!".into())
@ -115,15 +130,20 @@ engine
b.borrow_mut().set_speed(speed); b.borrow_mut().set_speed(speed);
Ok(().into()) Ok(().into())
} }
}).register_fn("turn_left", |bunny: &mut SharedBunnyType| { }
pub fn turn_left(bunny: &mut SharedBunnyType) {
if bunny.borrow().is_going() { if bunny.borrow().is_going() {
bunny.borrow_mut().turn(true); bunny.borrow_mut().turn(true);
} }
}).register_fn("turn_right", |bunny: &mut SharedBunnyType| { }
pub fn turn_right(bunny: &mut SharedBunnyType) {
if bunny.borrow().is_going() { if bunny.borrow().is_going() {
bunny.borrow_mut().turn(false); bunny.borrow_mut().turn(false);
} }
}); }
}
engine.load_package(exported_module!(bunny_api));
``` ```
### Push Constant Command Object into Custom Scope ### Push Constant Command Object into Custom Scope
@ -132,7 +152,9 @@ engine
let bunny: SharedBunnyType = Rc::new(RefCell::(EnergizerBunny::new())); let bunny: SharedBunnyType = Rc::new(RefCell::(EnergizerBunny::new()));
let mut scope = Scope::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)?; engine.consume_with_scope(&mut scope, script)?;
``` ```
@ -140,11 +162,11 @@ engine.consume_with_scope(&mut scope, script)?;
### Use the Command API in Script ### Use the Command API in Script
```rust ```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();
``` ```

View File

@ -11,11 +11,11 @@ individual functions instead of a full-blown [plugin module].
Macros Macros
------ ------
| Macro | Apply to | Description | | Macro | Signature | Description |
| ----------------------- | --------------------------------------------------------------- | ------------------------------------------------------------- | | ----------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------- |
| `#[export_fn]` | rust function defined in a Rust module | exports the function | | `#[export_fn]` | apply to 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 | | `register_exported_fn!` | `register_exported_fn!(&mut `_engine_`, "`_name_`", `_function_`)` | registers the function into an [`Engine`] under a specific name |
| `set_exported_fn!` | [`Module`] instance, register name string, use path to function | registers the function into an [`Module`] under 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!` `#[export_fn]` and `register_exported_fn!`

View File

@ -29,9 +29,9 @@ use rhai::plugins::*; // a "prelude" import for macros
#[export_module] #[export_module]
mod my_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. // 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'. // This function will be registered as 'greet'.
pub fn greet(name: &str) -> String { 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. Parameters should be set on inner attributes to specify the desired behavior.
| Attribute Parameter | Use with | Apply to | Description | | Attribute Parameter | Use with | Apply to | Description |
| ------------------- | --------------------------- | -------------------------------------------------------- | ------------------------------------------------------ | | ------------------- | --------------------------- | ----------------------------------------------------- | ------------------------------------------------------ |
| `skip` | `#[rhai_fn]`, `#[rhai_mod]` | function or sub-module | do not export this function/sub-module | | `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 | | `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 | | `get = "..."` | `#[rhai_fn]` | `pub fn (&mut Type) -> Value` | registers a getter for the named property |
| `set = "..."` | `#[rhai_fn]` | function with `&mut` first parameter | registers a setter for the named property | | `set = "..."` | `#[rhai_fn]` | `pub fn (&mut Type, Value)` | registers a setter for the named property |
| `index_get` | `#[rhai_fn]` | function with `&mut` first parameter | registers an index getter | | `index_get` | `#[rhai_fn]` | `pub fn (&mut Type, INT) -> Value` | registers an index getter |
| `index_set` | `#[rhai_fn]` | function with `&mut` first parameter | registers an index setter | | `index_set` | `#[rhai_fn]` | `pub fn (&mut Type, INT, Value)` | registers an index setter |
| `return_raw` | `#[rhai_fn]` | function returning `Result<Dynamic, Box<EvalAltResult>>` | marks this as a [fallible function] | | `return_raw` | `#[rhai_fn]` | `pub fn (...) -> Result<Dynamic, Box<EvalAltResult>>` | marks this as a [fallible function] |

View File

@ -53,7 +53,7 @@ impl TestStruct {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine engine
.register_type::<TestStruct>() .register_type::<TestStruct>()
.register_get_set("xyz", TestStruct::get_field, TestStruct::set_field) .register_get_set("xyz", TestStruct::get_field, TestStruct::set_field)
.register_fn("new_ts", TestStruct::new); .register_fn("new_ts", TestStruct::new);

View 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'
```

View File

@ -22,19 +22,38 @@ For the complete `Module` API, refer to the [documentation](https://docs.rs/rhai
Make the `Module` Available to the `Engine` 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 `Engine::load_package` supports loading a [module] as a [package].
loaded via `import` statements.
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 The easiest way is to use, for example, the [`StaticModuleResolver`][module resolver] to hold such
a custom module. a custom module.
```rust ```rust
use rhai::{Engine, Scope, Module, i64}; use rhai::{Engine, Scope, Module};
use rhai::module_resolvers::StaticModuleResolver; use rhai::module_resolvers::StaticModuleResolver;
let mut engine = Engine::new();
let mut scope = Scope::new();
let mut module = Module::new(); // new module let mut module = Module::new(); // new module
module.set_var("answer", 41_i64); // variable 'answer' under 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 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); resolver.insert("question", module);
// Set the module resolver into the 'Engine' // Set the module resolver into the 'Engine'
let mut engine = Engine::new();
engine.set_module_resolver(Some(resolver)); engine.set_module_resolver(Some(resolver));
// Use module-qualified variables // 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 // 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;
``` ```

View File

@ -11,8 +11,14 @@ A module resolver must implement the trait [`rhai::ModuleResolver`][traits],
which contains only one function: `resolve`. which contains only one function: `resolve`.
When Rhai prepares to load a module, `ModuleResolver::resolve` is called with the name 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 of the _module path_ (i.e. the path specified in the [`import`] statement).
return a [`Module`]; if the module cannot be load, return `EvalAltResult::ErrorModuleNotFound`.
* 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 Example
------- -------
@ -35,9 +41,12 @@ impl ModuleResolver for MyModuleResolver {
// Check module path. // Check module path.
if is_valid_module_path(path) { if is_valid_module_path(path) {
// Load the custom module. // Load the custom module.
let module: Module = load_secret_module(path); load_secret_module(path).map_err(|err|
Ok(module) // Return EvalAltResult::ErrorInModule upon loading error
EvalAltResult::ErrorInModule(err.to_string(), pos).into()
)
} else { } else {
// Return EvalAltResult::ErrorModuleNotFound if the path is invalid
Err(EvalAltResult::ErrorModuleNotFound(path.into(), pos).into()) Err(EvalAltResult::ErrorModuleNotFound(path.into(), pos).into())
} }
} }
@ -50,7 +59,7 @@ engine.set_module_resolver(Some(MyModuleResolver {}));
engine.consume(r#" engine.consume(r#"
import "hello" as foo; // this 'import' statement will call import "hello" as foo; // this 'import' statement will call
// 'MyModuleResolver::resolve' with "hello" as path // 'MyModuleResolver::resolve' with "hello" as `path`
foo:bar(); foo:bar();
"#)?; "#)?;
``` ```

View File

@ -12,16 +12,120 @@ Built-In Module Resolvers
------------------------ ------------------------
There are a number of standard resolvers built into Rhai, the default being the `FileModuleResolver` There are a number of standard resolvers built into Rhai, the default being the `FileModuleResolver`
which simply loads a script file based on the path (with `.rhai` extension attached) and execute it to form a module. which simply loads a script file based on the path (with `.rhai` extension attached)
and execute it to form a module.
Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace. Built-in module resolvers are grouped under the `rhai::module_resolvers` module namespace.
| Module Resolver | Description | Namespace |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------: | `FileModuleResolver` (default)
| `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 | The _default_ module resolution service, not available for [`no_std`] or [WASM] builds.
| `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 | 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` 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`: An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`:
```rust ```rust
// Use the 'StaticModuleResolver' use rhai::module_resolvers::StaticModuleResolver;
let resolver = rhai::module_resolvers::StaticModuleResolver::new();
// Create a module resolver
let resolver = StaticModuleResolver::new();
// Register functions into 'resolver'...
// Use the module resolver
engine.set_module_resolver(Some(resolver)); engine.set_module_resolver(Some(resolver));
// Effectively disable 'import' statements by setting module resolver to 'None' // Effectively disable 'import' statements by setting module resolver to 'None'

View File

@ -34,9 +34,11 @@ Macro Parameters
```rust ```rust
// Import necessary types and traits. // Import necessary types and traits.
use rhai::{ use rhai::{
def_package, def_package, // 'def_package!' macro
packages::Package, packages::Package, // 'Package' trait
packages::{ArithmeticPackage, BasicArrayPackage, BasicMapPackage, LogicPackage} packages::{ // pre-defined packages
ArithmeticPackage, BasicArrayPackage, BasicMapPackage, LogicPackage
}
}; };
// Define the package 'MyPackage'. // Define the package 'MyPackage'.

View File

@ -9,10 +9,12 @@ Packages reside under `rhai::packages::*` and the trait `rhai::packages::Package
packages to be used. packages to be used.
Packages typically contain Rust functions that are callable within a Rhai script. 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`], Once a package is created (e.g. via `Package::new`), it can be _shared_ (via `Package::get`)
even across threads (under [`sync`]). Therefore, a package only has to be created _once_. among multiple instances of [`Engine`], even across threads (under [`sync`]).
Therefore, a package only has to be created _once_.
```rust ```rust
use rhai::Engine; use rhai::Engine;

View File

@ -85,10 +85,10 @@ Extract Arguments
To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following: To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following:
| Argument type | Access (`n` = argument position) | Result | | Argument type | Access (`n` = argument position) | Result |
| ------------------------------ | ------------------------------------- | --------------------------------------------------------- | | ------------------------------ | ------------------------------------- | ----------------------------------------------------- |
| [Primary type][standard types] | `args[n].clone().cast::<T>()` | copy of value | | [Primary type][standard types] | `args[n].clone().cast::<T>()` | copy of value |
| Custom type | `args[n].read_lock::<T>().unwrap()` | immutable reference to 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] (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 | | `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), When there is a mutable reference to the `this` object (i.e. the first argument),

View File

@ -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 Lighter Alternative
------------------- -------------------

View File

@ -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. It is highly recommended that [`Engine`]'s be created immutable as much as possible.
```rust ```rust
// Use the fluent API to configure an 'Engine' and then keep an immutable instance. let mut engine = Engine::new();
let engine = Engine::new()
.register_get("field", get_field) // Use the fluent API to configure an 'Engine'
engine.register_get("field", get_field)
.register_set("field", set_field) .register_set("field", set_field)
.register_fn("do_work", action); .register_fn("do_work", action);
// Then turn it into an immutable instance
let engine = engine;
// 'engine' is immutable... // 'engine' is immutable...
``` ```