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.
* 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.

View File

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

View File

@ -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)

View File

@ -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].

View File

@ -3,36 +3,36 @@ Keywords List
{{#include ../links.md}}
| Keyword | Description | Inactive under | Overloadable |
| :-------------------: | ---------------------------------------- | :-------------: | :----------: |
| `true` | boolean true literal | | no |
| `false` | boolean false literal | | no |
| `let` | variable declaration | | no |
| `const` | constant declaration | | no |
| `is_shared` | is a value shared? | | no |
| `if` | if statement | | no |
| `else` | else block of if statement | | no |
| `while` | while loop | | no |
| `loop` | infinite loop | | no |
| `for` | for loop | | no |
| `in` | containment test, part of for loop | | no |
| `continue` | continue a loop at the next iteration | | no |
| `break` | loop breaking | | no |
| `return` | return value | | no |
| `throw` | throw exception | | no |
| `import` | import module | [`no_module`] | no |
| `export` | export variable | [`no_module`] | no |
| `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 |
| `call` | call a [function pointer] | | no |
| `curry` | curry a [function pointer] | | no |
| `this` | reference to base object for method call | [`no_function`] | no |
| `type_of` | get type name of value | | yes |
| `print` | print value | | yes |
| `debug` | print value in debug format | | yes |
| `eval` | evaluate script | | yes |
| Keyword | Description | Inactive under | Overloadable |
| :-------------------: | ------------------------------------------- | :-------------: | :----------: |
| `true` | boolean true literal | | no |
| `false` | boolean false literal | | no |
| `let` | variable declaration | | no |
| `const` | constant declaration | | no |
| `is_shared` | is a value shared? | | no |
| `if` | if statement | | no |
| `else` | else block of if statement | | no |
| `while` | while loop | | no |
| `loop` | infinite loop | | no |
| `for` | for loop | | no |
| `in` | 1) containment test<br/>2) part of for loop | | no |
| `continue` | continue a loop at the next iteration | | no |
| `break` | break out of loop iteration | | no |
| `return` | return value | | no |
| `throw` | throw exception | | no |
| `import` | import module | [`no_module`] | no |
| `export` | export variable | [`no_module`] | no |
| `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`) | 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 |
| `type_of` | get type name of value | | yes |
| `print` | print value | | yes |
| `debug` | print value in debug format | | yes |
| `eval` | evaluate script | | yes |
Reserved Keywords

View File

@ -3,14 +3,14 @@ Literals Syntax
{{#include ../links.md}}
| Type | Literal syntax |
| :--------------------------------: | :------------------------------------------------------------------------------: |
| `INT` | `42`, `-123`, `0`,<br/>`0x????..` (hex), `0b????..` (binary), `0o????..` (octal) |
| `FLOAT` | `42.0`, `-123.456`, `0.0` |
| [String] | `"... \x?? \u???? \U???????? ..."` |
| Character | `"... \x?? \u???? \U???????? ..."` |
| [`Array`] | `[ ???, ???, ??? ]` |
| [Object map] | `#{ a: ???, b: ???, c: ???, "def": ??? }` |
| Boolean true | `true` |
| Boolean false | `false` |
| `Nothing`/`null`/`nil`/`void`/Unit | `()` |
| Type | Literal syntax |
| :--------------------------------: | :-----------------------------------------------------------------------------------------: |
| `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 | single: `'?'`<br/>ASCII hex: `'\x??'`<br/>Unicode: `'\u????'`, `'\U????????'` |
| [`Array`] | `[ ???, ???, ??? ]` |
| [Object map] | `#{ a: ???, b: ???, c: ???, "def": ??? }` |
| Boolean true | `true` |
| Boolean false | `false` |
| `Nothing`/`null`/`nil`/`void`/Unit | `()` |

View File

@ -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_ |

View File

@ -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();

View File

@ -11,9 +11,9 @@ Creating them is accomplished via the `Func` trait which contains `create_from_s
(as well as its companion method `create_from_ast`):
```rust
use rhai::{Engine, Func}; // use 'Func' for 'create_from_script'
use rhai::{Engine, Func}; // use 'Func' for 'create_from_script'
let engine = Engine::new(); // create a new 'Engine' just for this
let engine = Engine::new(); // create a new 'Engine' just for this
let script = "fn calc(x, y) { x + y.len < 42 }";
@ -25,14 +25,14 @@ let script = "fn calc(x, y) { x + y.len < 42 }";
let func = Func::<(i64, String), bool>::create_from_script(
// ^^^^^^^^^^^^^ function parameter types in tuple
engine, // the 'Engine' is consumed into the closure
script, // the script, notice number of parameters must match
"calc" // the entry-point function name
engine, // the 'Engine' is consumed into the closure
script, // the script, notice number of parameters must match
"calc" // the entry-point function name
)?;
func(123, "hello".to_string())? == false; // call the closure
func(123, "hello".to_string())? == false; // call the closure
schedule_callback(func); // pass it as a callback to another function
schedule_callback(func); // pass it as a callback to another function
// Although there is nothing you can't do by manually writing out the closure yourself...
let engine = Engine::new();

View File

@ -8,13 +8,13 @@ Some optimizations can alter subtle semantics of the script.
For example:
```rust
if true { // condition always true
123.456; // eliminated
hello; // eliminated, EVEN THOUGH the variable doesn't exist!
foo(42) // promoted up-level
if true { // condition always true
123.456; // eliminated
hello; // eliminated, EVEN THOUGH the variable doesn't exist!
foo(42) // promoted up-level
}
foo(42) // <- the above optimizes to this
foo(42) // <- the above optimizes to this
```
If the original script were evaluated instead, it would have been an error - the variable `hello` does not exist,

View File

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

View File

@ -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'
// 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);

View File

@ -30,22 +30,22 @@ 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 |
| `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 |
| `clear` | _none_ | empties the array |
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |
| Function | Parameter(s) | Description |
| ------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `push` | element to insert | inserts an element at the end |
| `append` | 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 | 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` | 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) |
Use Custom Types With Arrays

View File

@ -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
")?;
```

View File

@ -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";
```

View File

@ -58,15 +58,15 @@ The `cast` method then converts the value into a specific, known type.
Alternatively, use the `try_cast` method which does not panic but returns `None` when the cast fails.
```rust
let list: Array = engine.eval("...")?; // return type is 'Array'
let item = list[0]; // an element in an 'Array' is 'Dynamic'
let list: Array = engine.eval("...")?; // return type is 'Array'
let item = list[0]; // an element in an 'Array' is 'Dynamic'
item.is::<i64>() == true; // 'is' returns whether a 'Dynamic' value is of a particular type
item.is::<i64>() == true; // 'is' returns whether a 'Dynamic' value is of a particular type
let value = item.cast::<i64>(); // if the element is 'i64', this succeeds; otherwise it panics
let value: i64 = item.cast(); // type can also be inferred
let value = item.cast::<i64>(); // if the element is 'i64', this succeeds; otherwise it panics
let value: i64 = item.cast(); // type can also be inferred
let value = item.try_cast::<i64>()?; // 'try_cast' does not panic when the cast fails, but returns 'None'
let value = item.try_cast::<i64>()?; // 'try_cast' does not panic when the cast fails, but returns 'None'
```
Type Name
@ -76,17 +76,21 @@ The `type_name` method gets the name of the actual type as a static string slice
which can be `match`-ed against.
```rust
let list: Array = engine.eval("...")?; // return type is 'Array'
let item = list[0]; // an element in an 'Array' is 'Dynamic'
let list: Array = engine.eval("...")?; // return type is 'Array'
let item = list[0]; // an element in an 'Array' is 'Dynamic'
match item.type_name() { // 'type_name' returns the name of the actual Rust type
match item.type_name() { // 'type_name' returns the name of the actual Rust type
"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`])

