Add Rhai book.

This commit is contained in:
Stephen Chung
2020-06-20 12:06:17 +08:00
parent 7e80d62df5
commit c7f1e12d6a
101 changed files with 3827 additions and 0 deletions

148
doc/src/rust/custom.md Normal file
View File

@@ -0,0 +1,148 @@
Register a Custom Type and its Methods
=====================================
{{#include ../links.md}}
Rhai works seamlessly with _any_ complex Rust type. The type can be registered with the `Engine`, as below.
Support for custom types can be turned off via the [`no_object`] feature.
```rust
use rhai::{Engine, EvalAltResult};
use rhai::RegisterFn;
#[derive(Clone)]
struct TestStruct {
field: i64
}
impl TestStruct {
fn update(&mut self) {
self.field += 41;
}
fn new() -> Self {
TestStruct { field: 1 }
}
}
fn main() -> Result<(), Box<EvalAltResult>>
{
let engine = Engine::new();
engine.register_type::<TestStruct>();
engine.register_fn("update", TestStruct::update);
engine.register_fn("new_ts", TestStruct::new);
let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
println!("result: {}", result.field); // prints 42
Ok(())
}
```
Register a Custom Type
---------------------
A custom type must implement `Clone` as this allows the [`Engine`] to pass by value.
Notice that the custom type needs to be _registered_ using `Engine::register_type`.
```rust
#[derive(Clone)]
struct TestStruct {
field: i64
}
impl TestStruct {
fn update(&mut self) { // methods take &mut as first parameter
self.field += 41;
}
fn new() -> Self {
TestStruct { field: 1 }
}
}
let engine = Engine::new();
engine.register_type::<TestStruct>();
```
Methods on Custom Type
---------------------
To use native custom types, methods and functions in Rhai scripts, simply register them
using one of the `Engine::register_XXX` API.
Below, the `update` and `new` methods are registered using `Engine::register_fn`.
```rust
engine.register_fn("update", TestStruct::update); // registers 'update(&mut TestStruct)'
engine.register_fn("new_ts", TestStruct::new); // registers 'new()'
```
***Note**: Rhai follows the convention that methods of custom types take a `&mut` first parameter
so that invoking methods can update the types. All other parameters in Rhai are passed by value (i.e. clones).*
Use the Custom Type in Scripts
-----------------------------
The custom type is then ready for use in scripts. Scripts can see the functions and methods registered earlier.
Get the evaluation result back out just as before, this time casting to the custom type:
```rust
let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
println!("result: {}", result.field); // prints 42
```
Method-Call Style vs. Function-Call Style
----------------------------------------
In fact, any function with a first argument that is a `&mut` reference can be used as method calls because
internally they are the same thing: methods on a type is implemented as a functions taking a `&mut` first argument.
```rust
fn foo(ts: &mut TestStruct) -> i64 {
ts.field
}
engine.register_fn("foo", foo); // register ad hoc function with correct signature
let result = engine.eval::<i64>(
"let x = new_ts(); x.foo()" // 'foo' can be called like a method on 'x'
)?;
println!("result: {}", result); // prints 1
```
Under [`no_object`], however, the _method_ style of function calls (i.e. calling a function as an object-method)
is no longer supported.
```rust
// Below is a syntax error under 'no_object' because 'clear' cannot be called in method style.
let result = engine.eval::<()>("let x = [1, 2, 3]; x.clear()")?;
```
[`type_of()`]
-------------
[`type_of()`] works fine with custom types and returns the name of the type.
If `Engine::register_type_with_name` is used to register the custom type
with a special "pretty-print" name, [`type_of()`] will return that name instead.
```rust
engine.register_type::<TestStruct>();
engine.register_fn("new_ts", TestStruct::new);
let x = new_ts();
print(x.type_of()); // prints "path::to::module::TestStruct"
engine.register_type_with_name::<TestStruct>("Hello");
engine.register_fn("new_ts", TestStruct::new);
let x = new_ts();
print(x.type_of()); // prints "Hello"
```

View File

@@ -0,0 +1,10 @@
Disable Custom Types
====================
{{#include ../links.md}}
The custom types API `register_type`, `register_type_with_name`, `register_get`, `register_set`, `register_get_set`,
`register_indexer_get`, `register_indexer_set` and `register_indexer_get_set` are not available under [`no_object`].
The indexers API `register_indexer_get`, `register_indexer_set` and `register_indexer_get_set` are also
not available under [`no_index`].

41
doc/src/rust/fallible.md Normal file
View File

@@ -0,0 +1,41 @@
Register a Fallible Rust Function
================================
{{#include ../links.md}}
If a function is _fallible_ (i.e. it returns a `Result<_, Error>`), it can be registered with `register_result_fn`
(using the `RegisterResultFn` trait).
The function must return `Result<Dynamic, Box<EvalAltResult>>`.
`Box<EvalAltResult>` implements `From<&str>` and `From<String>` etc.
and the error text gets converted into `Box<EvalAltResult::ErrorRuntime>`.
The error values are `Box`-ed in order to reduce memory footprint of the error path, which should be hit rarely.
```rust
use rhai::{Engine, EvalAltResult, Position};
use rhai::RegisterResultFn; // use 'RegisterResultFn' trait for 'register_result_fn'
// Function that may fail - the result type must be 'Dynamic'
fn safe_divide(x: i64, y: i64) -> Result<Dynamic, Box<EvalAltResult>> {
if y == 0 {
// Return an error if y is zero
Err("Division by zero!".into()) // short-cut to create Box<EvalAltResult::ErrorRuntime>
} else {
Ok((x / y).into()) // convert result into 'Dynamic'
}
}
fn main()
{
let engine = Engine::new();
// Fallible functions that return Result values must use register_result_fn()
engine.register_result_fn("divide", safe_divide);
if let Err(error) = engine.eval::<i64>("divide(40, 0)") {
println!("Error: {:?}", *error); // prints ErrorRuntime("Division by zero detected!", (1, 1)")
}
}
```

73
doc/src/rust/functions.md Normal file
View File

@@ -0,0 +1,73 @@
Register a Rust Function
========================
{{#include ../links.md}}
Rhai's scripting engine is very lightweight. It gets most of its abilities from functions.
To call these functions, they need to be _registered_ with the [`Engine`] using `Engine::register_fn`
(in the `RegisterFn` trait) and `Engine::register_result_fn` (in the `RegisterResultFn` trait,
see [fallible functions](/rust/fallible.md)).
```rust
use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString};
use rhai::RegisterFn; // use 'RegisterFn' trait for 'register_fn'
use rhai::RegisterResultFn; // use 'RegisterResultFn' trait for 'register_result_fn'
// Normal function that returns a standard type
// Remember to use 'ImmutableString' and not 'String'
fn add_len(x: i64, s: ImmutableString) -> i64 {
x + s.len()
}
// Alternatively, '&str' maps directly to 'ImmutableString'
fn add_len_str(x: i64, s: &str) -> i64 {
x + s.len()
}
// Function that returns a 'Dynamic' value - must return a 'Result'
fn get_any_value() -> Result<Dynamic, Box<EvalAltResult>> {
Ok((42_i64).into()) // standard types can use 'into()'
}
fn main() -> Result<(), Box<EvalAltResult>>
{
let engine = Engine::new();
engine.register_fn("add", add_len);
engine.register_fn("add_str", add_len_str);
let result = engine.eval::<i64>(r#"add(40, "xx")"#)?;
println!("Answer: {}", result); // prints 42
let result = engine.eval::<i64>(r#"add_str(40, "xx")"#)?;
println!("Answer: {}", result); // prints 42
// Functions that return Dynamic values must use register_result_fn()
engine.register_result_fn("get_any_value", get_any_value);
let result = engine.eval::<i64>("get_any_value()")?;
println!("Answer: {}", result); // prints 42
Ok(())
}
```
To create a [`Dynamic`] value, use the `Dynamic::from` method.
[Standard types] in Rhai can also use `into()`.
```rust
use rhai::Dynamic;
let x = (42_i64).into(); // 'into()' works for standard types
let y = Dynamic::from("hello!".to_string()); // remember &str is not supported by Rhai
```
Functions registered with the [`Engine`] can be _overloaded_ as long as the _signature_ is unique,
i.e. different functions can have the same name as long as their parameters are of different types
and/or different number.
New definitions _overwrite_ previous definitions of the same name and same number/types of parameters.

32
doc/src/rust/generic.md Normal file
View File

@@ -0,0 +1,32 @@
Register a Generic Rust Function
===============================
{{#include ../links.md}}
Rust generic functions can be used in Rhai, but separate instances for each concrete type must be registered separately.
This essentially _overloads_ the function with different parameter types as Rhai does not natively support generics
but Rhai does support _function overloading_.
```rust
use std::fmt::Display;
use rhai::{Engine, RegisterFn};
fn show_it<T: Display>(x: &mut T) {
println!("put up a good show: {}!", x)
}
fn main()
{
let engine = Engine::new();
engine.register_fn("print", show_it::<i64>);
engine.register_fn("print", show_it::<bool>);
engine.register_fn("print", show_it::<ImmutableString>);
}
```
The above example shows how to register multiple functions
(or, in this case, multiple overloaded versions of the same function)
under the same name.

View File

@@ -0,0 +1,42 @@
Custom Type Getters and Setters
==============================
{{#include ../links.md}}
A custom type can also expose members by registering `get` and/or `set` functions.
```rust
#[derive(Clone)]
struct TestStruct {
field: String
}
impl TestStruct {
// Returning a 'String' is OK - Rhai converts it into 'ImmutableString'
fn get_field(&mut self) -> String {
self.field.clone()
}
// Remember Rhai uses 'ImmutableString' or '&str' instead of 'String'
fn set_field(&mut self, new_val: ImmutableString) {
// Get a 'String' from an 'ImmutableString'
self.field = (*new_val).clone();
}
fn new() -> Self {
TestStruct { field: "hello" }
}
}
let engine = Engine::new();
engine.register_type::<TestStruct>();
engine.register_get_set("xyz", TestStruct::get_field, TestStruct::set_field);
engine.register_fn("new_ts", TestStruct::new);
// Return result can be 'String' - Rhai will automatically convert it from 'ImmutableString'
let result = engine.eval::<String>(r#"let a = new_ts(); a.xyz = "42"; a.xyz"#)?;
println!("Answer: {}", result); // prints 42
```

47
doc/src/rust/indexers.md Normal file
View File

@@ -0,0 +1,47 @@
Custom Type Indexers
===================
{{#include ../links.md}}
A custom type can also expose an _indexer_ by registering an indexer function.
A custom type with an indexer function defined can use the bracket '`[]`' notation to get a property value.
Indexers are disabled when the [`no_index`] feature is used.
```rust
#[derive(Clone)]
struct TestStruct {
fields: Vec<i64>
}
impl TestStruct {
fn get_field(&mut self, index: i64) -> i64 {
self.fields[index as usize]
}
fn set_field(&mut self, index: i64, value: i64) {
self.fields[index as usize] = value
}
fn new() -> Self {
TestStruct { fields: vec![1, 2, 3, 4, 5] }
}
}
let engine = Engine::new();
engine.register_type::<TestStruct>();
engine.register_fn("new_ts", TestStruct::new);
// Shorthand: engine.register_indexer_get_set(TestStruct::get_field, TestStruct::set_field);
engine.register_indexer_get(TestStruct::get_field);
engine.register_indexer_set(TestStruct::set_field);
let result = engine.eval::<i64>("let a = new_ts(); a[2] = 42; a[2]")?;
println!("Answer: {}", result); // prints 42
```
For efficiency reasons, indexers **cannot** be used to overload (i.e. override) built-in indexing operations for
[arrays] and [object maps].

57
doc/src/rust/operators.md Normal file
View File

@@ -0,0 +1,57 @@
Operator Overloading
===================
{{#include ../links.md}}
In Rhai, a lot of functionalities are actually implemented as functions, including basic operations
such as arithmetic calculations.
For example, in the expression "`a + b`", the `+` operator is _not_ built in, but calls a function named "`+`" instead!
```rust
let x = a + b;
let x = +(a, b); // <- the above is equivalent to this function call
```
Similarly, comparison operators including `==`, `!=` etc. are all implemented as functions,
with the stark exception of `&&` and `||`. Because they [_short-circuit_](/language/logic.md#boolean-operators),
`&&` and `||` are handled specially and _not_ via a function; as a result, overriding them has no effect at all.
Operator functions cannot be defined as a script function (because operators syntax are not valid function names).
However, operator functions _can_ be registered to the [`Engine`] via the methods
`Engine::register_fn`, `Engine::register_result_fn` etc.
When a custom operator function is registered with the same name as an operator, it _overrides_ the built-in version.
```rust
use rhai::{Engine, EvalAltResult, RegisterFn};
let mut engine = Engine::new();
fn strange_add(a: i64, b: i64) -> i64 { (a + b) * 42 }
engine.register_fn("+", strange_add); // overload '+' operator for two integers!
let result: i64 = engine.eval("1 + 0"); // the overloading version is used
println!("result: {}", result); // prints 42
let result: f64 = engine.eval("1.0 + 0.0"); // '+' operator for two floats not overloaded
println!("result: {}", result); // prints 1.0
fn mixed_add(a: i64, b: f64) -> f64 { (a as f64) + b }
engine.register_fn("+", mixed_add); // register '+' operator for an integer and a float
let result: i64 = engine.eval("1 + 1.0"); // prints 2.0 (normally an error)
```
Normally, use operator overloading for [custom types] only.
Be very careful when overriding built-in operators because script authors expect standard operators to behave in a
consistent and predictable manner, and will be annoyed if a calculation for '`+`' turns into a subtraction, for example.
Operator overloading also impacts script optimization when using [`OptimizationLevel::Full`].
See the [script-optimization] for more details.

17
doc/src/rust/options.md Normal file
View File

@@ -0,0 +1,17 @@
Engine Configuration Options
===========================
{{#include ../links.md}}
A number of other configuration options are available from the `Engine` to fine-tune behavior and safeguards.
| Method | Not available under | Description |
| ------------------------ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `set_optimization_level` | [`no_optimize`] | Set the amount of script _optimizations_ performed. See [script optimization]. |
| `set_max_expr_depths` | [`unchecked`] | Set the maximum nesting levels of an expression/statement. See [maximum statement depth]. |
| `set_max_call_levels` | [`unchecked`] | Set the maximum number of function call levels (default 50) to avoid infinite recursion. See [maximum call stack depth]. |
| `set_max_operations` | [`unchecked`] | Set the maximum number of _operations_ that a script is allowed to consume. See [maximum number of operations]. |
| `set_max_modules` | [`unchecked`] | Set the maximum number of [modules] that a script is allowed to load. See [maximum number of modules]. |
| `set_max_string_size` | [`unchecked`] | Set the maximum length (in UTF-8 bytes) for [strings]. See [maximum length of strings]. |
| `set_max_array_size` | [`unchecked`], [`no_index`] | Set the maximum size for [arrays]. See [maximum size of arrays]. |
| `set_max_map_size` | [`unchecked`], [`no_object`] | Set the maximum number of properties for [object maps]. See [maximum size of object maps]. |

19
doc/src/rust/override.md Normal file
View File

@@ -0,0 +1,19 @@
Override a Built-in Function
===========================
{{#include ../links.md}}
Any similarly-named function defined in a script overrides any built-in or registered
native Rust function of the same name and number of parameters.
```rust
// Override the built-in function 'to_int'
fn to_int(num) {
print("Ha! Gotcha! " + num);
}
print(to_int(123)); // what happens?
```
A registered native Rust function, in turn, overrides any built-in function of the
same name, number and types of parameters.

52
doc/src/rust/packages.md Normal file
View File

@@ -0,0 +1,52 @@
Packages
========
{{#include ../links.md}}
Standard built-in Rhai features are provided in various _packages_ that can be loaded via a call to `Engine::load_package`.
Packages reside under `rhai::packages::*` and the trait `rhai::packages::Package` must be loaded in order for
packages to be used.
```rust
use rhai::Engine;
use rhai::packages::Package // load the 'Package' trait to use packages
use rhai::packages::CorePackage; // the 'core' package contains basic functionalities (e.g. arithmetic)
let mut engine = Engine::new_raw(); // create a 'raw' Engine
let package = CorePackage::new(); // create a package - can be shared among multiple `Engine` instances
engine.load_package(package.get()); // load the package manually. 'get' returns a reference to the shared package
```
The follow packages are available:
| Package | Description | In `Core` | In `Standard` |
| ---------------------- | ------------------------------------------------------------------------------------------------------ | :-------: | :-----------: |
| `ArithmeticPackage` | Arithmetic operators (e.g. `+`, `-`, `*`, `/`) for numeric types that are not built in (e.g. `u16`) | Yes | Yes |
| `BasicIteratorPackage` | Numeric ranges (e.g. `range(1, 10)`) | Yes | Yes |
| `LogicPackage` | Logical and comparison operators (e.g. `==`, `>`) for numeric types that are not built in (e.g. `u16`) | Yes | Yes |
| `BasicStringPackage` | Basic string functions (e.g. `print`, `debug`, `len`) that are not built in | Yes | Yes |
| `BasicTimePackage` | Basic time functions (e.g. [timestamps]) | Yes | Yes |
| `MoreStringPackage` | Additional string functions, including converting common types to string | No | Yes |
| `BasicMathPackage` | Basic math functions (e.g. `sin`, `sqrt`) | No | Yes |
| `BasicArrayPackage` | Basic [array] functions (not available under `no_index`) | No | Yes |
| `BasicMapPackage` | Basic [object map] functions (not available under `no_object`) | No | Yes |
| `EvalPackage` | Disable [`eval`] | No | No |
| `CorePackage` | Basic essentials | Yes | Yes |
| `StandardPackage` | Standard library (default for `Engine::new`) | No | Yes |
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).
Once a package is created (e.g. via `new`), it can be _shared_ (via `get`) among multiple instances of [`Engine`],
even across threads (under [`sync`]). Therefore, a package only has to be created _once_.
Packages are actually implemented as [modules], so they share a lot of behavior and characteristics.
The main difference is that a package loads under the _global_ namespace, while a module loads under its own
namespace alias specified in an [`import`] statement (see also [modules]).
A package is _static_ (i.e. pre-loaded into an [`Engine`]), while a module is _dynamic_ (i.e. loaded with
the `import` statement).
Custom packages can also be created. See the macro [`def_package!`](https://docs.rs/rhai/0.13.0/rhai/macro.def_package.html).

View File

@@ -0,0 +1,16 @@
Printing for Custom Types
========================
{{#include ../links.md}}
To use custom types for [`print`] and [`debug`], or convert its value into a [string], it is necessary that the following
functions be registered (assuming the custom type is `T : Display + Debug`):
| Function | Signature | Typical implementation | Usage |
| ----------- | ------------------------------------------------ | ------------------------------------- | --------------------------------------------------------------------------------------- |
| `to_string` | `|s: &mut T| -> ImmutableString` | `s.to_string().into()` | Converts the custom type into a [string] |
| `print` | `|s: &mut T| -> ImmutableString` | `s.to_string().into()` | Converts the custom type into a [string] for the [`print`](#print-and-debug) statement |
| `debug` | `|s: &mut T| -> ImmutableString` | `format!("{:?}", s).into()` | Converts the custom type into a [string] for the [`debug`](#print-and-debug) statement |
| `+` | `|s1: ImmutableString, s: T| -> ImmutableString` | `s1 + s` | Append the custom type to another [string], for `print("Answer: " + type);` usage |
| `+` | `|s: T, s2: ImmutableString| -> ImmutableString` | `s.to_string().push_str(&s2).into();` | Append another [string] to the custom type, for `print(type + " is the answer");` usage |
| `+=` | `|s1: &mut ImmutableString, s: T|` | `s1 += s.to_string()` | Append the custom type to an existing [string], for `s += type;` usage |

59
doc/src/rust/scope.md Normal file
View File

@@ -0,0 +1,59 @@
`Scope` - Initializing and Maintaining State
===========================================
{{#include ../links.md}}
By default, Rhai treats each [`Engine`] invocation as a fresh one, persisting only the functions that have been defined
but no global state. This gives each evaluation a clean starting slate.
In order to continue using the same global state from one invocation to the next,
such a state must be manually created and passed in.
All `Scope` variables are [`Dynamic`], meaning they can store values of any type.
Under [`sync`], however, only types that are `Send + Sync` are supported, and the entire `Scope` itself
will also be `Send + Sync`. This is extremely useful in multi-threaded applications.
In this example, a global state object (a `Scope`) is created with a few initialized variables,
then the same state is threaded through multiple invocations:
```rust
use rhai::{Engine, Scope, EvalAltResult};
fn main() -> Result<(), Box<EvalAltResult>>
{
let engine = Engine::new();
// First create the state
let mut scope = Scope::new();
// Then push (i.e. add) some initialized variables into the state.
// Remember the system number types in Rhai are i64 (i32 if 'only_i32') ond f64.
// Better stick to them or it gets hard working with the script.
scope.push("y", 42_i64);
scope.push("z", 999_i64);
// 'set_value' adds a variable when one doesn't exist
scope.set_value("s", "hello, world!".to_string()); // remember to use 'String', not '&str'
// First invocation
engine.eval_with_scope::<()>(&mut scope, r"
let x = 4 + 5 - y + z + 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
// 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);
// We can modify scope variables directly with 'set_value'
scope.set_value("y", 42_i64);
assert_eq!(scope.get_value::<i64>("y").expect("variable y should exist"), 42);
Ok(())
}
```

21
doc/src/rust/strings.md Normal file
View File

@@ -0,0 +1,21 @@
`String` Parameters in Rust Functions
====================================
{{#include ../links.md}}
Rust functions accepting parameters of `String` should use `&str` instead because it maps directly to [`ImmutableString`]
which is the type that Rhai uses to represent [strings] internally.
```rust
fn get_len1(s: String) -> i64 { s.len() as i64 } // <- Rhai will not find this function
fn get_len2(s: &str) -> i64 { s.len() as i64 } // <- Rhai finds this function fine
fn get_len3(s: ImmutableString) -> i64 { s.len() as i64 } // <- the above is equivalent to this
engine.register_fn("len1", get_len1);
engine.register_fn("len2", get_len2);
engine.register_fn("len3", get_len3);
let len = engine.eval::<i64>("x.len1()")?; // error: function 'len1 (&str | ImmutableString)' not found
let len = engine.eval::<i64>("x.len2()")?; // works fine
let len = engine.eval::<i64>("x.len3()")?; // works fine
```

13
doc/src/rust/traits.md Normal file
View File

@@ -0,0 +1,13 @@
Traits
======
{{#include ../links.md}}
A number of traits, under the `rhai::` module namespace, provide additional functionalities.
| Trait | Description | Methods |
| ------------------ | ---------------------------------------------------------------------------------------- | --------------------------------------- |
| `RegisterFn` | Trait for registering functions | `register_fn` |
| `RegisterResultFn` | Trait for registering fallible functions returning `Result<Dynamic, Box<EvalAltResult>>` | `register_result_fn` |
| `Func` | Trait for creating anonymous functions from script | `create_from_ast`, `create_from_script` |
| `ModuleResolver` | Trait implemented by module resolution services | `resolve` |