View File

@ -10,8 +10,8 @@ This scenario is especially common when simulating object-oriented programming (
// Define object
let obj = #{
data: 42,
increment: Fn("inc_obj"), // use function pointers to
decrement: Fn("dec_obj"), // refer to method functions
increment: Fn("inc_obj"), // use function pointers to
decrement: Fn("dec_obj"), // refer to method functions
print: Fn("print_obj")
};

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
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

View File

@ -43,10 +43,12 @@ 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
print(get_message()); // prints message
}
say_hello();

View File

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

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

View File

@ -47,10 +47,10 @@ 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) {}` |
| Function type | Parameters | Object reference | Function signature |
| :-----------: | :--------: | :-----------------------: | :---------------------------: |
| 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`

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}}
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.

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
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;

View File

@ -57,17 +57,17 @@ Built-in Functions
The following methods (defined in the [`BasicMapPackage`][packages] but excluded if using a [raw `Engine`])
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 |
| `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`] |
| 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 | 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`] |
Examples

View File

@ -6,20 +6,20 @@ Built-in String Functions
The following standard methods (mostly defined in the [`MoreStringPackage`][packages] but excluded if
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 |
| `+=` 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) |
| `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 |
| `trim` | _none_ | trims the string of whitespace at the beginning and end |
| Function | Parameter(s) | Description |
| ------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `len` method and property | _none_ | returns the number of characters (not number of bytes) in the string |
| `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` | 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` | 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
--------

View File

@ -43,17 +43,17 @@ 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 |
| Escape sequence | Meaning |
| --------------- | -------------------------------- |
| `\\` | back-slash `\` |
| `\t` | tab |
| `\r` | carriage-return `CR` |
| `\n` | line-feed `LF` |
| `\"` | 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

View File

@ -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");
```

View File

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

View File

@ -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();
@ -66,27 +72,27 @@ engine.register_fn("config_set", move |value: i64| *cfg.borrow_mut().some_field
let cfg = config.clone();
engine.register_fn("config_add", move |value: String|
cfg.borrow_mut().some_list.push(value)
cfg.borrow_mut().some_list.push(value)
);
let cfg = config.clone();
engine.register_fn("config_add", move |values: &mut Array|
cfg.borrow_mut().some_list.extend(values.into_iter().map(|v| v.to_string()))
cfg.borrow_mut().some_list.extend(values.into_iter().map(|v| v.to_string()))
);
let cfg = config.clone();
engine.register_fn("config_add", move |key: String, value: bool|
cfg.borrow_mut().some_map.insert(key, value)
cfg.borrow_mut().some_map.insert(key, value)
);
let cfg = config.clone();
engine.register_fn("config_contains", move |value: String|
cfg.borrow().some_list.contains(&value)
cfg.borrow().some_list.contains(&value)
);
let cfg = config.clone();
engine.register_fn("config_is_set", move |value: String|
cfg.borrow().some_map.get(&value).cloned().unwrap_or(false)
cfg.borrow().some_map.get(&value).cloned().unwrap_or(false)
);
```

View File

@ -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();

View File

@ -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,37 +77,52 @@ 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| {
if on {
if bunny.borrow().is_going() {
println!("Still going...");
} else {
bunny.borrow_mut().go();
}
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...");
} else {
if bunny.borrow().is_going() {
bunny.borrow_mut().stop();
} else {
println!("Already out of battery!");
}
bunny.borrow_mut().go();
}
} else {
if bunny.borrow().is_going() {
bunny.borrow_mut().stop();
} else {
println!("Already out of battery!");
}
}
).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();
```

View File

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

View File

@ -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 {
@ -260,12 +260,12 @@ 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] |
| 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]` | `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] |

View File

@ -53,10 +53,10 @@ impl TestStruct {
let mut engine = Engine::new();
engine
.register_type::<TestStruct>()
.register_get_set("xyz", TestStruct::get_field, TestStruct::set_field)
.register_fn("new_ts", TestStruct::new);
engine
.register_type::<TestStruct>()
.register_get_set("xyz", TestStruct::get_field, TestStruct::set_field)
.register_fn("new_ts", TestStruct::new);
let result = engine.eval::<String>(r#"let a = new_ts(); a.xyz = "42"; a.xyz"#)?;

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`
------------------------------------------
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;
```

View File

@ -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();
"#)?;
```

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

View File

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

View File

@ -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;

View File

@ -84,12 +84,12 @@ Extract Arguments
To extract an argument from the `args` parameter (`&mut [&mut Dynamic]`), use the following:
| Argument type | Access (`n` = argument position) | Result |
| ------------------------------ | ------------------------------------- | --------------------------------------------------------- |
| [Primary type][standard types] | `args[n].clone().cast::<T>()` | copy of value |
| Custom type | `args[n].read_lock::<T>().unwrap()` | immutable reference to value |
| Custom type (consumed) | `std::mem::take(args[n]).cast::<T>()` | the _consumed_ value.<br/>The original value becomes `()` |
| `this` object | `args[0].write_lock::<T>().unwrap()` | mutable reference to value |
| Argument type | Access (`n` = argument position) | Result |
| ------------------------------ | ------------------------------------- | ----------------------------------------------------- |
| [Primary type][standard types] | `args[n].clone().cast::<T>()` | copy of value |
| [Custom type] | `args[n].read_lock::<T>().unwrap()` | immutable reference to value |
| [Custom type] (consumed) | `std::mem::take(args[n]).cast::<T>()` | the _consumed_ value; the original value becomes `()` |
| `this` object | `args[0].write_lock::<T>().unwrap()` | mutable reference to value |
When there is a mutable reference to the `this` object (i.e. the first argument),
there can be no other immutable references to `args`, otherwise the Rust borrow checker will complain.

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
-------------------

View File

@ -11,11 +11,15 @@ 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)
.register_set("field", set_field)
.register_fn("do_work", action);
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...
```