reorganize module
This commit is contained in:
@@ -1,164 +0,0 @@
|
||||
Manage `AST`'s
|
||||
==============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
When compiling a Rhai script to an [`AST`], the following data are packaged together as a single unit:
|
||||
|
||||
| Data | Type | Description | Requires feature | Access API |
|
||||
| -------------------------------- | :---------------------------------------: | ------------------------------------------------------------------------------------------------- | :------------------------------------: | :------------------------------------------------------------------------------------------------------------: |
|
||||
| Source name | [`ImmutableString`] | optional text name to identify the source of the script | | `source(&self)`,<br/>`clone_source(&self)`,<br/>`set_source(&mut self, source)`,<br/>`clear_source(&mut self)` |
|
||||
| [Module documentation][comments] | [`Vec<SmartString>`][`SmartString`] | documentation of the script | [`metadata`] | `doc(&self)`,<br/>`clear_doc(&mut self)` |
|
||||
| Statements | `Vec<Stmt>` | list of script statements at global level | [`internals`] | `statements(&self)`,<br/>`statements_mut(&mut self)` |
|
||||
| Functions | [`Shared<Module>`][`Module`] | [functions] defined in the script | [`internals`],<br/>not [`no_function`] | `shared_lib(&self)` |
|
||||
| Embedded [module resolver] | [`StaticModuleResolver`][module resolver] | embedded [module resolver] for [self-contained `AST`]({{rootUrl}}/rust/modules/self-contained.md) | [`internals`],<br/>not [`no_module`] | `resolver(&self)` |
|
||||
|
||||
Most of the [`AST`] API is available only under the [`internals`] feature.
|
||||
|
||||
```admonish tip.small "Tip: Source name"
|
||||
|
||||
Use the source name to identify the source script in errors – useful when multiple [modules]
|
||||
are imported recursively.
|
||||
```
|
||||
|
||||
~~~admonish info.small "`AST` public API"
|
||||
|
||||
For the complete [`AST`] API, refer to the [documentation](https://docs.rs/rhai/{{version}}/rhai/struct.AST.html) online.
|
||||
~~~
|
||||
|
||||
|
||||
Extract Only Functions
|
||||
----------------------
|
||||
|
||||
The following methods, not available under [`no_function`], allow manipulation of the [functions]
|
||||
encapsulated within an [`AST`]:
|
||||
|
||||
| Method | Description |
|
||||
| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| `clone_functions_only(&self)` | clone the [`AST`] into a new [`AST`] with only [functions], excluding statements |
|
||||
| `clone_functions_only_filtered(&self, filter)` | clone the [`AST`] into a new [`AST`] with only [functions] that pass the filter predicate, excluding statements |
|
||||
| `retain_functions(&mut self, filter)` | remove all [functions] in the [`AST`] that do not pass a particular predicate filter; statements are untouched |
|
||||
| `iter_functions(&self)` | return an iterator on all the [functions] in the [`AST`] |
|
||||
| `clear_functions(&mut self)` | remove all [functions] from the [`AST`], leaving only statements |
|
||||
|
||||
|
||||
Extract Only Statements
|
||||
-----------------------
|
||||
|
||||
The following methods allow manipulation of the statements in an [`AST`]:
|
||||
|
||||
| Method | Description |
|
||||
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
|
||||
| `clone_statements_only(&self)` | clone the [`AST`] into a new [`AST`] with only the statements, excluding [functions] |
|
||||
| `clear_statements(&mut self)` | remove all statements from the [`AST`], leaving only [functions] |
|
||||
| `iter_literal_variables(&self, constants, variables)` | return an iterator on all top-level literal constant and/or variable definitions in the [`AST`] |
|
||||
|
||||
|
||||
Merge and Combine AST's
|
||||
-----------------------
|
||||
|
||||
The following methods merge one [`AST`] with another:
|
||||
|
||||
| Method | Description |
|
||||
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `merge(&self, &ast)`,<br />`+` operator | append the second [`AST`] to this [`AST`], yielding a new [`AST`] that is a combination of the two; statements are simply appended, [functions] in the second [`AST`] of the same name and arity override similar [functions] in this [`AST`] |
|
||||
| `merge_filtered(&self, &ast, filter)` | append the second [`AST`] (but only [functions] that pass the predicate filter) to this [`AST`], yielding a new [`AST`] that is a combination of the two; statements are simply appended, [functions] in the second [`AST`] of the same name and arity override similar [functions] in this [`AST`] |
|
||||
| `combine(&mut self, ast)`,<br />`+=` operator | append the second [`AST`] to this [`AST`]; statements are simply appended, [functions] in the second [`AST`] of the same name and arity override similar [functions] in this [`AST`] |
|
||||
| `combine_filtered(&mut self, ast, filter)` | append the second [`AST`] (but only [functions] that pass the predicate filter) to this [`AST`]; statements are simply appended, [functions] in the second [`AST`] of the same name and arity override similar [functions] in this [`AST`] |
|
||||
|
||||
When statements are appended, beware that this may change the semantics of the script.
|
||||
|
||||
```rust
|
||||
// First script
|
||||
let ast1 = engine.compile(
|
||||
"
|
||||
fn foo(x) { 42 + x }
|
||||
foo(1)
|
||||
")?;
|
||||
|
||||
// Second script
|
||||
let ast2 = engine.compile(
|
||||
"
|
||||
fn foo(n) { `hello${n}` }
|
||||
foo("!")
|
||||
")?;
|
||||
|
||||
// Merge them
|
||||
let merged = ast1.merge(&ast2);
|
||||
|
||||
// Notice that using the '+' operator also works:
|
||||
let merged = &ast1 + &ast2;
|
||||
```
|
||||
|
||||
`merged` in the above example essentially contains the following script program:
|
||||
|
||||
```js
|
||||
fn foo(n) { `hello${n}` } // <- definition of first 'foo' is overwritten
|
||||
foo(1) // <- notice this will be "hello1" instead of 43,
|
||||
// but it is no longer the return value
|
||||
foo("!") // <- returns "hello!"
|
||||
```
|
||||
|
||||
|
||||
Walk an AST
|
||||
-----------
|
||||
|
||||
The [`internals`] feature allows access to internal Rhai data structures, particularly the nodes
|
||||
that make up the [`AST`].
|
||||
|
||||
### AST node types
|
||||
|
||||
There are a few useful types when walking an [`AST`]:
|
||||
|
||||
| Type | Description |
|
||||
| ------------ | ----------------------------------------------------------------- |
|
||||
| `ASTNode` | an `enum` with two variants: `Expr` or `Stmt` |
|
||||
| `Expr` | an _expression_ |
|
||||
| `Stmt` | a _statement_ |
|
||||
| `BinaryExpr` | a sub-type containing the LHS and RHS of a binary expression |
|
||||
| `FnCallExpr` | a sub-type containing information on a function call |
|
||||
| `CustomExpr` | a sub-type containing information on a [custom syntax] expression |
|
||||
|
||||
The `AST::walk` method takes a callback function and recursively walks the [`AST`] in depth-first
|
||||
manner, with the parent node visited before its children.
|
||||
|
||||
### Callback function signature
|
||||
|
||||
The signature of the callback function takes the following form.
|
||||
|
||||
> ```rust
|
||||
> FnMut(&[ASTNode]) -> bool
|
||||
> ```
|
||||
|
||||
The single argument passed to the method contains a slice of `ASTNode` types representing the path
|
||||
from the current node to the root of the [`AST`].
|
||||
|
||||
Return `true` to continue walking the [`AST`], or `false` to terminate.
|
||||
|
||||
### Children visit order
|
||||
|
||||
The order of visits to the children of each node type:
|
||||
|
||||
| Node type | Children visit order |
|
||||
| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [`if`] statement | <ol><li>condition expression</li><li>_then_ statements</li><li>_else_ statements (if any)</li></ol> |
|
||||
| [`switch`] statement | <ol><li>match element</li><li>each of the case conditions and statements, in order</li><li>each of the range conditions and statements, in order</li><li>default statements (if any)</li></ol> |
|
||||
| [`while`], [`do`], [`loop`] statement | <ol><li>condition expression</li><li>statements body</li></ol> |
|
||||
| [`for`] statement | <ol><li>collection expression</li><li>statements body</li></ol> |
|
||||
| [`return`] statement | return value expression |
|
||||
| [`throw`] statement | exception value expression |
|
||||
| [`try` ... `catch`][exception] statement | <ol><li>`try` statements body</li><li>`catch` statements body</li></ol> |
|
||||
| [`import`] statement | path expression |
|
||||
| [Array] literal | each of the element expressions, in order |
|
||||
| [Object map] literal | each of the element expressions, in order |
|
||||
| Interpolated [string] | each of the [string]/expression segments, in order |
|
||||
| Indexing | <ol><li>LHS expression</li><li>RHS (index) expression</li></ol> |
|
||||
| Field access/method call | <ol><li>LHS expression</li><li>RHS expression</li></ol> |
|
||||
| `&&`, <code>\|\|</code>, `??` | <ol><li>LHS expression</li><li>RHS expression</li></ol> |
|
||||
| [Function] call, [operator] expression | each of the argument expressions, in order |
|
||||
| [`let`][variable], [`const`][constant] statement | value expression |
|
||||
| Assignment statement | <ol><li>l-value expression</li><li>value expression</li></ol> |
|
||||
| Statements block | each of the statements, in order |
|
||||
| Custom syntax expression | each of the inputs stream, in order |
|
||||
| All others | single child (if any) |
|
@@ -1,24 +0,0 @@
|
||||
Built-in Operators
|
||||
==================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
The following operators are built-in, meaning that they are always available, even when using a [raw `Engine`].
|
||||
|
||||
All built-in operators are binary, and are supported for both operands of the same type.
|
||||
|
||||
| Operators | Assignment operators | Supported types<br/>(see [standard types]) |
|
||||
| ------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `+`, | `+=` | <ul><li>`INT`</li><li>`FLOAT` (if not [`no_float`])</li><li>[`Decimal`][rust_decimal] (requires [`decimal`])</li><li>`char`</li><li>[string]</li></ul> |
|
||||
| `-`, `*`, `/`, `%`, `**`, | `-=`, `*=`, `/=`, `%=`, `**=` | <ul><li>`INT`</li><li>`FLOAT` (if not [`no_float`])</li><li>[`Decimal`][rust_decimal] (requires [`decimal`])</li></ul> |
|
||||
| `<<`, `>>` | `<<=`, `>>=` | <ul><li>`INT`</li></ul> |
|
||||
| `&`, <code>\|</code>, `^` | `&=`, <code>\|=</code>, `^=` | <ul><li>`INT` (bit-wise)</li><li>`bool` (non-short-circuiting)</li></ul> |
|
||||
| `&&`, <code>\|\|</code> | | <ul><li>`bool` (short-circuits)</li></ul> |
|
||||
| `==`, `!=` | | <ul><li>`INT`</li><li>`FLOAT` (if not [`no_float`])</li><li>[`Decimal`][rust_decimal] (requires [`decimal`])</li><li>`bool`</li><li>`char`</li><li>[string]</li><li>[BLOB]</li><li>numeric [range]</li><li>`()`</li></ul> |
|
||||
| `>`, `>=`, `<`, `<=` | | <ul><li>`INT`</li><li>`FLOAT` (if not [`no_float`])</li><li>[`Decimal`][rust_decimal] (requires [`decimal`])</li><li>`char`</li><li>[string]</li><li>`()`</li></ul> |
|
||||
|
||||
```admonish tip.small
|
||||
|
||||
`FLOAT` and [`Decimal`][rust_decimal] also inter-operate with `INT`, while [strings] inter-operate
|
||||
with [characters][string] for certain operators (e.g. `+`).
|
||||
```
|
@@ -1,275 +0,0 @@
|
||||
Call Rhai Functions from Rust
|
||||
=============================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Rhai also allows working _backwards_ from the other direction – i.e. calling a Rhai-scripted
|
||||
[function] from Rust via `Engine::call_fn`.
|
||||
|
||||
```rust
|
||||
┌─────────────┐
|
||||
│ Rhai script │
|
||||
└─────────────┘
|
||||
|
||||
import "process" as proc; // this is evaluated every time
|
||||
|
||||
fn hello(x, y) {
|
||||
// hopefully 'my_var' is in scope when this is called
|
||||
x.len + y + my_var
|
||||
}
|
||||
|
||||
fn hello(x) {
|
||||
// hopefully 'my_string' is in scope when this is called
|
||||
x * my_string.len()
|
||||
}
|
||||
|
||||
fn hello() {
|
||||
// hopefully 'MY_CONST' is in scope when this is called
|
||||
if MY_CONST {
|
||||
proc::process_data(42); // can access imported module
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
┌──────┐
|
||||
│ Rust │
|
||||
└──────┘
|
||||
|
||||
// Compile the script to AST
|
||||
let ast = engine.compile(script)?;
|
||||
|
||||
// Create a custom 'Scope'
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// A custom 'Scope' can also contain any variables/constants available to
|
||||
// the functions
|
||||
scope.push("my_var", 42_i64);
|
||||
scope.push("my_string", "hello, world!");
|
||||
scope.push_constant("MY_CONST", true);
|
||||
|
||||
// Evaluate a function defined in the script, passing arguments into the
|
||||
// script as a tuple.
|
||||
//
|
||||
// Beware, arguments must be of the correct types because Rhai does not
|
||||
// have built-in type conversions. If arguments of the wrong types are passed,
|
||||
// the Engine will not find the function.
|
||||
//
|
||||
// Variables/constants pushed into the custom 'Scope'
|
||||
// (i.e. 'my_var', 'my_string', 'MY_CONST') are visible to the function.
|
||||
|
||||
let result = engine.call_fn::<i64>(&mut scope, &ast, "hello", ( "abc", 123_i64 ) )?;
|
||||
// ^^^ ^^^^^^^^^^^^^^^^^^
|
||||
// return type must be specified put arguments in a tuple
|
||||
|
||||
let result = engine.call_fn::<i64>(&mut scope, &ast, "hello", ( 123_i64, ) )?;
|
||||
// ^^^^^^^^^^^^ tuple of one
|
||||
|
||||
let result = engine.call_fn::<i64>(&mut scope, &ast, "hello", () )?;
|
||||
// ^^ unit = tuple of zero
|
||||
```
|
||||
|
||||
~~~admonish danger.small "Warning: Functions with one parameter"
|
||||
|
||||
Functions with only one single parameter is easy to get wrong.
|
||||
|
||||
The proper Rust syntax is a _tuple with one item_:
|
||||
|
||||
```rust
|
||||
( arg , )
|
||||
```
|
||||
|
||||
Notice the comma (`,`) after the argument. Without it, the expression is a single value
|
||||
`(arg)` which is the same as `arg` and not a tuple.
|
||||
|
||||
A syntax error with very confusing error message will be generated by the Rust compiler
|
||||
if the comma is omitted.
|
||||
~~~
|
||||
|
||||
~~~admonish warning.small "Default behavior"
|
||||
|
||||
When using `Engine::call_fn`, the [`AST`] is always evaluated _before_ the [function] is called.
|
||||
|
||||
This is usually desirable in order to [import][`import`] the necessary external [modules] that are
|
||||
needed by the [function].
|
||||
|
||||
All new [variables]/[constants] introduced are, by default, _not_ retained inside the [`Scope`].
|
||||
In other words, the [`Scope`] is _rewound_ before each call.
|
||||
|
||||
If these default behaviors are not desirable, override them with `Engine::call_fn_with_options`.
|
||||
~~~
|
||||
|
||||
|
||||
`FuncArgs` Trait
|
||||
----------------
|
||||
|
||||
```admonish note.side
|
||||
|
||||
Rhai implements [`FuncArgs`][traits] for tuples, arrays and `Vec<T>`.
|
||||
```
|
||||
|
||||
`Engine::call_fn` takes a parameter of any type that implements the [`FuncArgs`][traits] trait,
|
||||
which is used to parse a data type into individual argument values for the [function] call.
|
||||
|
||||
Custom types (e.g. structures) can also implement [`FuncArgs`][traits] so they can be used for
|
||||
calling `Engine::call_fn`.
|
||||
|
||||
```rust
|
||||
use std::iter::once;
|
||||
use rhai::FuncArgs;
|
||||
|
||||
// A struct containing function arguments
|
||||
struct Options {
|
||||
pub foo: bool,
|
||||
pub bar: String,
|
||||
pub baz: i64
|
||||
}
|
||||
|
||||
impl FuncArgs for Options {
|
||||
fn parse<C: Extend<Dynamic>>(self, container: &mut C) {
|
||||
container.extend(once(self.foo.into()));
|
||||
container.extend(once(self.bar.into()));
|
||||
container.extend(once(self.baz.into()));
|
||||
}
|
||||
}
|
||||
|
||||
let options = Options { foo: true, bar: "world", baz: 42 };
|
||||
|
||||
// The type 'Options' can now be used as argument to 'call_fn'
|
||||
// to call a function with three parameters: fn hello(foo, bar, baz)
|
||||
let result = engine.call_fn::<i64>(&mut scope, &ast, "hello", options)?;
|
||||
```
|
||||
|
||||
```admonish warning.small "Warning: You don't need this"
|
||||
|
||||
Implementing `FuncArgs` is almost never needed because Rhai works directly with
|
||||
any [custom type].
|
||||
|
||||
It is used only in niche cases where a [custom type's][custom type] fields need
|
||||
to be split up to pass to functions.
|
||||
```
|
||||
|
||||
|
||||
`Engine::call_fn_with_options`
|
||||
------------------------------
|
||||
|
||||
For more control, use `Engine::call_fn_with_options`, which takes a type `CallFnOptions`:
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, CallFnOptions};
|
||||
|
||||
let options = CallFnOptions::new()
|
||||
.eval_ast(false) // do not evaluate the AST
|
||||
.rewind_scope(false) // do not rewind the scope (i.e. keep new variables)
|
||||
.bind_this_ptr(&mut state); // 'this' pointer
|
||||
|
||||
let result = engine.call_fn_with_options::<i64>(
|
||||
options, // options
|
||||
&mut scope, // scope to use
|
||||
&ast, // AST containing the functions
|
||||
"hello", // function entry-point
|
||||
( "abc", 123_i64 ) // arguments
|
||||
)?;
|
||||
```
|
||||
|
||||
`CallFnOptions` allows control of the following:
|
||||
|
||||
| Field | Type | Default | Build method | Description |
|
||||
| -------------- | :---------------------------------: | :-----: | :-------------: | --------------------------------------------------------------------------------------------------------- |
|
||||
| `eval_ast` | `bool` | `true` | `eval_ast` | evaluate the [`AST`] before calling the target [function] (useful to run [`import` statements]) |
|
||||
| `rewind_scope` | `bool` | `true` | `rewind_scope` | rewind the custom [`Scope`] at the end of the [function] call so new local variables are removed |
|
||||
| `this_ptr` | [`Option<&mut Dynamic>`][`Dynamic`] | `None` | `bind_this_ptr` | bind the `this` pointer to a specific value |
|
||||
| `tag` | [`Option<Dynamic>`][`Dynamic`] | `None` | `with_tag` | set the _custom state_ for this evaluation (accessed via [`NativeCallContext::tag`][`NativeCallContext`]) |
|
||||
|
||||
### Skip evaluation of the `AST`
|
||||
|
||||
By default, the [`AST`] is evaluated before calling the target [function].
|
||||
|
||||
This is necessary to make sure that necessary [modules] imported via [`import`] statements are available.
|
||||
|
||||
Setting `eval_ast` to `false` skips this evaluation.
|
||||
|
||||
### Keep new variables/constants
|
||||
|
||||
By default, the [`Engine`] _rewinds_ the custom [`Scope`] after each call to the initial size,
|
||||
so any new [variable]/[constant] defined are cleared and will not spill into the custom [`Scope`].
|
||||
|
||||
This prevents the [`Scope`] from being continuously polluted by new [variables] and is usually the
|
||||
intuitively expected behavior.
|
||||
|
||||
Setting `rewind_scope` to `false` retains new [variables]/[constants] within the custom [`Scope`].
|
||||
|
||||
This allows the [function] to easily pass values back to the caller by leaving them inside the
|
||||
custom [`Scope`].
|
||||
|
||||
~~~admonish warning.small "Warning: new variables persist in `Scope`"
|
||||
|
||||
If the [`Scope`] is not rewound, beware that all [variables]/[constants] defined at top level of the
|
||||
[function] or in the script body will _persist_ inside the custom [`Scope`].
|
||||
|
||||
If any of them are temporary and not intended to be retained, define them inside a statements block
|
||||
(see example below).
|
||||
~~~
|
||||
|
||||
```rust
|
||||
┌─────────────┐
|
||||
│ Rhai script │
|
||||
└─────────────┘
|
||||
|
||||
fn initialize() {
|
||||
let x = 42; // 'x' is retained
|
||||
let y = x * 2; // 'y' is retained
|
||||
|
||||
// Use a new statements block to define temp variables
|
||||
{
|
||||
let temp = x + y; // 'temp' is NOT retained
|
||||
|
||||
foo = temp * temp; // 'foo' is visible in the scope
|
||||
}
|
||||
}
|
||||
|
||||
let foo = 123; // 'foo' is retained
|
||||
|
||||
// Use a new statements block to define temp variables
|
||||
{
|
||||
let bar = foo / 2; // 'bar' is NOT retained
|
||||
|
||||
foo = bar * bar;
|
||||
}
|
||||
|
||||
|
||||
┌──────┐
|
||||
│ Rust │
|
||||
└──────┘
|
||||
|
||||
let options = CallFnOptions::new().rewind_scope(false);
|
||||
|
||||
engine.call_fn_with_options(options, &mut scope, &ast, "initialize", ())?;
|
||||
|
||||
// At this point, 'scope' contains these variables: 'foo', 'x', 'y'
|
||||
```
|
||||
|
||||
### Bind the `this` pointer
|
||||
|
||||
```admonish note.side
|
||||
|
||||
`Engine::call_fn` cannot call functions in _method-call_ style.
|
||||
```
|
||||
|
||||
`CallFnOptions` can also bind a value to the `this` pointer of a script-defined [function].
|
||||
|
||||
It is possible, then, to call a [function] that uses `this`.
|
||||
|
||||
```rust
|
||||
let ast = engine.compile("fn action(x) { this += x; }")?;
|
||||
|
||||
let mut value: Dynamic = 1_i64.into();
|
||||
|
||||
let options = CallFnOptions::new()
|
||||
.eval_ast(false)
|
||||
.rewind_scope(false)
|
||||
.bind_this_ptr(&mut value);
|
||||
|
||||
engine.call_fn_with_options(options, &mut scope, &ast, "action", ( 41_i64, ))?;
|
||||
|
||||
assert_eq!(value.as_int()?, 42);
|
||||
```
|
@@ -1,160 +0,0 @@
|
||||
Compile a Script (to AST)
|
||||
=========================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
To repeatedly evaluate a script, _compile_ it first with `Engine::compile` into an `AST`
|
||||
(**A**bstract **S**yntax **T**ree) form.
|
||||
|
||||
`Engine::eval_ast_XXX` and `Engine::run_ast_XXX` evaluate a pre-compiled `AST`.
|
||||
|
||||
```rust
|
||||
// Compile to an AST and store it for later evaluations
|
||||
let ast = engine.compile("40 + 2")?;
|
||||
|
||||
for _ in 0..42 {
|
||||
let result: i64 = engine.eval_ast(&ast)?;
|
||||
|
||||
println!("Answer #{i}: {result}"); // prints 42
|
||||
}
|
||||
```
|
||||
|
||||
~~~admonish tip.small "Tip: Compile script file"
|
||||
|
||||
Compiling script files is also supported via `Engine::compile_file`
|
||||
(not available for [`no_std`] or [WASM] builds).
|
||||
|
||||
```rust
|
||||
let ast = engine.compile_file("hello_world.rhai".into())?;
|
||||
```
|
||||
~~~
|
||||
|
||||
~~~admonish info.small "See also: `AST` manipulation API"
|
||||
|
||||
Advanced users who may want to manipulate an `AST`, especially the functions contained within,
|
||||
should see the section on [_Manage AST's_](ast.md) for more details.
|
||||
~~~
|
||||
|
||||
|
||||
Practical Use – Header Template Scripts
|
||||
---------------------------------------------
|
||||
|
||||
Sometimes it is desirable to include a standardized _header template_ in a script that contains
|
||||
pre-defined [functions], [constants] and [imported][`import`] [modules].
|
||||
|
||||
```rust
|
||||
// START OF THE HEADER TEMPLATE
|
||||
// The following should run before every script...
|
||||
|
||||
import "hello" as h;
|
||||
import "world" as w;
|
||||
|
||||
// Standard constants
|
||||
|
||||
const GLOBAL_CONSTANT = 42;
|
||||
const SCALE_FACTOR = 1.2;
|
||||
|
||||
// Standard functions
|
||||
|
||||
fn foo(x, y) { ... }
|
||||
|
||||
fn bar() { ... }
|
||||
|
||||
fn baz() { ... }
|
||||
|
||||
// END OF THE HEADER TEMPLATE
|
||||
|
||||
// Everything below changes from run to run
|
||||
|
||||
foo(bar() + GLOBAL_CONSTANT, baz() * SCALE_FACTOR)
|
||||
```
|
||||
|
||||
### Option 1 – The easy way
|
||||
|
||||
Prepend the script header template onto independent scripts and run them as a whole.
|
||||
|
||||
> **Pros:** Easy!
|
||||
>
|
||||
> **Cons:** If the header template is long, work is duplicated every time to parse it.
|
||||
|
||||
```rust
|
||||
let header_template = "..... // scripts... .....";
|
||||
|
||||
for index in 0..10000 {
|
||||
let user_script = db.get_script(index);
|
||||
|
||||
// Just merge the two scripts...
|
||||
let combined_script = format!("{header_template}\n{user_script}\n");
|
||||
|
||||
// Run away!
|
||||
let result = engine.eval::<i64>(combined_script)?;
|
||||
|
||||
println!("{result}");
|
||||
}
|
||||
```
|
||||
|
||||
### Option 2 – The hard way
|
||||
|
||||
Option 1 requires the script header template to be recompiled every time. This can be expensive if
|
||||
the header is very long.
|
||||
|
||||
This option compiles both the script header template and independent scripts as separate `AST`'s
|
||||
which are then joined together to form a combined `AST`.
|
||||
|
||||
> **Pros:** No need to recompile the header template!
|
||||
>
|
||||
> **Cons:** More work...
|
||||
|
||||
```rust
|
||||
let header_template = "..... // scripts... .....";
|
||||
|
||||
let mut template_ast = engine.compile(header_template)?;
|
||||
|
||||
// If you don't want to run the template, only keep the functions
|
||||
// defined inside (e.g. closures), clear out the statements.
|
||||
template_ast.clear_statements();
|
||||
|
||||
for index in 0..10000 {
|
||||
let user_script = db.get_script(index);
|
||||
|
||||
let user_ast = engine.compile(user_script)?;
|
||||
|
||||
// Merge the two AST's
|
||||
let combined_ast = template_ast + user_ast;
|
||||
|
||||
// Run away!
|
||||
let result = engine.eval_ast::<i64>(combined_ast)?;
|
||||
|
||||
println!("{result}");
|
||||
```
|
||||
|
||||
### Option 3 – The not-so-hard way
|
||||
|
||||
Option 1 does repeated work, option 2 requires manipulating `AST`'s...
|
||||
|
||||
This option makes the scripted [functions] (not [imported][`import`] [modules] nor [constants]
|
||||
however) available globally by first making it a [module] (via [`Module::eval_ast_as_new`](modules/ast.md))
|
||||
and then loading it into the [`Engine`] via `Engine::register_global_module`.
|
||||
|
||||
> **Pros:** No need to recompile the header template!
|
||||
>
|
||||
> **Cons:** No [imported][`import`] [modules] nor [constants]; if the header template is changed, a new [`Engine`] must be created.
|
||||
|
||||
```rust
|
||||
let header_template = "..... // scripts... .....";
|
||||
|
||||
let template_ast = engine.compile(header_template)?;
|
||||
|
||||
let template_module = Module::eval_ast_as_new(Scope::new(), &template_ast, &engine)?;
|
||||
|
||||
engine.register_global_module(template_module.into());
|
||||
|
||||
for index in 0..10000 {
|
||||
let user_script = db.get_script(index);
|
||||
|
||||
// Run away!
|
||||
let result = engine.eval::<i64>(user_script)?;
|
||||
|
||||
println!("{result}");
|
||||
}
|
||||
```
|
@@ -1,97 +0,0 @@
|
||||
Custom Operators
|
||||
================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish info.side "See also"
|
||||
|
||||
See [this section][precedence] for details on operator [precedence].
|
||||
```
|
||||
|
||||
For use as a DSL (Domain-Specific Languages), it is sometimes more convenient to augment Rhai with
|
||||
customized operators performing specific logic.
|
||||
|
||||
`Engine::register_custom_operator` registers a [keyword] as a custom operator, giving it a particular
|
||||
_[precedence]_ (which cannot be zero).
|
||||
|
||||
Support for custom operators can be disabled via the [`no_custom_syntax`] feature.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```rust
|
||||
use rhai::Engine;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register a custom operator '#' and give it a precedence of 160
|
||||
// (i.e. between +|- and *|/)
|
||||
// Also register the implementation of the custom operator as a function
|
||||
engine.register_custom_operator("#", 160)?
|
||||
.register_fn("#", |x: i64, y: i64| (x * y) - (x + y));
|
||||
|
||||
// The custom operator can be used in expressions
|
||||
let result = engine.eval_expression::<i64>("1 + 2 * 3 # 4 - 5 / 6")?;
|
||||
// ^ custom operator
|
||||
|
||||
// The above is equivalent to: 1 + ((2 * 3) # 4) - (5 / 6)
|
||||
result == 15;
|
||||
```
|
||||
|
||||
|
||||
Alternatives to a Custom Operator
|
||||
---------------------------------
|
||||
|
||||
Custom operators are merely _syntactic sugar_. They map directly to registered functions.
|
||||
|
||||
```rust
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Define 'foo' operator
|
||||
engine.register_custom_operator("foo", 160)?;
|
||||
|
||||
engine.eval::<i64>("1 + 2 * 3 foo 4 - 5 / 6")?; // use custom operator
|
||||
|
||||
engine.eval::<i64>("1 + foo(2 * 3, 4) - 5 / 6")?; // <- above is equivalent to this
|
||||
```
|
||||
|
||||
A script using custom operators can always be pre-processed, via a pre-processor application,
|
||||
into a syntax that uses the corresponding function calls.
|
||||
|
||||
Using `Engine::register_custom_operator` merely enables a convenient shortcut.
|
||||
|
||||
|
||||
Must be a Valid Identifier or Reserved Symbol
|
||||
---------------------------------------------
|
||||
|
||||
All custom operators must be _identifiers_ that follow the same naming rules as [variables].
|
||||
|
||||
Alternatively, they can also be [reserved symbols]({{rootUrl}}/appendix/operators.md#symbols),
|
||||
[disabled operators or keywords][disable keywords and operators].
|
||||
|
||||
```rust
|
||||
engine.register_custom_operator("foo", 20)?; // 'foo' is a valid custom operator
|
||||
|
||||
engine.register_custom_operator("#", 20)?; // the reserved symbol '#' is also
|
||||
// a valid custom operator
|
||||
|
||||
engine.register_custom_operator("+", 30)?; // <- error: '+' is an active operator
|
||||
|
||||
engine.register_custom_operator("=>", 30)?; // <- error: '=>' is an active symbol
|
||||
```
|
||||
|
||||
|
||||
Binary Operators Only
|
||||
---------------------
|
||||
|
||||
All custom operators must be _binary_ (i.e. they take two operands).
|
||||
_Unary_ custom operators are not supported.
|
||||
|
||||
```rust
|
||||
// Register unary '#' operator
|
||||
engine.register_custom_operator("#", 160)?
|
||||
.register_fn("#", |x: i64| x * x);
|
||||
|
||||
engine.eval::<i64>("# 42")?; // <- syntax error
|
||||
```
|
@@ -1,246 +0,0 @@
|
||||
Really Advanced – Custom Parsers
|
||||
======================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Sometimes it is desirable to have multiple [custom syntax] starting with the same symbol.
|
||||
|
||||
This is especially common for _command-style_ syntax where the second symbol calls a particular command:
|
||||
|
||||
```rust
|
||||
// The following simulates a command-style syntax, all starting with 'perform'.
|
||||
perform hello world; // A fixed sequence of symbols
|
||||
perform action 42; // Perform a system action with a parameter
|
||||
perform update system; // Update the system
|
||||
perform check all; // Check all system settings
|
||||
perform cleanup; // Clean up the system
|
||||
perform add something; // Add something to the system
|
||||
perform remove something; // Delete something from the system
|
||||
```
|
||||
|
||||
Alternatively, a [custom syntax] may have variable length, with a termination symbol:
|
||||
|
||||
```rust
|
||||
// The following is a variable-length list terminated by '>'
|
||||
tags < "foo", "bar", 123, ... , x+y, true >
|
||||
```
|
||||
|
||||
For even more flexibility in order to handle these advanced use cases, there is a
|
||||
_low level_ API for [custom syntax] that allows the registration of an entire mini-parser.
|
||||
|
||||
Use `Engine::register_custom_syntax_with_state_raw` to register a [custom syntax] _parser_ together
|
||||
with an implementation function, both of which accept a custom user-defined _state_ value.
|
||||
|
||||
|
||||
How Custom Parsers Work
|
||||
-----------------------
|
||||
|
||||
### Leading Symbol
|
||||
|
||||
Under this API, the leading symbol for a custom parser is no longer restricted to be valid identifiers.
|
||||
|
||||
It can either be:
|
||||
|
||||
* an identifier that isn't a normal [keyword] unless [disabled][disable keywords and operators], or
|
||||
|
||||
* a valid symbol (see [list]({{rootUrl}}/appendix/operators.md)) which is not a normal [operator] unless [disabled][disable keywords and operators].
|
||||
|
||||
### Parser Function Signature
|
||||
|
||||
The [custom syntax] parser has the following signature.
|
||||
|
||||
> ```rust
|
||||
> Fn(symbols: &[ImmutableString], look_ahead: &str, state: &mut Dynamic) -> Result<Option<ImmutableString>, ParseError>
|
||||
> ```
|
||||
|
||||
where:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| ------------ | :---------------------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `symbols` | [`&[ImmutableString]`][`ImmutableString`] | a slice of symbols that have been parsed so far, possibly containing `$expr$` and/or `$block$`; `$ident$` and other literal markers are replaced by the actual text |
|
||||
| `look_ahead` | `&str` | a string slice containing the next symbol that is about to be read |
|
||||
| `state` | [`&mut Dynamic`][`Dynamic`] | mutable reference to a user-defined _state_ |
|
||||
|
||||
Most strings are [`ImmutableString`]'s so it is usually more efficient to just `clone` the appropriate one
|
||||
(if any matches, or keep an internal cache for commonly-used symbols) as the return value.
|
||||
|
||||
### Parameter #1 – Symbols Parsed So Far
|
||||
|
||||
The symbols parsed so far are provided as a slice of [`ImmutableString`]s.
|
||||
|
||||
The custom parser can inspect this symbols stream to determine the next symbol to parse.
|
||||
|
||||
| Argument type | Value |
|
||||
| :-----------: | ----------------- |
|
||||
| text [string] | text value |
|
||||
| `$ident$` | identifier name |
|
||||
| `$symbol$` | symbol literal |
|
||||
| `$expr$` | `$expr$` |
|
||||
| `$block$` | `$block$` |
|
||||
| `$func$` | `$func$` |
|
||||
| `$bool$` | `true` or `false` |
|
||||
| `$int$` | value of number |
|
||||
| `$float$` | value of number |
|
||||
| `$string$` | [string] text |
|
||||
|
||||
### Parameter #2 – Look-Ahead Symbol
|
||||
|
||||
The _look-ahead_ symbol is the symbol that will be parsed _next_.
|
||||
|
||||
If the look-ahead is an expected symbol, the customer parser just returns it to continue parsing,
|
||||
or it can return `$ident$` to parse it as an identifier, or even `$expr$` to start parsing
|
||||
an expression.
|
||||
|
||||
```admonish tip.side.wide "Tip: Strings vs identifiers"
|
||||
|
||||
The look-ahead of an identifier (e.g. [variable] name) is its text name.
|
||||
|
||||
That of a [string] literal is its content wrapped in _quotes_ (`"`), e.g. `"this is a string"`.
|
||||
```
|
||||
|
||||
If the look-ahead is `{`, then the custom parser may also return `$block$` to start parsing a
|
||||
statements block.
|
||||
|
||||
If the look-ahead is unexpected, the custom parser should then return the symbol expected
|
||||
and Rhai will fail with a parse error containing information about the expected symbol.
|
||||
|
||||
### Parameter #3 – User-Defined Custom _State_
|
||||
|
||||
The _state's_ value starts off as [`()`].
|
||||
|
||||
Its type is [`Dynamic`], possible to hold any value.
|
||||
|
||||
Usually it is set to an [object map] that contains information on the state of parsing.
|
||||
|
||||
### Return value
|
||||
|
||||
The return value is `Result<Option<ImmutableString>, ParseError>` where:
|
||||
|
||||
| Value | Description |
|
||||
| :----------------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `Ok(None)` | parsing is complete and there is no more symbol to match |
|
||||
| `Ok(Some(symbol))` | the next `symbol` to match, which can also be `$expr$`, `$ident$`, `$block$` etc. |
|
||||
| `Err(error)` | `error` that is reflected back to the [`Engine`] – normally `ParseError( ParseErrorType::BadInput( LexError::ImproperSymbol(message) ), Position::NONE)` to indicate that there is a syntax error, but it can be any `ParseError`. |
|
||||
|
||||
A custom parser always returns `Some` with the _next_ symbol expected (which can be `$ident$`,
|
||||
`$expr$`, `$block$` etc.) or `None` if parsing should terminate (_without_ reading the
|
||||
look-ahead symbol).
|
||||
|
||||
#### The `$$` return symbol short-cut
|
||||
|
||||
A return symbol starting with `$$` is treated specially.
|
||||
|
||||
Like `None`, it also terminates parsing, but at the same time it adds this symbol as text into the
|
||||
_inputs_ stream at the end.
|
||||
|
||||
This is typically used to inform the implementation function which [custom syntax] variant was
|
||||
actually parsed.
|
||||
|
||||
```rust
|
||||
fn implementation_fn(context: &mut EvalContext, inputs: &[Expression], state: &Dynamic) -> Result<Dynamic, Box<EvalAltResult>>
|
||||
{
|
||||
// Get the last symbol
|
||||
let key = inputs.last().unwrap().get_string_value().unwrap();
|
||||
|
||||
// Make sure it starts with '$$'
|
||||
assert!(key.starts_with("$$"));
|
||||
|
||||
// Execute the custom syntax expression
|
||||
match key {
|
||||
"$$hello" => { ... }
|
||||
"$$world" => { ... }
|
||||
"$$foo" => { ... }
|
||||
"$$bar" => { ... }
|
||||
_ => Err(...)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`$$` is a convenient _short-cut_. An alternative method is to pass such information in the user-defined
|
||||
custom _state_.
|
||||
|
||||
### Implementation Function Signature
|
||||
|
||||
The signature of an implementation function for `Engine::register_custom_syntax_with_state_raw` is
|
||||
as follows, which is slightly different from the function for `Engine::register_custom_syntax`.
|
||||
|
||||
> ```rust
|
||||
> Fn(context: &mut EvalContext, inputs: &[Expression], state: &Dynamic) -> Result<Dynamic, Box<EvalAltResult>>
|
||||
> ```
|
||||
|
||||
where:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | :---------------------------------: | ----------------------------------------------------- |
|
||||
| `context` | [`&mut EvalContext`][`EvalContext`] | mutable reference to the current _evaluation context_ |
|
||||
| `inputs` | `&[Expression]` | a list of input expression trees |
|
||||
| `state` | [`&Dynamic`][`Dynamic`] | reference to the user-defined state |
|
||||
|
||||
|
||||
Custom Parser Example
|
||||
---------------------
|
||||
|
||||
```rust
|
||||
engine.register_custom_syntax_with_state_raw(
|
||||
// The leading symbol - which needs not be an identifier.
|
||||
"perform",
|
||||
// The custom parser implementation - always returns the next symbol expected
|
||||
// 'look_ahead' is the next symbol about to be read
|
||||
//
|
||||
// Return symbols starting with '$$' also terminate parsing but allows us
|
||||
// to determine which syntax variant was actually parsed so we can perform the
|
||||
// appropriate action. This is a convenient short-cut to keeping the value
|
||||
// inside the state.
|
||||
//
|
||||
// The return type is 'Option<ImmutableString>' to allow common text strings
|
||||
// to be interned and shared easily, reducing allocations during parsing.
|
||||
|symbols, look_ahead, state| match symbols.len() {
|
||||
// perform ...
|
||||
1 => Ok(Some("$ident$".into())),
|
||||
// perform command ...
|
||||
2 => match symbols[1].as_str() {
|
||||
"action" => Ok(Some("$expr$".into())),
|
||||
"hello" => Ok(Some("world".into())),
|
||||
"update" | "check" | "add" | "remove" => Ok(Some("$ident$".into())),
|
||||
"cleanup" => Ok(Some("$$cleanup".into())),
|
||||
cmd => Err(LexError::ImproperSymbol(format!("Improper command: {cmd}"))
|
||||
.into_err(Position::NONE)),
|
||||
},
|
||||
// perform command arg ...
|
||||
3 => match (symbols[1].as_str(), symbols[2].as_str()) {
|
||||
("action", _) => Ok(Some("$$action".into())),
|
||||
("hello", "world") => Ok(Some("$$hello-world".into())),
|
||||
("update", arg) => match arg {
|
||||
"system" => Ok(Some("$$update-system".into())),
|
||||
"client" => Ok(Some("$$update-client".into())),
|
||||
_ => Err(LexError::ImproperSymbol(format!("Cannot update {arg}"))
|
||||
.into_err(Position::NONE))
|
||||
},
|
||||
("check", arg) => Ok(Some("$$check".into())),
|
||||
("add", arg) => Ok(Some("$$add".into())),
|
||||
("remove", arg) => Ok(Some("$$remove".into())),
|
||||
(cmd, arg) => Err(LexError::ImproperSymbol(
|
||||
format!("Invalid argument for command {cmd}: {arg}")
|
||||
).into_err(Position::NONE)),
|
||||
},
|
||||
_ => unreachable!(),
|
||||
},
|
||||
// No variables declared/removed by this custom syntax
|
||||
false,
|
||||
// Implementation function
|
||||
|context, inputs, state| {
|
||||
let cmd = inputs.last().unwrap().get_string_value().unwrap();
|
||||
|
||||
match cmd {
|
||||
"$$cleanup" => { ... }
|
||||
"$$action" => { ... }
|
||||
"$$update-system" => { ... }
|
||||
"$$update-client" => { ... }
|
||||
"$$check" => { ... }
|
||||
"$$add" => { ... }
|
||||
"$$remove" => { ... }
|
||||
_ => Err(format!("Invalid command: {cmd}"))
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
@@ -1,491 +0,0 @@
|
||||
Extend Rhai with Custom Syntax
|
||||
==============================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
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:
|
||||
|
||||
```admonish danger.small "Don't Do It™"
|
||||
|
||||
Stick with standard language syntax as much as possible.
|
||||
|
||||
Having to learn Rhai is bad enough, no sane user would ever want to learn _yet_ another obscure
|
||||
language syntax just to do something.
|
||||
|
||||
Try [custom operators] first. A custom syntax should be considered a _last resort_.
|
||||
```
|
||||
|
||||
```admonish success.small "Where this might be useful"
|
||||
|
||||
* Where an operation is used a _LOT_ and a custom syntax saves a lot of typing.
|
||||
|
||||
* 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.
|
||||
|
||||
* Where you just want to confuse your user and make their lives miserable, because you can.
|
||||
```
|
||||
|
||||
```admonish tip.small "Disable custom syntax"
|
||||
|
||||
Custom syntax can be disabled via the [`no_custom_syntax`] feature.
|
||||
```
|
||||
|
||||
|
||||
How to Do It
|
||||
------------
|
||||
|
||||
### Step One – Design The Syntax
|
||||
|
||||
A custom syntax is simply a list of symbols.
|
||||
|
||||
These symbol types can be used:
|
||||
|
||||
* Standard [keywords]
|
||||
* Standard [operators]
|
||||
* Reserved [symbols]({{rootUrl}}/appendix/operators.md#symbols).
|
||||
* Identifiers following the [variable] naming rules.
|
||||
* `$expr$` – any valid expression, statement or statements block.
|
||||
* `$block$` – any valid statements block (i.e. must be enclosed by `{` ... `}`).
|
||||
* `$func$` – any valid [closure], or any valid statements block as the body of a [closure] with no parameters (if not [`no_function`]).
|
||||
* `$ident$` – any [variable] name.
|
||||
* `$symbol$` – any [symbol][operator], active or reserved.
|
||||
* `$bool$` – a boolean value.
|
||||
* `$int$` – an integer number.
|
||||
* `$float$` – a floating-point number (if not [`no_float`]).
|
||||
* `$string$` – a [string] literal.
|
||||
|
||||
#### The first symbol must be an identifier
|
||||
|
||||
There is no specific limit on the combination and sequencing of each symbol type,
|
||||
except the _first_ symbol which must be a custom [keyword] that follows the naming rules
|
||||
of [variables].
|
||||
|
||||
The first symbol also cannot be a normal [keyword] unless it is [disabled][disable keywords and operators].
|
||||
Any valid identifier that is not an active [keyword] works fine, even if it is a reserved [keyword].
|
||||
|
||||
#### The first symbol must be unique
|
||||
|
||||
Rhai uses the _first_ symbol as a clue to parse custom syntax.
|
||||
|
||||
Therefore, at any one time, there can only be _one_ custom syntax starting with each unique symbol.
|
||||
|
||||
Any new custom syntax definition using the same first symbol simply _overwrites_ the previous one.
|
||||
|
||||
#### Example
|
||||
|
||||
```rust
|
||||
exec [ $ident$ $symbol$ $int$ ] <- $expr$ : $block$
|
||||
```
|
||||
|
||||
The above syntax is made up of a stream of symbols:
|
||||
|
||||
| Position | Input slot | Symbol | Description |
|
||||
| :------: | :--------: | :--------: | -------------------------------------------------------------------------------------------------------- |
|
||||
| 1 | | `exec` | custom keyword |
|
||||
| 2 | | `[` | the left bracket symbol |
|
||||
| 2 | 0 | `$ident$` | a [variable] name |
|
||||
| 3 | 1 | `$symbol$` | the operator |
|
||||
| 4 | 2 | `$int$` | an integer number |
|
||||
| 5 | | `]` | the right bracket symbol |
|
||||
| 6 | | `<-` | the left-arrow symbol (which is a [reserved symbol]({{rootUrl}}/appendix/operators.md#symbols) in Rhai). |
|
||||
| 7 | 3 | `$expr$` | an expression, which may be enclosed with `{` ... `}`, or not. |
|
||||
| 8 | | `:` | the colon symbol |
|
||||
| 9 | 4 | `$block$` | a statements block, which must be enclosed with `{` ... `}`. |
|
||||
|
||||
This syntax matches the following sample code and generates five inputs (one for each non-keyword):
|
||||
|
||||
```rust
|
||||
// Assuming the 'exec' custom syntax implementation declares the variable 'hello':
|
||||
let x = exec [hello < 42] <- foo(1, 2) : {
|
||||
hello += bar(hello);
|
||||
baz(hello);
|
||||
};
|
||||
|
||||
print(x); // variable 'x' has a value returned by the custom syntax
|
||||
|
||||
print(hello); // variable declared by a custom syntax persists!
|
||||
```
|
||||
|
||||
|
||||
### Step Two – Implementation
|
||||
|
||||
Any custom syntax must include an _implementation_ of it.
|
||||
|
||||
#### Function signature
|
||||
|
||||
The signature of an implementation function is as follows.
|
||||
|
||||
> ```rust
|
||||
> Fn(context: &mut EvalContext, inputs: &[Expression]) -> Result<Dynamic, Box<EvalAltResult>>
|
||||
> ```
|
||||
|
||||
where:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | :---------------------------------: | ----------------------------------------------------- |
|
||||
| `context` | [`&mut EvalContext`][`EvalContext`] | mutable reference to the current _evaluation context_ |
|
||||
| `inputs` | `&[Expression]` | a list of input expression trees |
|
||||
|
||||
and [`EvalContext`] is a type that encapsulates the current _evaluation context_.
|
||||
|
||||
#### Return value
|
||||
|
||||
Return value is the result of evaluating the custom syntax expression.
|
||||
|
||||
#### Access arguments
|
||||
|
||||
The most important argument is `inputs` where the matched identifiers (`$ident$`), expressions/statements (`$expr$`)
|
||||
and statements blocks (`$block$`) are provided.
|
||||
|
||||
To access a particular argument, use the following patterns:
|
||||
|
||||
| Argument type | Pattern (`n` = slot in `inputs`) | Result type | Description |
|
||||
| :-----------: | ------------------------------------------------------------------------------------------------------------ | :---------------------------------: | --------------------------------------------------- |
|
||||
| `$ident$` | `inputs[n].get_string_value().unwrap()` | `&str` | [variable] name |
|
||||
| `$symbol$` | `inputs[n].get_literal_value::<ImmutableString>().unwrap()` | [`ImmutableString`] | symbol literal |
|
||||
| `$expr$` | `&inputs[n]` | `&Expression` | an expression tree |
|
||||
| `$block$` | `&inputs[n]` | `&Expression` | an expression tree |
|
||||
| `$func$` | `&inputs[n]` | `&Expression` | an expression tree (output is a [function pointer]) |
|
||||
| `$bool$` | `inputs[n].get_literal_value::<bool>().unwrap()` | `bool` | boolean value |
|
||||
| `$int$` | `inputs[n].get_literal_value::<INT>().unwrap()` | `INT` | integer number |
|
||||
| `$float$` | `inputs[n].get_literal_value::<FLOAT>().unwrap()` | `FLOAT` | floating-point number |
|
||||
| `$string$` | `inputs[n].get_literal_value::<ImmutableString>().unwrap()`<br/><br/>`inputs[n].get_string_value().unwrap()` | [`ImmutableString`]<br/><br/>`&str` | [string] text |
|
||||
|
||||
#### Get literal constants
|
||||
|
||||
Several argument types represent literal constants that can be obtained directly via
|
||||
`Expression::get_literal_value<T>` or `Expression::get_string_value` (for [strings]).
|
||||
|
||||
```rust
|
||||
let expression = &inputs[0];
|
||||
|
||||
// Use 'get_literal_value' with a turbo-fish type to extract the value
|
||||
let string_value = expression.get_literal_value::<ImmutableString>().unwrap();
|
||||
let string_slice = expression.get_string_value().unwrap();
|
||||
|
||||
let float_value = expression.get_literal_value::<FLOAT>().unwrap();
|
||||
|
||||
// Or assign directly to a variable with type...
|
||||
let int_value: i64 = expression.get_literal_value().unwrap();
|
||||
|
||||
// Or use type inference!
|
||||
let bool_value = expression.get_literal_value().unwrap();
|
||||
|
||||
if bool_value { ... } // 'bool_value' inferred to be 'bool'
|
||||
```
|
||||
|
||||
#### Evaluate an expression tree
|
||||
|
||||
Use the `EvalContext::eval_expression_tree` method to evaluate an arbitrary expression tree
|
||||
within the current evaluation context.
|
||||
|
||||
```rust
|
||||
let expression = &inputs[0];
|
||||
let result = context.eval_expression_tree(expression)?;
|
||||
```
|
||||
|
||||
#### Retain variables in block scope
|
||||
|
||||
When an expression tree actually contains a statements block (i.e. `$block`), local
|
||||
[variables]/[constants] defined within that block are usually removed at the end of the block.
|
||||
|
||||
Sometimes it is useful to retain these local [variables]/[constants] for further processing
|
||||
(e.g. collecting new [variables] into an [object map]).
|
||||
|
||||
As such, evaluate the expression tree using the `EvalContext::eval_expression_tree_raw` method which
|
||||
contains a parameter to control whether the statements block should be rewound.
|
||||
|
||||
```rust
|
||||
// Assume 'expression' contains a statements block with local variable definitions
|
||||
let expression = &inputs[0];
|
||||
let result = context.eval_expression_tree_raw(expression, false)?;
|
||||
|
||||
// Variables defined within 'expression' persist in context.scope()
|
||||
```
|
||||
|
||||
#### Declare variables
|
||||
|
||||
New [variables]/[constants] maybe declared (usually with a [variable] name that is passed in via `$ident$`).
|
||||
|
||||
It can simply be pushed into the [`Scope`].
|
||||
|
||||
```rust
|
||||
let var_name = inputs[0].get_string_value().unwrap();
|
||||
let expression = &inputs[1];
|
||||
|
||||
context.scope_mut().push(var_name, 0_i64); // declare new variable
|
||||
|
||||
let result = context.eval_expression_tree(expression)?;
|
||||
```
|
||||
|
||||
|
||||
### Step Three – Register the Custom Syntax
|
||||
|
||||
Use `Engine::register_custom_syntax` to register a custom syntax.
|
||||
|
||||
Again, beware that the _first_ symbol must be unique. If there already exists a custom syntax starting
|
||||
with that symbol, the previous syntax will be overwritten.
|
||||
|
||||
The syntax is passed simply as a slice of `&str`.
|
||||
|
||||
```rust
|
||||
// Custom syntax implementation
|
||||
fn implementation_func(context: &mut EvalContext, inputs: &[Expression]) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
let var_name = inputs[0].get_string_value().unwrap();
|
||||
let stmt = &inputs[1];
|
||||
let condition = &inputs[2];
|
||||
|
||||
// Push new variable into the scope BEFORE 'context.eval_expression_tree'
|
||||
context.scope_mut().push(var_name.to_string(), 0_i64);
|
||||
|
||||
let mut count = 0_i64;
|
||||
|
||||
loop {
|
||||
// Evaluate the statements block
|
||||
context.eval_expression_tree(stmt)?;
|
||||
|
||||
count += 1;
|
||||
|
||||
// Declare a new variable every three turns...
|
||||
if count % 3 == 0 {
|
||||
context.scope_mut().push(format!("{var_name}{count}"), count);
|
||||
}
|
||||
|
||||
// Evaluate the condition expression
|
||||
let expr_result = !context.eval_expression_tree(condition)?;
|
||||
|
||||
match expr_result.as_bool() {
|
||||
Ok(true) => (),
|
||||
Ok(false) => break,
|
||||
Err(err) => return Err(EvalAltResult::ErrorMismatchDataType(
|
||||
"bool".to_string(),
|
||||
err.to_string(),
|
||||
condition.position(),
|
||||
).into()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Dynamic::UNIT)
|
||||
}
|
||||
|
||||
// Register the custom syntax (sample): exec<x> -> { x += 1 } while x < 0
|
||||
engine.register_custom_syntax(
|
||||
[ "exec", "<", "$ident$", ">", "->", "$block$", "while", "$expr$" ], // the custom syntax
|
||||
true, // variables declared within this custom syntax
|
||||
implementation_func
|
||||
)?;
|
||||
```
|
||||
|
||||
Remember that a custom syntax acts as an _expression_, so it can show up practically anywhere:
|
||||
|
||||
```rust
|
||||
// Use as an expression:
|
||||
let foo = (exec<x> -> { x += 1 } while x < 42) * 100;
|
||||
|
||||
// New variables are successfully declared...
|
||||
x == 42;
|
||||
x3 == 3;
|
||||
x6 == 6;
|
||||
|
||||
// Use as a function call argument:
|
||||
do_something(exec<x> -> { x += 1 } while x < 42, 24, true);
|
||||
|
||||
// Use as a statement:
|
||||
exec<x> -> { x += 1 } while x < 0;
|
||||
// ^ terminate statement with ';' unless the custom
|
||||
// syntax already ends with '}'
|
||||
```
|
||||
|
||||
|
||||
### Step Four – Disable Unneeded Statement Types
|
||||
|
||||
When a DSL needs a custom syntax, most likely than not it is extremely specialized.
|
||||
Therefore, many statement types actually may not make sense under the same usage scenario.
|
||||
|
||||
So, while at it, better [disable][disable keywords and operators] those built-in keywords
|
||||
and [operators] that should not be used by the user. The would leave only the bare minimum
|
||||
language surface exposed, together with the custom syntax that is tailor-designed for
|
||||
the scenario.
|
||||
|
||||
A [keyword] or [operator] that is disabled can still be used in a custom syntax.
|
||||
|
||||
In an extreme case, it is possible to disable _every_ [keyword] in the language, leaving only
|
||||
custom syntax (plus possibly expressions). But again, Don't Do It™ – unless you are certain
|
||||
of what you're doing.
|
||||
|
||||
|
||||
### Step Five – Document
|
||||
|
||||
For custom syntax, documentation is crucial.
|
||||
|
||||
Make sure there are _lots_ of examples for users to follow.
|
||||
|
||||
|
||||
### Step Six – Profit!
|
||||
|
||||
|
||||
Practical Example – Matrix Literal
|
||||
----------------------------------------
|
||||
|
||||
Say you'd want to use something like [`ndarray`](https://crates.io/crates/ndarray) to manipulate matrices.
|
||||
|
||||
However, you'd like to write matrix literals in a more intuitive syntax than an [array]
|
||||
of [arrays].
|
||||
|
||||
In other words, you'd like to turn:
|
||||
|
||||
```rust
|
||||
// Array of arrays
|
||||
let matrix = [ [ a, b, 0 ],
|
||||
[ -b, a, 0 ],
|
||||
[ 0, 0, c * d ] ];
|
||||
```
|
||||
|
||||
into:
|
||||
|
||||
```rust
|
||||
// Directly parse to an ndarray::Array (look ma, no commas!)
|
||||
let matrix = @| a b 0 |
|
||||
| -b a 0 |
|
||||
| 0 0 c*d |;
|
||||
```
|
||||
|
||||
This can easily be done via a custom syntax, which yields a syntax that is more pleasing.
|
||||
|
||||
```rust
|
||||
// Disable the '|' symbol since it'll conflict with the bit-wise OR operator.
|
||||
// Do this BEFORE registering the custom syntax.
|
||||
engine.disable_symbol("|");
|
||||
|
||||
engine.register_custom_syntax(
|
||||
["@", "|", "$expr$", "$expr$", "$expr$", "|",
|
||||
"|", "$expr$", "$expr$", "$expr$", "|",
|
||||
"|", "$expr$", "$expr$", "$expr$", "|"
|
||||
],
|
||||
false,
|
||||
|context, inputs| {
|
||||
use ndarray::arr2;
|
||||
|
||||
let mut values = [[0.0; 3]; 3];
|
||||
|
||||
for y in 0..3 {
|
||||
for x in 0..3 {
|
||||
let offset = y * 3 + x;
|
||||
|
||||
match context.eval_expression_tree(&inputs[offset])?.as_float() {
|
||||
Ok(v) => values[y][x] = v,
|
||||
Err(typ) => return Err(Box::new(EvalAltResult::ErrorMismatchDataType(
|
||||
"float".to_string(), typ.to_string(),
|
||||
inputs[offset].position()
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let matrix = arr2(&values);
|
||||
|
||||
Ok(Dynamic::from(matrix))
|
||||
},
|
||||
)?;
|
||||
```
|
||||
|
||||
For matrices of flexible dimensions, check out [custom syntax parsers](custom-syntax-parsers.md).
|
||||
|
||||
|
||||
Practical Example – Defining Temporary Variables
|
||||
------------------------------------------------------
|
||||
|
||||
It is possible to define temporary [variables]/[constants] which are available only to code blocks
|
||||
within the custom syntax.
|
||||
|
||||
```rust
|
||||
engine.register_custom_syntax(
|
||||
[ "with", "offset", "(", "$expr$", ",", "$expr$", ")", "$block$", ],
|
||||
true, // must be true in order to define new variables
|
||||
|context, inputs| {
|
||||
// Get the two offsets
|
||||
let x = context.eval_expression_tree(&inputs[0])?.as_int().map_err(|typ| Box::new(
|
||||
EvalAltResult::ErrorMismatchDataType("integer".to_string(), typ.to_string(), inputs[0].position())
|
||||
))?;
|
||||
let y = context.eval_expression_tree(&inputs[1])?.as_int().map_err(|typ| Box::new(
|
||||
EvalAltResult::ErrorMismatchDataType("integer".to_string(), typ.to_string(), inputs[1].position())
|
||||
))?;
|
||||
|
||||
// Add them as temporary constants into the scope, available only to the code block
|
||||
let orig_len = context.scope().len();
|
||||
|
||||
context.scope_mut().push_constant("x", x);
|
||||
context.scope_mut().push_constant("y", y);
|
||||
|
||||
// Run the code block
|
||||
let result = context.eval_expression_tree(&inputs[2]);
|
||||
|
||||
// Remove the temporary constants from the scope so they don't leak outside
|
||||
context.scope_mut().rewind(orig_len);
|
||||
|
||||
// Return the result
|
||||
result
|
||||
},
|
||||
)?;
|
||||
```
|
||||
|
||||
Practical Example – Recreating C's Ternary Operator
|
||||
---------------------------------------------------------
|
||||
|
||||
Rhai has [if-expressions](../language/if.md#if-expression), but sometimes a C-style _ternary_ operator
|
||||
is more concise.
|
||||
|
||||
```rust
|
||||
// A custom syntax must start with a unique symbol, so we use 'iff'.
|
||||
// Register the custom syntax: iff condition ? true-value : false-value
|
||||
engine.register_custom_syntax(
|
||||
["iff", "$expr$", "?", "$expr$", ":", "$expr$"],
|
||||
false,
|
||||
|context, inputs| match context.eval_expression_tree(&inputs[0])?.as_bool() {
|
||||
Ok(true) => context.eval_expression_tree(&inputs[1]),
|
||||
Ok(false) => context.eval_expression_tree(&inputs[2]),
|
||||
Err(typ) => Err(Box::new(EvalAltResult::ErrorMismatchDataType(
|
||||
"bool".to_string(), typ.to_string(), inputs[0].position()
|
||||
))),
|
||||
},
|
||||
)?;
|
||||
```
|
||||
|
||||
```admonish tip.small "Tip: Custom syntax performance"
|
||||
|
||||
The code in the example above is essentially what the [`if`] statement does internally, and since
|
||||
custom syntax is pre-parsed, there really is no performance penalty!
|
||||
```
|
||||
|
||||
|
||||
Practical Example – Recreating JavaScript's `var` Statement
|
||||
-----------------------------------------------------------------
|
||||
|
||||
The following example recreates a statement similar to the `var` variable declaration syntax in
|
||||
JavaScript, which creates a global variable if one doesn't already exist.
|
||||
There is currently no equivalent in Rhai.
|
||||
|
||||
```rust
|
||||
// Register the custom syntax: var x = ???
|
||||
engine.register_custom_syntax([ "var", "$ident$", "=", "$expr$" ], true, |context, inputs| {
|
||||
let var_name = inputs[0].get_string_value().unwrap().to_string();
|
||||
let expr = &inputs[1];
|
||||
|
||||
// Evaluate the expression
|
||||
let value = context.eval_expression_tree(expr)?;
|
||||
|
||||
// Push a new variable into the scope if it doesn't already exist.
|
||||
// Otherwise just set its value.
|
||||
if !context.scope().is_constant(var_name).unwrap_or(false) {
|
||||
context.scope_mut().set_value(var_name.to_string(), value);
|
||||
Ok(Dynamic::UNIT)
|
||||
} else {
|
||||
Err(format!("variable {} is constant", var_name).into())
|
||||
}
|
||||
})?;
|
||||
```
|
@@ -1,54 +0,0 @@
|
||||
Break-Points
|
||||
============
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
A _break-point_ **always** stops the current evaluation and calls the [debugging][debugger]
|
||||
callback.
|
||||
|
||||
A break-point is represented by the `debugger::BreakPoint` type, which is an `enum` with
|
||||
the following variants.
|
||||
|
||||
| `BreakPoint` variant | Not available under | Description |
|
||||
| ---------------------------------------- | :-----------------: | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `AtPosition { source, pos, enabled }` | [`no_position`] | breaks at the specified position in the specified source (empty if none);<br/>if `pos` is at beginning of line, breaks anywhere on the line |
|
||||
| `AtFunctionName { name, enabled }` | | breaks when a function matching the specified name is called (can be [operator]) |
|
||||
| `AtFunctionCall { name, args, enabled }` | | breaks when a function matching the specified name (can be [operator]) and the specified number of arguments is called |
|
||||
| `AtProperty { name, enabled }` | [`no_object`] | breaks at the specified property access |
|
||||
|
||||
|
||||
Access Break-Points
|
||||
-------------------
|
||||
|
||||
The following [`debugger::Debugger`] methods allow access to break-points for manipulation.
|
||||
|
||||
| Method | Return type | Description |
|
||||
| ------------------ | :--------------------: | ------------------------------------------------- |
|
||||
| `break_points` | `&[BreakPoint]` | returns a slice of all `BreakPoint`'s |
|
||||
| `break_points_mut` | `&mut Vec<BreakPoint>` | returns a mutable reference to all `BreakPoint`'s |
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```rust
|
||||
use rhai::debugger::*;
|
||||
|
||||
let debugger = &mut context.global_runtime_state_mut().debugger_mut();
|
||||
|
||||
// Get number of break-points.
|
||||
let num_break_points = debugger.break_points().len();
|
||||
|
||||
// Add a new break-point on calls to 'foo(_, _, _)'
|
||||
debugger.break_points_mut().push(
|
||||
BreakPoint::AtFunctionCall { name: "foo".into(), args: 3 }
|
||||
);
|
||||
|
||||
// Display all break-points
|
||||
for bp in debugger.break_points().iter() {
|
||||
println!("{bp}");
|
||||
}
|
||||
|
||||
// Clear all break-points
|
||||
debugger.break_points_mut().clear();
|
||||
```
|
@@ -1,32 +0,0 @@
|
||||
Call Stack
|
||||
==========
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
```admonish info.side.wide "Call stack frames"
|
||||
|
||||
Each "frame" in the call stack corresponds to one layer of [function] call (script-defined
|
||||
or native Rust).
|
||||
|
||||
A call stack frame has the type `debugger::CallStackFrame`.
|
||||
```
|
||||
|
||||
The [debugger] keeps a _call stack_ of [function] calls with argument values.
|
||||
|
||||
This call stack can be examined to determine the control flow at any particular point.
|
||||
|
||||
The `Debugger::call_stack` method returns a slice of all call stack frames.
|
||||
|
||||
```rust
|
||||
use rhai::debugger::*;
|
||||
|
||||
let debugger = &mut context.global_runtime_state().debugger();
|
||||
|
||||
// Get depth of the call stack.
|
||||
let depth = debugger.call_stack().len();
|
||||
|
||||
// Display all function calls
|
||||
for frame in debugger.call_stack().iter() {
|
||||
println!("{frame}");
|
||||
}
|
||||
```
|
@@ -1,110 +0,0 @@
|
||||
Register with the Debugger
|
||||
==========================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
Hooking up a debugging interface is as simple as providing closures to the [`Engine`]'s built-in
|
||||
debugger via `Engine::register_debugger`.
|
||||
|
||||
```rust
|
||||
use rhai::debugger::{ASTNode, DebuggerCommand};
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.register_debugger(
|
||||
// Provide a callback to initialize the debugger state
|
||||
|engine, mut debugger| {
|
||||
debugger.set_state(...);
|
||||
debugger
|
||||
},
|
||||
// Provide a callback for each debugging step
|
||||
|context, event, node, source, pos| {
|
||||
...
|
||||
|
||||
DebuggerCommand::StepOver
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
~~~admonish tip.small "Tip: Accessing the `Debugger`"
|
||||
|
||||
The type `debugger::Debugger` allows for manipulating [break-points], among others.
|
||||
|
||||
The [`Engine`]'s debugger instance can be accessed via `context.global_runtime_state().debugger()` (immutable)
|
||||
or `context.global_runtime_state_mut().debugger_mut()` (mutable).
|
||||
~~~
|
||||
|
||||
|
||||
Callback Functions Signature
|
||||
----------------------------
|
||||
|
||||
There are two callback functions to register for the debugger.
|
||||
|
||||
The first is simply a function to initialize the state of the debugger with the following signature.
|
||||
|
||||
> ```rust
|
||||
> Fn(&Engine, debugger::Debugger) -> debugger::Debugger
|
||||
> ```
|
||||
|
||||
The second callback is a function which will be called by the debugger during each step, with the
|
||||
following signature.
|
||||
|
||||
> ```rust
|
||||
> Fn(context: EvalContext, event: debugger::DebuggerEvent, node: ASTNode, source: &str, pos: Position) -> Result<debugger::DebuggerCommand, Box<EvalAltResult>>
|
||||
> ```
|
||||
|
||||
where:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | :-------------: | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `context` | [`EvalContext`] | the current _evaluation context_ |
|
||||
| `event` | `DebuggerEvent` | an `enum` indicating the event that triggered the debugger |
|
||||
| `node` | `ASTNode` | an `enum` with two variants: `Expr` or `Stmt`, corresponding to the current expression node or statement node in the [`AST`] |
|
||||
| `source` | `&str` | the source of the current [`AST`], or empty if none |
|
||||
| `pos` | `Position` | position of the current node, same as `node.position()` |
|
||||
|
||||
and [`EvalContext`] is a type that encapsulates the current _evaluation context_.
|
||||
|
||||
### Event
|
||||
|
||||
The `event` parameter of the second closure passed to `Engine::register_debugger` contains a
|
||||
`debugger::DebuggerEvent` which is an `enum` with the following variants.
|
||||
|
||||
| `DebuggerEvent` variant | Description |
|
||||
| -------------------------------- | ----------------------------------------------------------------------------------------------- |
|
||||
| `Start` | the debugger is triggered at the beginning of evaluation |
|
||||
| `Step` | the debugger is triggered at the next step of evaluation |
|
||||
| `BreakPoint(`_n_`)` | the debugger is triggered by the _n_-th [break-point] |
|
||||
| `FunctionExitWithValue(`_r_`)` | the debugger is triggered by a function call returning with value _r_ which is `&Dynamic` |
|
||||
| `FunctionExitWithError(`_err_`)` | the debugger is triggered by a function call exiting with error _err_ which is `&EvalAltResult` |
|
||||
| `End` | the debugger is triggered at the end of evaluation |
|
||||
|
||||
### Return value
|
||||
|
||||
```admonish tip.side.wide "Tip: Initialization"
|
||||
|
||||
When a script starts evaluation, the debugger always stops at the very _first_ [`AST`] node
|
||||
with the `event` parameter set to `DebuggerStatus::Start`.
|
||||
|
||||
This allows initialization to be done (e.g. setting up [break-points]).
|
||||
```
|
||||
|
||||
The second closure passed to `Engine::register_debugger` will be called when stepping into or over
|
||||
expressions and statements, or when [break-points] are hit.
|
||||
|
||||
The return type of the closure is `Result<debugger::DebuggerCommand, Box<EvalAltResult>>`.
|
||||
|
||||
If an error is returned, the script evaluation at that particular instance returns with that
|
||||
particular error. It is thus possible to _abort_ the script evaluation by returning an error that is
|
||||
not _catchable_, such as `EvalAltResult::ErrorTerminated`.
|
||||
|
||||
If no error is returned, then the return `debugger::DebuggerCommand` variant determines the
|
||||
continued behavior of the debugger.
|
||||
|
||||
| `DebuggerCommand` variant | Behavior | `gdb` equivalent |
|
||||
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | :--------------: |
|
||||
| `Continue` | continue with normal script evaluation | `continue` |
|
||||
| `StepInto` | run to the next expression or statement, diving into functions | `step` |
|
||||
| `StepOver` | run to the next expression or statement, skipping over functions | |
|
||||
| `Next` | run to the next statement, skipping over functions | `next` |
|
||||
| `FunctionExit` | run to the end of the current function call; debugger is triggered _before_ the function call returns and the [`Scope`] cleared | `finish` |
|
@@ -1,50 +0,0 @@
|
||||
Debugging Interface
|
||||
===================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
For systems open to external user-created scripts, it is usually desirable to provide a _debugging_
|
||||
experience to the user. The alternative is to provide a custom implementation of [`debug`] via
|
||||
`Engine::on_debug` that traps debug output to show in a side panel, for example, which is actually
|
||||
extremely simple.
|
||||
|
||||
Nevertheless, in some systems, it may not be convenient, or even possible, for the user to debug his
|
||||
or her scripts simply via good-old [`print`] or [`debug`] statements – the system does not
|
||||
have any facility for printed output, for instance.
|
||||
|
||||
Or the system may require more advanced debugging facilities than mere [`print`] statements –
|
||||
such as [break-points].
|
||||
|
||||
For these advanced scenarios, Rhai contains a _Debugging_ interface, turned on via the [`debugging`]
|
||||
feature (which implies the [`internals`] feature).
|
||||
|
||||
The debugging interface resides under the `debugger` sub-module.
|
||||
|
||||
|
||||
```admonish tip.small "The Rhai Debugger"
|
||||
|
||||
The [`rhai-dbg`]({{repoHome}}/src/bin/rhai-dbg.rs) bin tool shows a simple example of
|
||||
employing the debugging interface to create a debugger for Rhai scripts!
|
||||
```
|
||||
|
||||
|
||||
Built-in Functions
|
||||
------------------
|
||||
|
||||
The following functions (defined in the [`DebuggingPackage`][built-in packages] but excluded when
|
||||
using a [raw `Engine`]) provides runtime information for debugging purposes.
|
||||
|
||||
| Function | Parameter(s) | Not available under | Description |
|
||||
| ------------ | ------------ | :---------------------------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `back_trace` | _none_ | [`no_function`], [`no_index`] | returns an [array] of [object maps] or [strings], each containing one level of [function] call;</br>returns an empty [array] if no [debugger] is registered |
|
||||
|
||||
```rust
|
||||
// This recursive function prints its own call stack during each run
|
||||
fn foo(x) {
|
||||
print(back_trace()); // prints the current call stack
|
||||
|
||||
if x > 0 {
|
||||
foo(x - 1)
|
||||
}
|
||||
}
|
||||
```
|
@@ -1,92 +0,0 @@
|
||||
Implement a Debugging Server
|
||||
============================
|
||||
|
||||
Sometimes it is desirable to embed a debugging _server_ inside the application such that an external
|
||||
debugger interface can connect to the application's running instance at runtime.
|
||||
|
||||
This way, when scripts are run within the application, it is easy for an external interface to debug
|
||||
those scripts as they run.
|
||||
|
||||
Such connections may take the form of any communication channel, for example a TCP/IP connection, a
|
||||
named pipe, or an MPSC channel.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
### Server side
|
||||
|
||||
The following example assumes bi-direction, blocking messaging channels, such as a WebSocket
|
||||
connection, with a server that accepts connections and creates those channels.
|
||||
|
||||
```rust
|
||||
use rhai::debugger::{ASTNode, DebuggerCommand};
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.register_debugger(
|
||||
// Use the initialization callback to set up the communications channel
|
||||
// and listen to it
|
||||
|engine, mut debugger| {
|
||||
// Create server that will listen to requests
|
||||
let mut server = MyCommServer::new();
|
||||
server.listen("localhost:8080");
|
||||
|
||||
// Wrap it up in a shared locked cell so it can be 'Clone'
|
||||
let server = Rc::new(RefCell::new(server));
|
||||
|
||||
// Store the channel in the debugger state
|
||||
debugger.set_state(Dynamic::from(server));
|
||||
debugger
|
||||
},
|
||||
// Trigger the server during each debugger stop point
|
||||
|context, event, node, source, pos| {
|
||||
// Get the state
|
||||
let mut state = context.tag_mut();
|
||||
|
||||
// Get the server
|
||||
let mut server = state.write_lock::<MyCommServer>().unwrap();
|
||||
|
||||
// Send the event to the server - blocking call
|
||||
server.send_message(...);
|
||||
|
||||
// Receive command - blocking call
|
||||
match server.receive_message() {
|
||||
None => DebuggerCommand::StepOver,
|
||||
// Decode command
|
||||
Ok(...) => { ... }
|
||||
Ok(...) => { ... }
|
||||
Ok(...) => { ... }
|
||||
Ok(...) => { ... }
|
||||
Ok(...) => { ... }
|
||||
:
|
||||
:
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Client side
|
||||
|
||||
The client can be any system that can work with WebSockets for messaging.
|
||||
|
||||
```js
|
||||
// Connect to the application's debugger
|
||||
let webSocket = new WebSocket("wss://localhost:8080");
|
||||
|
||||
webSocket.on_message = (event) => {
|
||||
let msg = JSON.parse(event.data);
|
||||
|
||||
switch msg.type {
|
||||
// handle debugging events from the application...
|
||||
case "step": {
|
||||
:
|
||||
}
|
||||
:
|
||||
:
|
||||
}
|
||||
};
|
||||
|
||||
// Send command to the application
|
||||
webSocket.send("step-over");
|
||||
```
|
@@ -1,67 +0,0 @@
|
||||
Debugger State
|
||||
==============
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
Sometimes it is useful to keep a persistent _state_ within the [debugger].
|
||||
|
||||
The `Engine::register_debugger` API accepts a function that returns the initial value of the
|
||||
[debugger's][debugger] state, which is a [`Dynamic`] and can hold any value.
|
||||
|
||||
This state value is the stored into the [debugger]'s custom state.
|
||||
|
||||
|
||||
Access the Debugger State
|
||||
-------------------------
|
||||
|
||||
Use `EvalContext::global_runtime_state().debugger()` (immutable) or
|
||||
`EvalContext::global_runtime_state_mut().debugger_mut()` (mutable) to gain access to the current
|
||||
[`debugger::Debugger`] instance.
|
||||
|
||||
The following [`debugger::Debugger`] methods allow access to the custom [debugger] state.
|
||||
|
||||
| Method | Parameter type | Return type | Description |
|
||||
| ----------- | :-------------------------------: | :-------------------------: | ----------------------------------------------- |
|
||||
| `state` | _none_ | [`&Dynamic`][`Dynamic`] | returns the custom state |
|
||||
| `state_mut` | _none_ | [`&mut Dynamic`][`Dynamic`] | returns a mutable reference to the custom state |
|
||||
| `set_state` | [`impl Into<Dynamic>`][`Dynamic`] | _none_ | sets the value of the custom state |
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```rust
|
||||
engine.register_debugger(
|
||||
|engine, mut debugger| {
|
||||
// Say, use an object map for the debugger state
|
||||
let mut state = Map::new();
|
||||
// Initialize properties
|
||||
state.insert("hello".into(), 42_64.into());
|
||||
state.insert("foo".into(), false.into());
|
||||
|
||||
debugger.set_state(state);
|
||||
debugger
|
||||
},
|
||||
|context, node, source, pos| {
|
||||
// Print debugger state - which is an object map
|
||||
let state = context.global_runtime_state().debugger().state();
|
||||
println!("Current state = {state}");
|
||||
|
||||
// Get the state as an object map
|
||||
let mut state = context.global_runtime_state_mut()
|
||||
.debugger_mut().state_mut()
|
||||
.write_lock::<Map>().unwrap();
|
||||
|
||||
// Read state
|
||||
let hello = state.get("hello").unwrap().as_int().unwrap();
|
||||
|
||||
// Modify state
|
||||
state.insert("hello".into(), (hello + 1).into());
|
||||
state.insert("foo".into(), true.into());
|
||||
state.insert("something_new".into(), "hello, world!".into());
|
||||
|
||||
// Continue with debugging
|
||||
Ok(DebuggerCommand::StepInto)
|
||||
}
|
||||
);
|
||||
```
|
@@ -1,85 +0,0 @@
|
||||
Variable Definition Filter
|
||||
==========================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
[`Engine::on_def_var`]: https://docs.rs/rhai/{{version}}/rhai/struct.Engine.html#method.on_def_var
|
||||
|
||||
|
||||
Although it is easy to disable variable _[shadowing]_ via [`Engine::set_allow_shadowing`][options],
|
||||
sometimes more fine-grained control is needed.
|
||||
|
||||
For example, it may be the case that not _all_ variables [shadowing] must be disallowed, but that only
|
||||
a particular variable name needs to be protected and not others. Or only under very special
|
||||
circumstances.
|
||||
|
||||
Under this scenario, it is possible to provide a _filter_ closure to the [`Engine`] via
|
||||
[`Engine::on_def_var`] that traps variable definitions (i.e. [`let`][variable] or
|
||||
[`const`][constant] statements) in a Rhai script.
|
||||
|
||||
The filter is called when a [variable] or [constant] is defined both during runtime and compilation.
|
||||
|
||||
```rust
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register a variable definition filter.
|
||||
engine.on_def_var(|is_runtime, info, context| {
|
||||
match (info.name, info.is_const) {
|
||||
// Disallow defining 'MYSTIC_NUMBER' as a constant!
|
||||
("MYSTIC_NUMBER", true) => Ok(false),
|
||||
// Disallow defining constants not at global level!
|
||||
(_, true) if info.nesting_level > 0 => Ok(false),
|
||||
// Throw any exception you like...
|
||||
("hello", _) => Err(EvalAltResult::ErrorVariableNotFound(info.name.to_string(), Position::NONE).into()),
|
||||
// Return Ok(true) to continue with normal variable definition.
|
||||
_ => Ok(true)
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
Function Signature
|
||||
------------------
|
||||
|
||||
The function signature passed to [`Engine::on_def_var`] takes the following form.
|
||||
|
||||
> ```rust
|
||||
> Fn(is_runtime: bool, info: VarDefInfo, context: EvalContext) -> Result<bool, Box<EvalAltResult>>
|
||||
> ```
|
||||
|
||||
where:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| ------------ | :-------------: | ----------------------------------------------------------------------------------------------- |
|
||||
| `is_runtime` | `bool` | `true` if the [variable] definition event happens during runtime, `false` if during compilation |
|
||||
| `info` | `VarDefInfo` | information on the [variable] being defined |
|
||||
| `context` | [`EvalContext`] | the current _evaluation context_ |
|
||||
|
||||
and `VarDefInfo` is a simple `struct` that contains the following fields:
|
||||
|
||||
| Field | Type | Description |
|
||||
| --------------- | :-----: | --------------------------------------------------------------------------------------- |
|
||||
| `name` | `&str` | [variable] name |
|
||||
| `is_const` | `bool` | `true` if the definition is a [`const`][constant]; `false` if it is a [`let`][variable] |
|
||||
| `nesting_level` | `usize` | the current nesting level; the global level is zero |
|
||||
| `will_shadow` | `bool` | will this [variable] _[shadow]_ an existing [variable] of the same name? |
|
||||
|
||||
and [`EvalContext`] is a type that encapsulates the current _evaluation context_.
|
||||
|
||||
### Return value
|
||||
|
||||
The return value is `Result<bool, Box<EvalAltResult>>` where:
|
||||
|
||||
| Value | Description |
|
||||
| ------------------------- | -------------------------------------------------- |
|
||||
| `Ok(true)` | normal [variable] definition should continue |
|
||||
| `Ok(false)` | [throws][exception] a runtime or compilation error |
|
||||
| `Err(Box<EvalAltResult>)` | error that is reflected back to the [`Engine`] |
|
||||
|
||||
```admonish bug.small "Error during compilation"
|
||||
|
||||
During compilation (i.e. when `is_runtime` is `false`), `EvalAltResult::ErrorParsing` is passed
|
||||
through as the compilation error.
|
||||
|
||||
All other errors map to `ParseErrorType::ForbiddenVariable`.
|
||||
```
|
@@ -1,28 +0,0 @@
|
||||
Disable Certain Keywords and/or Operators
|
||||
=========================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
For certain embedded usage, it is sometimes necessary to restrict the language to a strict subset of
|
||||
Rhai to prevent usage of certain language features.
|
||||
|
||||
Rhai supports surgically disabling a [keyword] or [operator] via `Engine::disable_symbol`.
|
||||
|
||||
```rust
|
||||
use rhai::Engine;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine
|
||||
.disable_symbol("if") // disable the 'if' keyword
|
||||
.disable_symbol("+="); // disable the '+=' operator
|
||||
|
||||
// The following all return parse errors.
|
||||
|
||||
engine.compile("let x = if true { 42 } else { 0 };")?;
|
||||
// ^ 'if' is rejected as a reserved keyword
|
||||
|
||||
engine.compile("let x = 40 + 2; x += 1;")?;
|
||||
// ^ '+=' is not recognized as an operator
|
||||
// ^ other operators are not affected
|
||||
```
|
@@ -1,33 +0,0 @@
|
||||
Disable Looping
|
||||
===============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
For certain scripts, especially those in embedded usage for straight calculations, or where Rhai
|
||||
script [`AST`]'s are eventually transcribed into some other instruction set, looping may be
|
||||
undesirable as it may not be supported by the application itself.
|
||||
|
||||
Rhai looping constructs include the [`while`], [`loop`], [`do`] and [`for`] statements.
|
||||
|
||||
Although it is possible to disable these keywords via
|
||||
[`Engine::disable_symbol`][disable keywords and operators], it is simpler to disable all looping
|
||||
via [`Engine::set_allow_looping`][options].
|
||||
|
||||
```rust
|
||||
use rhai::Engine;
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Disable looping
|
||||
engine.set_allow_looping(false);
|
||||
|
||||
// The following all return parse errors.
|
||||
|
||||
engine.compile("while x == y { x += 1; }")?;
|
||||
|
||||
engine.compile(r#"loop { print("hello world!"); }"#)?;
|
||||
|
||||
engine.compile("do { x += 1; } until x > 10;")?;
|
||||
|
||||
engine.compile("for n in 0..10 { print(n); }")?;
|
||||
```
|
@@ -1,94 +0,0 @@
|
||||
Use Rhai as a Domain-Specific Language (DSL)
|
||||
============================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Rhai can be successfully used as a domain-specific language (DSL).
|
||||
|
||||
|
||||
Expressions Only
|
||||
----------------
|
||||
|
||||
In many DSL scenarios, only evaluation of expressions is needed.
|
||||
|
||||
The [`Engine::eval_expression_XXX`][`eval_expression`] API can be used to restrict a script to
|
||||
expressions only.
|
||||
|
||||
|
||||
Unicode Standard Annex #31 Identifiers
|
||||
--------------------------------------
|
||||
|
||||
[Variable] names and other identifiers do not necessarily need to be ASCII-only.
|
||||
|
||||
The [`unicode-xid-ident`] feature, when turned on, causes Rhai to allow [variable] names and
|
||||
identifiers that follow [Unicode Standard Annex #31](http://www.unicode.org/reports/tr31/).
|
||||
|
||||
This is sometimes useful in a non-English DSL.
|
||||
|
||||
|
||||
Disable Keywords and/or Operators
|
||||
---------------------------------
|
||||
|
||||
In some DSL scenarios, it is necessary to further restrict the language to exclude certain
|
||||
language features that are not necessary or dangerous to the application.
|
||||
|
||||
For example, a DSL may disable the [`while`] loop while keeping all other statement types intact.
|
||||
|
||||
It is possible, in Rhai, to surgically [disable keywords and operators].
|
||||
|
||||
|
||||
Custom Operators
|
||||
----------------
|
||||
|
||||
Some DSL scenarios require special operators that make sense only for that specific environment.
|
||||
In such cases, it is possible to define [custom operators] in Rhai.
|
||||
|
||||
```rust
|
||||
let animal = "rabbit";
|
||||
let food = "carrot";
|
||||
|
||||
animal eats food // custom operator 'eats'
|
||||
|
||||
eats(animal, food) // <- the above actually de-sugars to this
|
||||
|
||||
let x = foo # bar; // custom operator '#'
|
||||
|
||||
let x = #(foo, bar) // <- the above actually de-sugars to this
|
||||
```
|
||||
|
||||
Although a [custom operator] always de-sugars to a simple function call, nevertheless it makes the
|
||||
DSL syntax much simpler and expressive.
|
||||
|
||||
|
||||
Custom Syntax
|
||||
-------------
|
||||
|
||||
For advanced DSL scenarios, it is possible to define entire expression [_syntax_][custom syntax] –
|
||||
essentially custom statement types.
|
||||
|
||||
For example, the following is a SQL-like syntax for some obscure DSL operation:
|
||||
|
||||
```rust
|
||||
let table = [..., ..., ..., ...];
|
||||
|
||||
// Syntax = calculate $ident$ ( $expr$ -> $ident$ ) => $ident$ : $expr$
|
||||
let total = calculate sum(table->price) => row : row.weight > 50;
|
||||
|
||||
// Note: There is nothing special about those symbols; to make it look exactly like SQL:
|
||||
// Syntax = SELECT $ident$ ( $ident$ ) AS $ident$ FROM $expr$ WHERE $expr$
|
||||
let total = SELECT sum(price) AS row FROM table WHERE row.weight > 50;
|
||||
```
|
||||
|
||||
After registering this [custom syntax] with Rhai, it can be used anywhere inside a script as
|
||||
a normal expression.
|
||||
|
||||
For its evaluation, the callback function will receive the following list of inputs:
|
||||
|
||||
* `inputs[0] = "sum"` – math operator
|
||||
* `inputs[1] = "price"` – field name
|
||||
* `inputs[2] = "row"` – loop variable name
|
||||
* `inputs[3] = Expression(table)` – data source
|
||||
* `inputs[4] = Expression(row.weight > 50)` – filter predicate
|
||||
|
||||
Other identifiers, such as `"calculate"`, `"FROM"`, as well as symbols such as `->` and `:` etc.,
|
||||
are parsed in the order defined within the [custom syntax].
|
@@ -1,56 +0,0 @@
|
||||
Use Rhai in Dynamic Libraries
|
||||
=============================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Sometimes functions registered into a Rhai [`Engine`] come from _dynamic libraries_ (a.k.a. _shared
|
||||
libraries_ in Linux or _DLL's_ in Windows), which are compiled separately from the main binary, not
|
||||
statically linked but loaded dynamically at runtime.
|
||||
|
||||
~~~admonish example.small "`rhai-dylib`"
|
||||
|
||||
The project [`rhai-dylib`] demonstrates an API for creating dynamically-loadable libraries
|
||||
for use with a Rhai [`Engine`].
|
||||
~~~
|
||||
|
||||
|
||||
Problem Symptom
|
||||
---------------
|
||||
|
||||
The symptom is usually _Function Not Found_ errors even though the relevant functions (within the
|
||||
dynamic library) have already been registered into the [`Engine`].
|
||||
|
||||
This usually happens when a mutable reference to the [`Engine`] is passed into an entry-point
|
||||
function exposed by the dynamic library and the [`Engine`]'s function registration API is called
|
||||
inside the dynamic library.
|
||||
|
||||
|
||||
Problem Cause
|
||||
-------------
|
||||
|
||||
To counter [DOS] attacks, the _hasher_ used by Rhai, [`ahash`], automatically generates a different
|
||||
_seed_ for hashing during each compilation and execution run.
|
||||
|
||||
This means that hash values generated by the hasher will not be _stable_ – they change
|
||||
during each compile, and during each run.
|
||||
|
||||
This creates hash mismatches between the main binary and the loaded dynamic library because, as they
|
||||
are not linked together at compile time, two independent copies of the hasher reside in them,
|
||||
resulting in different hashes for even the same function signature.
|
||||
|
||||
|
||||
Solution
|
||||
--------
|
||||
|
||||
Use [static hashing] for force predictable hashes.
|
||||
|
||||
```admonish warning.small "Warning: Safety considerations"
|
||||
|
||||
Static hashing allows dynamic libraries with Rhai code to be loaded and used with
|
||||
an [`Engine`] in the main binary.
|
||||
|
||||
However, a fixed seed enlarges the attack surface of Rhai to malicious intent
|
||||
(e.g. [DOS] attacks).
|
||||
|
||||
This safety trade-off should be carefully considered.
|
||||
```
|
@@ -1,26 +0,0 @@
|
||||
`EvalContext`
|
||||
=============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Many functions in advanced APIs contain a parameter of type `EvalContext` in order to allow the
|
||||
current evaluation state to be accessed and/or modified.
|
||||
|
||||
`EvalContext` encapsulates the current _evaluation context_ and exposes the following methods.
|
||||
|
||||
| Method | Return type | Description |
|
||||
| ---------------------------- | :----------------------------------------------------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `scope()` | [`&Scope`][`Scope`] | reference to the current [`Scope`] |
|
||||
| `scope_mut()` | [`&mut Scope`][`Scope`] | mutable reference to the current [`Scope`]; [variables] can be added to/removed from it |
|
||||
| `engine()` | [`&Engine`][`Engine`] | reference to the current [`Engine`] |
|
||||
| `source()` | `Option<&str>` | reference to the current source, if any |
|
||||
| `tag()` | [`&Dynamic`][`Dynamic`] | reference to the custom state that is persistent during the current run |
|
||||
| `tag_mut()` | [`&mut Dynamic`][`Dynamic`] | mutable reference to the custom state that is persistent during the current run |
|
||||
| `iter_imports()` | `impl Iterator<Item = (&str,`[`&Module`][`Module`]`)>` | iterator of the current stack of [modules] imported via [`import`] statements, in reverse order (i.e. later [modules] come first); not available under [`no_module`] |
|
||||
| `global_runtime_state()` | [`&GlobalRuntimeState`][`GlobalRuntimeState`] | reference to the current [global runtime state][`GlobalRuntimeState`] (including the stack of [modules] imported via [`import`] statements) |
|
||||
| `global_runtime_state_mut()` | [`&mut &mut GlobalRuntimeState`][`GlobalRuntimeState`] | mutable reference to the current [global runtime state][`GlobalRuntimeState`]; use this to access the [`debugger`][debugger] field in order to set/clear [break-points] |
|
||||
| `iter_namespaces()` | `impl Iterator<Item =`[`&Module`][`Module`]`>` | iterator of the [namespaces][function namespaces] (as [modules]) containing all script-defined [functions], in reverse order (i.e. later [modules] come first) |
|
||||
| `namespaces()` | [`&[&Module]`][`Module`] | reference to the [namespaces][function namespaces] (as [modules]) containing all script-defined [functions] |
|
||||
| `this_ptr()` | [`Option<&Dynamic>`][`Dynamic`] | reference to the current bound `this` pointer, if any |
|
||||
| `this_ptr_mut()` | [`&mut Option<&mut Dynamic>`][`Dynamic`] | mutable reference to the current bound `this` pointer, if any |
|
||||
| `call_level()` | `usize` | the current nesting level of [function] calls |
|
@@ -1,84 +0,0 @@
|
||||
Evaluate Expressions Only
|
||||
=========================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
~~~admonish tip.side "Tip: `Dynamic`"
|
||||
|
||||
Use [`Dynamic`] if you're uncertain of the return type.
|
||||
~~~
|
||||
|
||||
Very often, a use case does not require a full-blown scripting _language_, but only needs to
|
||||
evaluate _expressions_.
|
||||
|
||||
In these cases, use the `Engine::compile_expression` and `Engine::eval_expression` methods or their
|
||||
`_with_scope` variants.
|
||||
|
||||
```rust
|
||||
let result: i64 = engine.eval_expression("2 + (10 + 10) * 2")?;
|
||||
|
||||
let result: Dynamic = engine.eval_expression("get_value(42)")?;
|
||||
|
||||
// Usually this is done together with a custom scope with variables...
|
||||
|
||||
let mut scope = Scope::new();
|
||||
|
||||
scope.push("x", 42_i64);
|
||||
scope.push_constant("SCALE", 10_i64);
|
||||
|
||||
let result: i64 = engine.eval_expression_with_scope(&mut scope,
|
||||
"(x + 1) * SCALE"
|
||||
)?;
|
||||
```
|
||||
|
||||
~~~admonish bug "No statements allowed"
|
||||
|
||||
When evaluating _expressions_, no full-blown statement (e.g. [`while`], [`for`], `fn`) –
|
||||
not even [variable] assignment – is supported and will be considered syntax errors.
|
||||
|
||||
This is true also for [statement expressions]({{rootUrl}}/language/statement-expression.md)
|
||||
and [closures].
|
||||
|
||||
```rust
|
||||
// The following are all syntax errors because the script
|
||||
// is not a strict expression.
|
||||
|
||||
engine.eval_expression::<()>("x = 42")?;
|
||||
|
||||
let ast = engine.compile_expression("let x = 42")?;
|
||||
|
||||
let result = engine.eval_expression_with_scope::<i64>(&mut scope,
|
||||
"{ let y = calc(x); x + y }"
|
||||
)?;
|
||||
|
||||
let fp: FnPtr = engine.eval_expression("|x| x + 1")?;
|
||||
```
|
||||
~~~
|
||||
|
||||
|
||||
~~~admonish tip "Tip: `if`-expressions and `switch`-expressions"
|
||||
|
||||
[`if` expressions]({{rootUrl}}/language/if.md#if-expression) are allowed if both statement blocks
|
||||
contain only a single expression each.
|
||||
|
||||
[`switch` expressions]({{rootUrl}}/language/switch-expression.md) are allowed if all match
|
||||
actions are expressions and not statements.
|
||||
|
||||
loop expressions are not allowed.
|
||||
|
||||
```rust
|
||||
// The following are allowed.
|
||||
|
||||
let result = engine.eval_expression_with_scope::<i64>(&mut scope,
|
||||
"if x { 42 } else { 123 }"
|
||||
)?;
|
||||
|
||||
let result = engine.eval_expression_with_scope::<i64>(&mut scope, "
|
||||
switch x {
|
||||
0 => x * 42,
|
||||
1..=9 => foo(123) + bar(1),
|
||||
10 => 0,
|
||||
}
|
||||
")?;
|
||||
```
|
||||
~~~
|
@@ -1,46 +0,0 @@
|
||||
Create a Rust Closure from a Rhai Function
|
||||
==========================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
```admonish tip.side "Tip"
|
||||
|
||||
Very useful as callback functions!
|
||||
```
|
||||
|
||||
It is possible to further encapsulate a script in Rust such that it becomes a normal Rust closure.
|
||||
|
||||
Creating them is accomplished via the `Func` trait which contains `create_from_script`
|
||||
(as well as its companion method `create_from_ast`).
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Func}; // use 'Func' for 'create_from_script'
|
||||
|
||||
let engine = Engine::new(); // create a new 'Engine' just for this
|
||||
|
||||
let script = "fn calc(x, y) { x + y.len < 42 }";
|
||||
|
||||
// Func takes two type parameters:
|
||||
// 1) a tuple made up of the types of the script function's parameters
|
||||
// 2) the return type of the script function
|
||||
//
|
||||
// 'func' will have type Box<dyn Fn(i64, &str) -> Result<bool, Box<EvalAltResult>>> and is callable!
|
||||
let func = Func::<(i64, &str), 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
|
||||
)?;
|
||||
|
||||
func(123, "hello")? == false; // call the closure
|
||||
|
||||
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();
|
||||
let ast = engine.compile(script)?;
|
||||
schedule_callback(Box::new(move |x: i64, y: String| {
|
||||
engine.call_fn::<bool>(&mut Scope::new(), &ast, "calc", (x, y))
|
||||
}));
|
||||
```
|
@@ -1,134 +0,0 @@
|
||||
Your First Script in Rhai
|
||||
=========================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
Run a Script
|
||||
------------
|
||||
|
||||
To get going with Rhai is as simple as creating an instance of the scripting engine `rhai::Engine`
|
||||
via `Engine::new`, then calling `Engine::run`.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
|
||||
pub fn main() -> Result<(), Box<EvalAltResult>>
|
||||
// ^^^^^^^^^^^^^^^^^^
|
||||
// Rhai API error type
|
||||
{
|
||||
// Create an 'Engine'
|
||||
let engine = Engine::new();
|
||||
|
||||
// Your first Rhai Script
|
||||
let script = "print(40 + 2);";
|
||||
|
||||
// Run the script - prints "42"
|
||||
engine.run(script)?;
|
||||
|
||||
// Done!
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Get a Return Value
|
||||
------------------
|
||||
|
||||
To return a value from the script, use `Engine::eval` instead.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
|
||||
pub fn main() -> Result<(), Box<EvalAltResult>>
|
||||
{
|
||||
let engine = Engine::new();
|
||||
|
||||
let result = engine.eval::<i64>("40 + 2")?;
|
||||
// ^^^^^^^ required: cast the result to a type
|
||||
|
||||
println!("Answer: {result}"); // prints 42
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Use Script Files
|
||||
----------------
|
||||
|
||||
```admonish info.side "Script file extension"
|
||||
|
||||
Rhai script files are customarily named with extension `.rhai`.
|
||||
```
|
||||
|
||||
Or evaluate a script file directly with `Engine::run_file` or `Engine::eval_file`.
|
||||
|
||||
Loading and running script files is not available for [`no_std`] or [WASM] builds.
|
||||
|
||||
```rust
|
||||
let result = engine.eval_file::<i64>("hello_world.rhai".into())?;
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
// a 'PathBuf' is needed
|
||||
|
||||
// Running a script file also works in a similar manner
|
||||
engine.run_file("hello_world.rhai".into())?;
|
||||
```
|
||||
|
||||
```admonish tip "Tip: Unix shebangs"
|
||||
|
||||
On Unix-like systems, the _shebang_ (`#!`) is used at the very beginning of a script file to mark a
|
||||
script with an interpreter (for Rhai this would be [`rhai-run`]({{rootUrl}}/start/bin.md)).
|
||||
|
||||
If a script file starts with `#!`, the entire first line is skipped by `Engine::compile_file` and
|
||||
`Engine::eval_file`. Because of this, Rhai scripts with shebangs at the beginning need no special processing.
|
||||
|
||||
This behavior is also present for non-Unix (e.g. Windows) environments so scripts are portable.
|
||||
|
||||
~~~js
|
||||
#!/home/to/me/bin/rhai-run
|
||||
|
||||
// This is a Rhai script
|
||||
|
||||
let answer = 42;
|
||||
print(`The answer is: ${answer}`);
|
||||
~~~
|
||||
```
|
||||
|
||||
|
||||
Specify the Return Type
|
||||
-----------------------
|
||||
|
||||
~~~admonish tip.side "Tip: `Dynamic`"
|
||||
|
||||
Use [`Dynamic`] if you're uncertain of the return type.
|
||||
~~~
|
||||
|
||||
The type parameter for `Engine::eval` is used to specify the type of the return value, which _must_
|
||||
match the actual type or an error is returned. Rhai is very strict here.
|
||||
|
||||
There are two ways to specify the return type: _turbofish_ notation, or type inference.
|
||||
|
||||
### Turbofish
|
||||
|
||||
```rust
|
||||
let result = engine.eval::<i64>("40 + 2")?; // return type is i64
|
||||
|
||||
result.is::<i64>() == true;
|
||||
|
||||
let result = engine.eval::<Dynamic>("boo()")?; // use 'Dynamic' if you're not sure what type it'll be!
|
||||
|
||||
let result = engine.eval::<String>("40 + 2")?; // returns an error because the actual return type is i64, not String
|
||||
```
|
||||
|
||||
### Type inference
|
||||
|
||||
```rust
|
||||
let result: i64 = engine.eval("40 + 2")?; // return type is inferred to be i64
|
||||
|
||||
result.is::<i64>() == true;
|
||||
|
||||
let result: Dynamic = engine.eval("boo()")?; // use 'Dynamic' if you're not sure what type it'll be!
|
||||
|
||||
let result: String = engine.eval("40 + 2")?; // returns an error because the actual return type is i64, not String
|
||||
```
|
@@ -1,8 +0,0 @@
|
||||
Using the Engine
|
||||
================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Rhai's interpreter resides in the [`Engine`] type under the master `rhai` namespace.
|
||||
|
||||
This section shows how to set up, configure and use this scripting engine.
|
@@ -1,184 +0,0 @@
|
||||
Generate Definition Files for Language Server
|
||||
=============================================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
Rhai's [language server][lsp] works with IDEs to provide integrated support for the Rhai scripting language.
|
||||
|
||||
Functions and [modules] registered with an [`Engine`] can output their [metadata][functions metadata]
|
||||
into _definition files_ which are used by the [language server][lsp].
|
||||
|
||||
Definitions are generated via the `Engine::definitions` and `Engine::definitions_with_scope` API.
|
||||
|
||||
This API requires the [`metadata`] and [`internals`] feature.
|
||||
|
||||
|
||||
Configurable Options
|
||||
--------------------
|
||||
|
||||
The `Definitions` type supports the following options in a fluent method-chaining style.
|
||||
|
||||
| Option | Method | Default |
|
||||
| ------------------------------------------------------------------- | --------------------------- | :-----: |
|
||||
| Write headers in definition files? | `with_headers` | `false` |
|
||||
| Include [standard packages][built-in packages] in definition files? | `include_standard_packages` | `true` |
|
||||
|
||||
```rust
|
||||
engine
|
||||
.definitions()
|
||||
.with_headers(true) // write headers in all files
|
||||
.include_standard_packages(false) // skip standard packages
|
||||
.write_to_dir("path/to/my/definitions")
|
||||
.unwrap();
|
||||
```
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Scope};
|
||||
use rhai::plugin::*;
|
||||
|
||||
// Plugin module: 'general_kenobi'
|
||||
#[export_module]
|
||||
pub mod general_kenobi {
|
||||
use std::convert::TryInto;
|
||||
|
||||
/// Returns a string where "hello there" is repeated 'n' times.
|
||||
pub fn hello_there(n: i64) -> String {
|
||||
"hello there ".repeat(n.try_into().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
// Create scripting engine
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Create custom Scope
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// This variable will also show up in the generated definition file.
|
||||
scope.push("hello_there", "hello there");
|
||||
|
||||
// Static module namespaces will generate independent definition files.
|
||||
engine.register_static_module(
|
||||
"general_kenobi",
|
||||
exported_module!(general_kenobi).into()
|
||||
);
|
||||
|
||||
// Custom operators will also show up in the generated definition file.
|
||||
engine.register_custom_operator("minus", 100).unwrap();
|
||||
engine.register_fn("minus", |a: i64, b: i64| a - b);
|
||||
|
||||
engine.run_with_scope(&mut scope,
|
||||
"hello_there = general_kenobi::hello_there(4 minus 2);"
|
||||
)?;
|
||||
|
||||
// Output definition files in the specified directory.
|
||||
engine
|
||||
.definitions()
|
||||
.write_to_dir("path/to/my/definitions")
|
||||
.unwrap();
|
||||
|
||||
// Output definition files in the specified directory.
|
||||
// Variables in the provided 'Scope' are included.
|
||||
engine
|
||||
.definitions_with_scope(&scope)
|
||||
.write_to_dir("path/to/my/definitions")
|
||||
.unwrap();
|
||||
|
||||
// Output a single definition file with everything merged.
|
||||
// Variables in the provided 'Scope' are included.
|
||||
engine
|
||||
.definitions_with_scope(&scope)
|
||||
.write_to_file("path/to/my/definitions/all_in_one.d.rhai")
|
||||
.unwrap();
|
||||
|
||||
// Output functions metadata to a JSON string.
|
||||
// Functions in standard packages are skipped and not included.
|
||||
let json = engine
|
||||
.definitions()
|
||||
.include_standard_packages(false) // skip standard packages
|
||||
.unwrap();
|
||||
```
|
||||
|
||||
|
||||
Definition Files
|
||||
----------------
|
||||
|
||||
The generated definition files will look like the following.
|
||||
|
||||
```rust
|
||||
┌───────────────────────┐
|
||||
│ general_kenobi.d.rhai │
|
||||
└───────────────────────┘
|
||||
|
||||
module general_kenobi;
|
||||
|
||||
/// Returns a string where "hello there" is repeated 'n' times.
|
||||
fn hello_there(n: int) -> String;
|
||||
|
||||
|
||||
┌──────────────────┐
|
||||
│ __scope__.d.rhai │
|
||||
└──────────────────┘
|
||||
|
||||
module static;
|
||||
|
||||
let hello_there;
|
||||
|
||||
|
||||
┌───────────────────┐
|
||||
│ __static__.d.rhai │
|
||||
└───────────────────┘
|
||||
|
||||
module static;
|
||||
|
||||
op minus(int, int) -> int;
|
||||
|
||||
:
|
||||
:
|
||||
|
||||
|
||||
┌────────────────────┐
|
||||
│ __builtin__.d.rhai │
|
||||
└────────────────────┘
|
||||
|
||||
module static;
|
||||
|
||||
:
|
||||
:
|
||||
|
||||
|
||||
┌──────────────────────────────┐
|
||||
│ __builtin-operators__.d.rhai │
|
||||
└──────────────────────────────┘
|
||||
|
||||
module static;
|
||||
|
||||
:
|
||||
:
|
||||
|
||||
```
|
||||
|
||||
|
||||
All-in-One Definition File
|
||||
--------------------------
|
||||
|
||||
`Definitions::write_to_file` generates a single definition file with everything merged in, like the following.
|
||||
|
||||
```rust
|
||||
module static;
|
||||
|
||||
op minus(int, int) -> int;
|
||||
|
||||
:
|
||||
:
|
||||
|
||||
module general_kenobi {
|
||||
/// Returns a string where "hello there" is repeated 'n' times.
|
||||
fn hello_there(n: int) -> String;
|
||||
}
|
||||
|
||||
let hello_there;
|
||||
```
|
@@ -1,147 +0,0 @@
|
||||
Export Functions Metadata to JSON
|
||||
=================================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
|
||||
`Engine::gen_fn_metadata_to_json`<br/>`Engine::gen_fn_metadata_with_ast_to_json`
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
As part of a _reflections_ API, `Engine::gen_fn_metadata_to_json` and the corresponding
|
||||
`Engine::gen_fn_metadata_with_ast_to_json` export the full list of [custom types] and
|
||||
[functions metadata] in JSON format.
|
||||
|
||||
~~~admonish warning.small "Requires `metadata`"
|
||||
|
||||
The [`metadata`] feature is required for this API, which also pulls in the
|
||||
[`serde_json`](https://crates.io/crates/serde_json) crate.
|
||||
~~~
|
||||
|
||||
### Sources
|
||||
|
||||
Functions and [custom types] from the following sources are included:
|
||||
|
||||
1. Script-defined functions in an [`AST`] (for `Engine::gen_fn_metadata_with_ast_to_json`)
|
||||
2. Native Rust functions registered into the global namespace via the `Engine::register_XXX` API
|
||||
3. [Custom types] registered into the global namespace via the `Engine::register_type_with_name` API
|
||||
4. _Public_ (i.e. non-[`private`]) functions (native Rust or Rhai scripted) and [custom types] in static modules
|
||||
registered via `Engine::register_static_module`
|
||||
5. Native Rust functions and [custom types] in external [packages] registered via `Engine::register_global_module`
|
||||
6. Native Rust functions and [custom types] in [built-in packages] (optional)
|
||||
|
||||
|
||||
JSON Schema
|
||||
-----------
|
||||
|
||||
The JSON schema used to hold metadata is very simple, containing a nested structure of
|
||||
`modules`, a list of `customTypes` and a list of `functions`.
|
||||
|
||||
### Module Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"doc": "//! Module documentation",
|
||||
|
||||
"modules":
|
||||
{
|
||||
"sub_module_1": /* namespace 'sub_module_1' */
|
||||
{
|
||||
"modules":
|
||||
{
|
||||
"sub_sub_module_A": /* namespace 'sub_module_1::sub_sub_module_A' */
|
||||
{
|
||||
"doc": "//! Module documentation can also occur in any sub-module",
|
||||
|
||||
"customTypes": /* custom types exported in 'sub_module_1::sub_sub_module_A' */
|
||||
[
|
||||
{ ... custom type metadata ... },
|
||||
{ ... custom type metadata ... },
|
||||
{ ... custom type metadata ... }
|
||||
...
|
||||
],
|
||||
"functions": /* functions exported in 'sub_module_1::sub_sub_module_A' */
|
||||
[
|
||||
{ ... function metadata ... },
|
||||
{ ... function metadata ... },
|
||||
{ ... function metadata ... },
|
||||
{ ... function metadata ... }
|
||||
...
|
||||
]
|
||||
},
|
||||
"sub_sub_module_B": /* namespace 'sub_module_1::sub_sub_module_B' */
|
||||
{
|
||||
...
|
||||
}
|
||||
}
|
||||
},
|
||||
"sub_module_2": /* namespace 'sub_module_2' */
|
||||
{
|
||||
...
|
||||
},
|
||||
...
|
||||
},
|
||||
|
||||
"customTypes": /* custom types registered globally */
|
||||
[
|
||||
{ ... custom type metadata ... },
|
||||
{ ... custom type metadata ... },
|
||||
{ ... custom type metadata ... },
|
||||
...
|
||||
],
|
||||
|
||||
"functions": /* functions registered globally or in the 'AST' */
|
||||
[
|
||||
{ ... function metadata ... },
|
||||
{ ... function metadata ... },
|
||||
{ ... function metadata ... },
|
||||
{ ... function metadata ... },
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Type Metadata Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"typeName": "alloc::string::String", /* name of Rust type */
|
||||
"displayName": "MyType",
|
||||
"docComments": /* omitted if none */
|
||||
[
|
||||
"/// My super-string type.",
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Function Metadata Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"baseHash": 9876543210, /* partial hash with only number of parameters */
|
||||
"fullHash": 1234567890, /* full hash with actual parameter types */
|
||||
"namespace": "internal" | "global",
|
||||
"access": "public" | "private",
|
||||
"name": "fn_name",
|
||||
"isAnonymous": false,
|
||||
"type": "native" | "script",
|
||||
"numParams": 42, /* number of parameters */
|
||||
"params": /* omitted if no parameters */
|
||||
[
|
||||
{ "name": "param_1", "type": "type_1" },
|
||||
{ "name": "param_2" }, /* no type name */
|
||||
{ "type": "type_3" }, /* no parameter name */
|
||||
...
|
||||
],
|
||||
"thisType": "this_type", /* omitted if none */
|
||||
"returnType": "ret_type", /* omitted if () or unknown */
|
||||
"signature": "[private] fn_name(param_1: type_1, param_2, _: type_3) -> ret_type",
|
||||
"docComments": /* omitted if none */
|
||||
[
|
||||
"/// doc-comment line 1",
|
||||
"/// doc-comment line 2",
|
||||
"/** doc-comment block */",
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
@@ -1,93 +0,0 @@
|
||||
Get Native Function Signatures
|
||||
==============================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
|
||||
`Engine::gen_fn_signatures`
|
||||
---------------------------
|
||||
|
||||
As part of a _reflections_ API, `Engine::gen_fn_signatures` returns a list of function _signatures_
|
||||
(as `Vec<String>`), each corresponding to a particular native function available to that [`Engine`] instance.
|
||||
|
||||
> _name_ `(`_param 1_`:`_type 1_`,` _param 2_`:`_type 2_`,` ... `,` _param n_`:`_type n_`) ->` _return type_
|
||||
|
||||
The [`metadata`] feature must be used to turn on this API.
|
||||
|
||||
### Sources
|
||||
|
||||
Functions from the following sources are included, in order:
|
||||
|
||||
1. Native Rust functions registered into the global namespace via the `Engine::register_XXX` API
|
||||
2. _Public_ (i.e. non-[`private`]) functions (native Rust or Rhai scripted) in global sub-modules
|
||||
registered via `Engine::register_static_module`.
|
||||
3. Native Rust functions in external [packages] registered via `Engine::register_global_module`
|
||||
4. Native Rust functions in [built-in packages] (optional)
|
||||
|
||||
|
||||
Functions Metadata
|
||||
------------------
|
||||
|
||||
Beware, however, that not all function signatures contain parameters and return value information.
|
||||
|
||||
### `Engine::register_XXX`
|
||||
|
||||
For instance, functions registered via `Engine::register_XXX` contain no information on the names of
|
||||
parameter because Rust simply does not make such metadata available natively.
|
||||
|
||||
Type names, however, _are_ provided.
|
||||
|
||||
A function registered under the name `foo` with three parameters.
|
||||
|
||||
> `foo(_: i64, _: char, _: &str) -> String`
|
||||
|
||||
An [operator] function. Notice that function names do not need to be valid identifiers.
|
||||
|
||||
> `+=(_: &mut i64, _: i64)`
|
||||
|
||||
A [property setter][getters/setters].
|
||||
Notice that function names do not need to be valid identifiers.
|
||||
In this case, the first parameter should be `&mut T` of the custom type and the return value is `()`:
|
||||
|
||||
> `set$prop(_: &mut TestStruct, _: i64)`
|
||||
|
||||
### Script-Defined Functions
|
||||
|
||||
Script-defined [function] signatures contain parameter names.
|
||||
Since _all_ parameters, as well as the return value, are [`Dynamic`] the types are simply not shown.
|
||||
|
||||
> `foo(x, y, z)`
|
||||
|
||||
is probably defined simply as:
|
||||
|
||||
```rust
|
||||
/// This is a doc-comment, included in this function's metadata.
|
||||
fn foo(x, y, z) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
which is really the same as:
|
||||
|
||||
> `foo(x: Dynamic, y: Dynamic, z: Dynamic) -> Result<Dynamic, Box<EvalAltResult>>`
|
||||
|
||||
### Plugin Functions
|
||||
|
||||
Functions defined in [plugin modules] are the best.
|
||||
They contain all metadata describing the functions, including [doc-comments].
|
||||
|
||||
For example, a plugin function `combine`:
|
||||
|
||||
> `/// This is a doc-comment, included in this function's metadata.`
|
||||
> `combine(list: &mut MyStruct<i64>, num: usize, name: &str) -> bool`
|
||||
|
||||
Notice that function names do not need to be valid identifiers.
|
||||
|
||||
For example, an [operator] defined as a [fallible function] in a [plugin module] via
|
||||
`#[rhai_fn(name="+=", return_raw)]` returns `Result<bool, Box<EvalAltResult>>`:
|
||||
|
||||
> `+=(list: &mut MyStruct<i64>, value: &str) -> Result<bool, Box<EvalAltResult>>`
|
||||
|
||||
For example, a [property getter][getters/setters] defined in a [plugin module]:
|
||||
|
||||
> `get$prop(obj: &mut MyStruct<i64>) -> String`
|
@@ -1,49 +0,0 @@
|
||||
Functions and Custom Types Metadata
|
||||
===================================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
~~~admonish warning.small "Requires `metadata`"
|
||||
|
||||
Exporting metadata requires the [`metadata`] feature.
|
||||
~~~
|
||||
|
||||
|
||||
Functions
|
||||
---------
|
||||
|
||||
The _metadata_ of a [function] means all relevant information related to a function's
|
||||
definition including:
|
||||
|
||||
1. Its callable name
|
||||
|
||||
2. Its access mode (public or [private][`private`])
|
||||
|
||||
3. Its parameter names and types (if any)
|
||||
|
||||
4. Its return value and type (if any)
|
||||
|
||||
5. Its nature (i.e. native Rust or Rhai-scripted)
|
||||
|
||||
6. Its [namespace][function namespace] ([module] or global)
|
||||
|
||||
7. Its purpose, in the form of [doc-comments]
|
||||
|
||||
8. Usage notes, warnings, examples etc., in the form of [doc-comments]
|
||||
|
||||
A function's _signature_ encapsulates the first four pieces of information in a single concise line
|
||||
of definition:
|
||||
|
||||
> `[private]` _name_ `(`_param 1_`:`_type 1_`,` _param 2_`:`_type 2_`,` ... `,` _param n_`:`_type n_`) ->` _return type_
|
||||
|
||||
|
||||
Custom Types
|
||||
------------
|
||||
|
||||
The _metadata_ of a [custom type] include:
|
||||
|
||||
1. Its full Rust type name
|
||||
|
||||
2. Its pretty-print _display name_ (which can be the same as its Rust type name)
|
||||
|
||||
3. Its purpose, in the form of [doc-comments]
|
@@ -1,212 +0,0 @@
|
||||
Constants Propagation
|
||||
=====================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
```admonish tip.side
|
||||
|
||||
Effective in template-based machine-generated scripts to turn on/off certain sections.
|
||||
```
|
||||
|
||||
[Constants] propagation is commonly used to:
|
||||
|
||||
* remove dead code,
|
||||
|
||||
* avoid [variable] lookups,
|
||||
|
||||
* [pre-calculate](op-eval.md) [constant] expressions.
|
||||
|
||||
```rust
|
||||
const ABC = true;
|
||||
const X = 41;
|
||||
|
||||
if ABC || calc(X+1) { print("done!"); } // 'ABC' is constant so replaced by 'true'...
|
||||
// 'X' is constant so replaced by 41...
|
||||
|
||||
if true || calc(42) { print("done!"); } // '41+1' is replaced by 42
|
||||
// since '||' short-circuits, 'calc' is never called
|
||||
|
||||
if true { print("done!"); } // <- the line above is equivalent to this
|
||||
|
||||
print("done!"); // <- the line above is further simplified to this
|
||||
// because the condition is always true
|
||||
```
|
||||
|
||||
~~~admonish tip "Tip: Custom `Scope` constants"
|
||||
|
||||
[Constant] values can be provided in a custom [`Scope`] object to the [`Engine`]
|
||||
for optimization purposes.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Scope};
|
||||
|
||||
let engine = Engine::new();
|
||||
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Add constant to custom scope
|
||||
scope.push_constant("ABC", true);
|
||||
|
||||
// Evaluate script with custom scope
|
||||
engine.run_with_scope(&mut scope,
|
||||
r#"
|
||||
if ABC { // 'ABC' is replaced by 'true'
|
||||
print("done!");
|
||||
}
|
||||
"#)?;
|
||||
```
|
||||
~~~
|
||||
|
||||
~~~admonish tip "Tip: Customer module constants"
|
||||
|
||||
[Constants] defined in [modules] that are registered into an [`Engine`] via
|
||||
`Engine::register_global_module` are used in optimization.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Module};
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
let mut module = Module::new();
|
||||
|
||||
// Add constant to module
|
||||
module.set_var("ABC", true);
|
||||
|
||||
// Register global module
|
||||
engine.register_global_module(module.into());
|
||||
|
||||
// Evaluate script
|
||||
engine.run(
|
||||
r#"
|
||||
if ABC { // 'ABC' is replaced by 'true'
|
||||
print("done!");
|
||||
}
|
||||
"#)?;
|
||||
```
|
||||
~~~
|
||||
|
||||
~~~admonish danger "Caveat: Constants in custom scope and modules are also propagated into functions"
|
||||
|
||||
[Constants] defined at _global_ level typically cannot be seen by script [functions] because they are _pure_.
|
||||
|
||||
```rust
|
||||
const MY_CONSTANT = 42; // <- constant defined at global level
|
||||
|
||||
print(MY_CONSTANT); // <- optimized to: print(42)
|
||||
|
||||
fn foo() {
|
||||
MY_CONSTANT // <- not optimized: 'foo' cannot see 'MY_CONSTANT'
|
||||
}
|
||||
|
||||
print(foo()); // error: 'MY_CONSTANT' not found
|
||||
```
|
||||
|
||||
When [constants] are provided in a custom [`Scope`] (e.g. via `Engine::compile_with_scope`,
|
||||
`Engine::eval_with_scope` or `Engine::run_with_scope`), or in a [module] registered via
|
||||
`Engine::register_global_module`, instead of defined within the same script, they are also
|
||||
propagated to [functions].
|
||||
|
||||
This is usually the intuitive usage and behavior expected by regular users, even though it means
|
||||
that a script will behave differently (essentially a runtime error) when [script optimization] is disabled.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Scope};
|
||||
|
||||
let engine = Engine::new();
|
||||
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Add constant to custom scope
|
||||
scope.push_constant("MY_CONSTANT", 42_i64);
|
||||
|
||||
engine.run_with_scope(&mut scope,
|
||||
"
|
||||
print(MY_CONSTANT); // optimized to: print(42)
|
||||
|
||||
fn foo() {
|
||||
MY_CONSTANT // optimized to: fn foo() { 42 }
|
||||
}
|
||||
|
||||
print(foo()); // prints 42
|
||||
")?;
|
||||
```
|
||||
|
||||
The script will act differently when [script optimization] is disabled because script [functions]
|
||||
are _pure_ and typically cannot see [constants] within the custom [`Scope`].
|
||||
|
||||
Therefore, constants in [functions] now throw a runtime error.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Scope, OptimizationLevel};
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Turn off script optimization, no constants propagation is performed
|
||||
engine.set_optimization_level(OptimizationLevel::None);
|
||||
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Add constant to custom scope
|
||||
scope.push_constant("MY_CONSTANT", 42_i64);
|
||||
|
||||
engine.run_with_scope(&mut scope,
|
||||
"
|
||||
print(MY_CONSTANT); // prints 42
|
||||
|
||||
fn foo() {
|
||||
MY_CONSTANT // <- 'foo' cannot see 'MY_CONSTANT'
|
||||
}
|
||||
|
||||
print(foo()); // error: 'MY_CONSTANT' not found
|
||||
")?;
|
||||
```
|
||||
~~~
|
||||
|
||||
~~~admonish danger "Caveat: Beware of large constants"
|
||||
|
||||
[Constants] propagation replaces each usage of the [constant] with a clone of its value.
|
||||
|
||||
This may have negative implications to performance if the [constant] value is expensive to clone
|
||||
(e.g. if the type is very large).
|
||||
|
||||
```rust
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Push a large constant into the scope...
|
||||
let big_type = AVeryLargeType::take_long_time_to_create();
|
||||
scope.push_constant("MY_BIG_TYPE", big_type);
|
||||
|
||||
// Causes each usage of 'MY_BIG_TYPE' in the script below to be replaced
|
||||
// by cloned copies of 'AVeryLargeType'.
|
||||
let result = engine.run_with_scope(&mut scope,
|
||||
"
|
||||
let value = MY_BIG_TYPE.value;
|
||||
let data = MY_BIG_TYPE.data;
|
||||
let len = MY_BIG_TYPE.len();
|
||||
let has_options = MY_BIG_TYPE.has_options();
|
||||
let num_options = MY_BIG_TYPE.options_len();
|
||||
")?;
|
||||
```
|
||||
|
||||
To avoid this, compile the script first to an [`AST`] _without_ the [constants], then evaluate the
|
||||
[`AST`] (e.g. with `Engine::eval_ast_with_scope` or `Engine::run_ast_with_scope`) together with
|
||||
the [constants].
|
||||
~~~
|
||||
|
||||
~~~admonish danger "Caveat: Constants may be modified by Rust methods"
|
||||
|
||||
If the [constants] are modified later on (yes, it is possible, via Rust _methods_),
|
||||
the modified values will not show up in the optimized script.
|
||||
Only the initialization values of [constants] are ever retained.
|
||||
|
||||
```rust
|
||||
const MY_SECRET_ANSWER = 42;
|
||||
|
||||
MY_SECRET_ANSWER.update_to(666); // assume 'update_to(&mut i64)' is a Rust function
|
||||
|
||||
print(MY_SECRET_ANSWER); // prints 42 because the constant is propagated
|
||||
```
|
||||
|
||||
This is almost never a problem because real-world scripts seldom modify a [constant],
|
||||
but the possibility is always there.
|
||||
~~~
|
@@ -1,51 +0,0 @@
|
||||
Dead Code Elimination
|
||||
=====================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
```admonish question.side.wide "But who writes dead code?"
|
||||
|
||||
Nobody deliberately writes scripts with dead code (we hope).
|
||||
|
||||
They are, however, extremely common in template-based machine-generated scripts.
|
||||
```
|
||||
|
||||
Rhai attempts to eliminate _dead code_.
|
||||
|
||||
"Dead code" is code that does nothing and has no side effects.
|
||||
|
||||
Example is an pure expression by itself as a statement (allowed in Rhai).
|
||||
The result of the expression is calculated then immediately discarded and not used.
|
||||
|
||||
```rust
|
||||
{
|
||||
let x = 999; // NOT eliminated: variable may be used later on (perhaps even an 'eval')
|
||||
|
||||
123; // eliminated: no effect
|
||||
|
||||
"hello"; // eliminated: no effect
|
||||
|
||||
[1, 2, x, 4]; // eliminated: no effect
|
||||
|
||||
if 42 > 0 { // '42 > 0' is replaced by 'true' and the first branch promoted
|
||||
foo(42); // promoted, NOT eliminated: the function 'foo' may have side-effects
|
||||
} else {
|
||||
bar(x); // eliminated: branch is never reached
|
||||
}
|
||||
|
||||
let z = x; // eliminated: local variable, no side-effects, and only pure afterwards
|
||||
|
||||
666 // NOT eliminated: this is the return value of the block,
|
||||
// and the block is the last one so this is the return value of the whole script
|
||||
}
|
||||
```
|
||||
|
||||
The above script optimizes to:
|
||||
|
||||
```rust
|
||||
{
|
||||
let x = 999;
|
||||
foo(42);
|
||||
666
|
||||
}
|
||||
```
|
@@ -1,25 +0,0 @@
|
||||
Turn Off Script Optimizations
|
||||
=============================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
When scripts:
|
||||
|
||||
* are known to be run only _once_ and then thrown away,
|
||||
|
||||
* are known to contain no dead code,
|
||||
|
||||
* do not use constants in calculations
|
||||
|
||||
the optimization pass may be a waste of time and resources.
|
||||
|
||||
In that case, turn optimization off by setting the optimization level to [`OptimizationLevel::None`].
|
||||
|
||||
```rust
|
||||
let engine = rhai::Engine::new();
|
||||
|
||||
// Turn off the optimizer
|
||||
engine.set_optimization_level(rhai::OptimizationLevel::None);
|
||||
```
|
||||
|
||||
Alternatively, disable optimizations via the [`no_optimize`] feature.
|
@@ -1,75 +0,0 @@
|
||||
Eager Function Evaluation When Using Full Optimization Level
|
||||
============================================================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
When the optimization level is [`OptimizationLevel::Full`], the [`Engine`] assumes all functions to
|
||||
be _pure_ and will _eagerly_ evaluated all function calls with constant arguments, using the result
|
||||
to replace the call.
|
||||
|
||||
This also applies to all operators (which are implemented as functions).
|
||||
|
||||
```rust
|
||||
// When compiling the following with OptimizationLevel::Full...
|
||||
|
||||
const DECISION = 1;
|
||||
// this condition is now eliminated because 'sign(DECISION) > 0'
|
||||
if sign(DECISION) > 0 // <- is a call to the 'sign' and '>' functions, and they return 'true'
|
||||
{
|
||||
print("hello!"); // <- this block is promoted to the parent level
|
||||
} else {
|
||||
print("boo!"); // <- this block is eliminated because it is never reached
|
||||
}
|
||||
|
||||
print("hello!"); // <- the above is equivalent to this
|
||||
// ('print' and 'debug' are handled specially)
|
||||
```
|
||||
|
||||
~~~admonish danger "Won't this be dangerous?"
|
||||
|
||||
Yes! _Very!_
|
||||
|
||||
```rust
|
||||
// Nuclear silo control
|
||||
if launch_nukes && president_okeyed {
|
||||
print("This is NOT a drill!");
|
||||
update_defcon(1);
|
||||
start_world_war(3);
|
||||
launch_all_nukes();
|
||||
} else {
|
||||
print("This is a drill. Thank you for your cooperation.");
|
||||
}
|
||||
```
|
||||
|
||||
In the script above (well... as if nuclear silos will one day be controlled by Rhai scripts),
|
||||
the functions `update_defcon`, `start_world_war` and `launch_all_nukes` will be evaluated
|
||||
during _compilation_ because they have constant arguments.
|
||||
|
||||
The [variables] `launch_nukes` and `president_okeyed` are never checked, because the script
|
||||
actually has not yet been run! The functions are called during compilation.
|
||||
This is, _obviously_, not what you want.
|
||||
|
||||
**Moral of the story: compile with an [`Engine`] that does not have any functions registered.
|
||||
Register functions _AFTER_ compilation.**
|
||||
~~~
|
||||
|
||||
~~~admonish question "Why would I ever want to do this then?"
|
||||
|
||||
Good question! There are two reasons:
|
||||
|
||||
* A function call may result in cleaner code than the resultant value.
|
||||
In Rust, this would have been handled via a `const` function.
|
||||
|
||||
* Evaluating a value to a [custom type] that has no representation in script.
|
||||
|
||||
```rust
|
||||
// A complex function that returns a unique ID based on the arguments
|
||||
let id = make_unique_id(123, "hello", true);
|
||||
|
||||
// The above is arguably clearer than:
|
||||
// let id = 835781293546; // generated from 123, "hello" and true
|
||||
|
||||
// A custom type that cannot be represented in script
|
||||
let complex_obj = make_complex_obj(42);
|
||||
```
|
||||
~~~
|
@@ -1,62 +0,0 @@
|
||||
Script Optimization
|
||||
===================
|
||||
|
||||
{{#title Script Optimization}}
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
Rhai includes an _optimizer_ that tries to optimize a script after parsing.
|
||||
This can reduce resource utilization and increase execution speed.
|
||||
|
||||
Script optimization can be turned off via the [`no_optimize`] feature.
|
||||
|
||||
|
||||
Optimization Levels
|
||||
===================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
There are three levels of optimization: `None`, `Simple` and `Full`.
|
||||
The default is `Simple`.
|
||||
|
||||
An [`Engine`]'s optimization level is set via [`Engine::set_optimization_level`][options].
|
||||
|
||||
```rust
|
||||
// Turn on aggressive optimizations
|
||||
engine.set_optimization_level(rhai::OptimizationLevel::Full);
|
||||
```
|
||||
|
||||
`None`
|
||||
------
|
||||
|
||||
`None` is obvious – no optimization on the AST is performed.
|
||||
|
||||
|
||||
`Simple` (Default)
|
||||
------------------
|
||||
|
||||
`Simple` performs only relatively _safe_ optimizations without causing side-effects (i.e. it only
|
||||
relies on static analysis and [built-in operators] for [constant] [standard types], and will not
|
||||
perform any external function calls).
|
||||
|
||||
```admonish warning.small
|
||||
After _constants propagation_ is performed, if the [constants] are then modified (yes, it is possible, via Rust functions),
|
||||
the modified values will _not_ show up in the optimized script.
|
||||
|
||||
Only the initialization values of [constants] are ever retained.
|
||||
```
|
||||
|
||||
```admonish warning.small
|
||||
|
||||
Overriding a [built-in operator] in the [`Engine`] afterwards has no effect after the
|
||||
optimizer replaces an expression with its calculated value.
|
||||
```
|
||||
|
||||
`Full`
|
||||
------
|
||||
|
||||
`Full` is _much_ more aggressive, _including_ calling external functions on [constant] arguments to
|
||||
determine their results.
|
||||
|
||||
One benefit to this is that many more optimization opportunities arise, especially with regards to
|
||||
comparison operators.
|
@@ -1,85 +0,0 @@
|
||||
Eager Operator Evaluation
|
||||
=========================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
Most operators are actually function calls, and those functions can be overridden, so whether they
|
||||
are optimized away depends on the situation:
|
||||
|
||||
```admonish info.side.wide "No external functions"
|
||||
|
||||
Rhai guarantees that no external function will be run, which may trigger side-effects
|
||||
(unless the optimization level is [`OptimizationLevel::Full`]).
|
||||
```
|
||||
|
||||
* if the operands are not [constant] values, it is **not** optimized;
|
||||
|
||||
* if the [operator] is [overloaded][operator overloading], it is **not** optimized because the
|
||||
overloading function may not be _pure_ (i.e. may cause side-effects when called);
|
||||
|
||||
* if the [operator] is not _built-in_ (see list of [built-in operators]), it is **not** optimized;
|
||||
|
||||
* if the [operator] is a [built-in operator] for a [standard type][standard types], it is called and
|
||||
replaced by a [constant] result.
|
||||
|
||||
```js
|
||||
// The following is most likely generated by machine.
|
||||
|
||||
const DECISION = 1; // this is an integer, one of the standard types
|
||||
|
||||
if DECISION == 1 { // this is optimized into 'true'
|
||||
:
|
||||
} else if DECISION == 2 { // this is optimized into 'false'
|
||||
:
|
||||
} else if DECISION == 3 { // this is optimized into 'false'
|
||||
:
|
||||
} else {
|
||||
:
|
||||
}
|
||||
|
||||
// Or an equivalent using 'switch':
|
||||
|
||||
switch DECISION {
|
||||
1 => ..., // this statement is promoted
|
||||
2 => ..., // this statement is eliminated
|
||||
3 => ..., // this statement is eliminated
|
||||
_ => ... // this statement is eliminated
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Pre-Evaluation of Constant Expressions
|
||||
--------------------------------------
|
||||
|
||||
Because of the eager evaluation of [operators][built-in operators] for [standard types], many
|
||||
[constant] expressions will be evaluated and replaced by the result.
|
||||
|
||||
```rust
|
||||
let x = (1+2) * 3 - 4/5 % 6; // will be replaced by 'let x = 9'
|
||||
|
||||
let y = (1 > 2) || (3 <= 4); // will be replaced by 'let y = true'
|
||||
```
|
||||
|
||||
For operators that are not optimized away due to one of the above reasons, the function calls are
|
||||
simply left behind.
|
||||
|
||||
```rust
|
||||
// Assume 'new_state' returns some custom type that is NOT one of the standard types.
|
||||
// Also assume that the '==' operator is defined for that custom type.
|
||||
const DECISION_1 = new_state(1);
|
||||
const DECISION_2 = new_state(2);
|
||||
const DECISION_3 = new_state(3);
|
||||
|
||||
if DECISION == 1 { // NOT optimized away because the operator is not built-in
|
||||
: // and may cause side-effects if called!
|
||||
:
|
||||
} else if DECISION == 2 { // same here, NOT optimized away
|
||||
:
|
||||
} else if DECISION == 3 { // same here, NOT optimized away
|
||||
:
|
||||
} else {
|
||||
:
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, turn the optimizer to [`OptimizationLevel::Full`].
|
@@ -1,21 +0,0 @@
|
||||
Optimization Passes
|
||||
===================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
[Script optimization] is performed via multiple _passes_.
|
||||
Each pass does a specific optimization.
|
||||
|
||||
The optimization is completed when no passes can simplify the [`AST`] any further.
|
||||
|
||||
|
||||
Built-in Optimization Passes
|
||||
----------------------------
|
||||
|
||||
| Pass | Description |
|
||||
| ------------------------------------------ | ------------------------------------------------- |
|
||||
| [Dead code elimination](dead-code.md) | Eliminates code that cannot be reached |
|
||||
| [Constants propagation](constants.md) | Replaces [constants] with values |
|
||||
| [Compound assignments rewrite](rewrite.md) | Rewrites assignments into compound assignments |
|
||||
| [Eager operator evaluation](op-eval.md) | Eagerly calls operators with [constant] arguments |
|
||||
| [Eager function evaluation](eager.md) | Eagerly calls functions with [constant] arguments |
|
@@ -1,50 +0,0 @@
|
||||
Re-Optimize an AST
|
||||
==================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
Sometimes it is more efficient to store one single, large script with delimited code blocks guarded by
|
||||
constant variables. This script is compiled once to an [`AST`].
|
||||
|
||||
Then, depending on the execution environment, constants are passed into the [`Engine`] and the
|
||||
[`AST`] is _re_-optimized based on those constants via `Engine::optimize_ast`, effectively pruning
|
||||
out unused code sections.
|
||||
|
||||
The final, optimized [`AST`] is then used for evaluations.
|
||||
|
||||
```rust
|
||||
// Compile master script to AST
|
||||
let master_ast = engine.compile(
|
||||
"
|
||||
fn do_work() {
|
||||
// Constants in scope are also propagated into functions
|
||||
print(SCENARIO);
|
||||
}
|
||||
|
||||
switch SCENARIO {
|
||||
1 => do_work(),
|
||||
2 => do_something(),
|
||||
3 => do_something_else(),
|
||||
_ => do_nothing()
|
||||
}
|
||||
")?;
|
||||
|
||||
for n in 0..5_i64 {
|
||||
// Create a new scope - put constants in it to aid optimization
|
||||
let mut scope = Scope::new();
|
||||
scope.push_constant("SCENARIO", n);
|
||||
|
||||
// Re-optimize the AST
|
||||
let new_ast = engine.optimize_ast(&scope, master_ast.clone(), OptimizationLevel::Simple);
|
||||
|
||||
// Run it
|
||||
engine.run_ast(&new_ast)?;
|
||||
}
|
||||
```
|
||||
|
||||
```admonish note.small "Constants propagation"
|
||||
|
||||
Beware that [constants] inside the custom [`Scope`] will also be propagated to [functions] defined
|
||||
within the script while normally such [functions] are _pure_ and cannot see [variables]/[constants]
|
||||
within the global [`Scope`].
|
||||
```
|
@@ -1,47 +0,0 @@
|
||||
Compound Assignment Rewrite
|
||||
===========================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
```admonish info.side "Avoid cloning"
|
||||
|
||||
Arguments passed as value are always cloned.
|
||||
```
|
||||
|
||||
Usually, a _compound assignment_ (e.g. `+=` for append) takes a mutable first parameter
|
||||
(i.e. `&mut`) while the corresponding simple [operator] (i.e. `+`) does not.
|
||||
|
||||
The script optimizer rewrites normal assignments into _compound assignments_ wherever possible in
|
||||
order to avoid unnecessary cloning.
|
||||
|
||||
```rust
|
||||
let big = create_some_very_big_type();
|
||||
|
||||
big = big + 1;
|
||||
// ^ 'big' is cloned here
|
||||
|
||||
// The above is equivalent to:
|
||||
let temp_value = big + 1;
|
||||
big = temp_value;
|
||||
|
||||
big += 1; // <- 'big' is NOT cloned
|
||||
```
|
||||
|
||||
~~~admonish warning.small "Warning: Simple references only"
|
||||
|
||||
Only _simple variable references_ are optimized.
|
||||
|
||||
No [_common sub-expression elimination_](https://en.wikipedia.org/wiki/Common_subexpression_elimination)
|
||||
is performed by Rhai.
|
||||
|
||||
```rust
|
||||
x = x + 1; // <- this statement...
|
||||
|
||||
x += 1; // <- ... is rewritten to this
|
||||
|
||||
x[y] = x[y] + 1; // <- but this is not,
|
||||
// so MUCH slower...
|
||||
|
||||
x[y] += 1; // <- ... than this
|
||||
```
|
||||
~~~
|
@@ -1,85 +0,0 @@
|
||||
Subtle Semantic Changes After Optimization
|
||||
==========================================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
|
||||
Some optimizations can alter subtle semantics of the script, causing the script to behave
|
||||
differently when run with or without optimization.
|
||||
|
||||
Typically, this involves some form of error that may arise in the original, unoptimized script but
|
||||
is optimized away by the [script optimizer][script optimization].
|
||||
|
||||
```admonish danger.small "DO NOT depend on runtime errors"
|
||||
|
||||
Needless to say, it is usually a _Very Bad Idea™_ to depend on a script failing with a runtime error
|
||||
or such kind of subtleties.
|
||||
|
||||
If it turns out to be necessary (why? I would never guess), turn script optimization off by setting
|
||||
the optimization level to [`OptimizationLevel::None`].
|
||||
```
|
||||
|
||||
|
||||
Disappearing Runtime Errors
|
||||
---------------------------
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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, so the script would have been terminated at that point
|
||||
with a runtime error.
|
||||
|
||||
In fact, any errors inside a statement that has been eliminated will silently _disappear_.
|
||||
|
||||
```rust
|
||||
print("start!");
|
||||
if my_decision { /* do nothing... */ } // eliminated due to no effect
|
||||
print("end!");
|
||||
|
||||
// The above optimizes to:
|
||||
|
||||
print("start!");
|
||||
print("end!");
|
||||
```
|
||||
|
||||
In the script above, if `my_decision` holds anything other than a boolean value,
|
||||
the script should have been terminated due to a type error.
|
||||
|
||||
However, after optimization, the entire [`if`] statement is removed (because an access to
|
||||
`my_decision` produces no side-effects), thus the script silently runs to completion without errors.
|
||||
|
||||
|
||||
Eliminated Useless Work
|
||||
-----------------------
|
||||
|
||||
Another example is more subtle – that of an empty loop body.
|
||||
|
||||
```rust
|
||||
// ... say, the 'Engine' is limited to no more than 10,000 operations...
|
||||
|
||||
// The following should fail because it exceeds the operations limit:
|
||||
for n in 0..42000 {
|
||||
// empty loop
|
||||
}
|
||||
|
||||
// The above is optimized away because the loop body is empty
|
||||
// and the iterations simply do nothing.
|
||||
()
|
||||
```
|
||||
|
||||
Normally, and empty loop body inside a [`for`] statement with a pure iterator does nothing and can
|
||||
be safely eliminated.
|
||||
|
||||
Thus the script now runs silently to completion without errors.
|
||||
|
||||
Without optimization, the script may fail by exceeding the [maximum number of operations] allowed.
|
@@ -1,23 +0,0 @@
|
||||
Side-Effect Considerations for Full Optimization Level
|
||||
======================================================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
All of Rhai's built-in functions (and operators which are implemented as functions) are _pure_
|
||||
(i.e. they do not mutate state nor cause any side-effects, with the exception of `print` and `debug`
|
||||
which are handled specially) so using [`OptimizationLevel::Full`] is usually quite safe _unless_
|
||||
custom types and functions are registered.
|
||||
|
||||
If custom functions are registered, they _may_ be called (or maybe not, if the calls happen to lie
|
||||
within a pruned code block).
|
||||
|
||||
If custom functions are registered to overload [built-in operators], they will also be called when
|
||||
the operators are used (in an [`if`] statement, for example), potentially causing side-effects.
|
||||
|
||||
```admonish tip.small "Rule of thumb"
|
||||
|
||||
* _Always_ register custom types and functions _after_ compiling scripts if [`OptimizationLevel::Full`] is used.
|
||||
|
||||
* _DO NOT_ depend on knowledge that the functions have no side-effects, because those functions can change later on and,
|
||||
when that happens, existing scripts may break in subtle ways.
|
||||
```
|
@@ -1,53 +0,0 @@
|
||||
Volatility Considerations for Full Optimization Level
|
||||
=====================================================
|
||||
|
||||
{{#include ../../links.md}}
|
||||
|
||||
Even if a custom function does not mutate state nor cause side-effects, it may still be _volatile_,
|
||||
i.e. it _depends_ on external environment and does not guarantee the same result for the same inputs.
|
||||
|
||||
A perfect example is a function that gets the current time – obviously each run will return a
|
||||
different value!
|
||||
|
||||
```rust
|
||||
print(get_current_time(true)); // prints the current time
|
||||
// notice the call to 'get_current_time'
|
||||
// has constant arguments
|
||||
|
||||
// The above, under full optimization level, is rewritten to:
|
||||
|
||||
print("10:25AM"); // the function call is replaced by
|
||||
// its result at the time of optimization!
|
||||
```
|
||||
|
||||
```admonish danger.small "Warning"
|
||||
|
||||
**Avoid using [`OptimizationLevel::Full`]** if volatile custom functions are involved.
|
||||
```
|
||||
|
||||
The optimizer, when using [`OptimizationLevel::Full`], _merrily assumes_ that all functions are
|
||||
_non-volatile_, so when it finds [constant] arguments (or none) it eagerly executes the function
|
||||
call and replaces it with the result.
|
||||
|
||||
This causes the script to behave differently from the intended semantics.
|
||||
|
||||
~~~admonish tip "Tip: Mark a function as volatile"
|
||||
|
||||
All native functions are assumed to be **non-volatile**, meaning that they are eagerly called under
|
||||
[`OptimizationLevel::Full`] when all arguments are [constant] (or none).
|
||||
|
||||
It is possible to [mark a function defined within a plugin module as volatile]({{rootUrl}}/plugins/module.md#volatile-functions)
|
||||
to prevent this behavior.
|
||||
|
||||
```rust
|
||||
#[export_module]
|
||||
mod my_module {
|
||||
// This function is marked 'volatile' and will not be
|
||||
// eagerly executed even under OptimizationLevel::Full.
|
||||
#[rhai_fn(volatile)]
|
||||
pub get_current_time(am_pm: bool) -> String {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
~~~
|
@@ -1,54 +0,0 @@
|
||||
Engine Configuration Options
|
||||
============================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
A number of other configuration options are available from the [`Engine`] to fine-tune behavior and safeguards.
|
||||
|
||||
|
||||
Compile-Time Language Features
|
||||
------------------------------
|
||||
|
||||
| Method | Description | Default |
|
||||
| ------------------------------------------------------------------ | -------------------------------------------------------------------------------------- | :-------------------------------------: |
|
||||
| `set_optimization_level`<br/>(not available under [`no_optimize`]) | sets the amount of script _optimizations_ performed (see [script optimization]) | [`Simple`][`OptimizationLevel::Simple`] |
|
||||
| `set_allow_if_expression` | allows/disallows [`if`-expressions]({{rootUrl}}/language/if.md#if-expression) | allow |
|
||||
| `set_allow_switch_expression` | allows/disallows [`switch` expressions]({{rootUrl}}/language/switch-expression.md) | allow |
|
||||
| `set_allow_loop_expressions` | allows/disallows loop expressions | allow |
|
||||
| `set_allow_statement_expression` | allows/disallows [statement expressions]({{rootUrl}}/language/statement-expression.md) | allow |
|
||||
| `set_allow_anonymous_fn`<br/>(not available under [`no_function`]) | allows/disallows [anonymous functions] | allow |
|
||||
| `set_allow_looping` | allows/disallows looping (i.e. [`while`], [`loop`], [`do`] and [`for`] statements) | allow |
|
||||
| `set_allow_shadowing` | allows/disallows _[shadowing]_ of [variables] | allow |
|
||||
| `set_strict_variables` | enables/disables [_Strict Variables_ mode][strict variables] | disabled |
|
||||
| `set_fast_operators` | enables/disables [_Fast Operators_ mode][fast operators] | enabled |
|
||||
| `disable_symbol` | disables a certain [keyword] or [operator] (see [disable keywords and operators]) | |
|
||||
|
||||
Beware that these options activate during _compile-time_ only. If an [`AST`] is compiled on an
|
||||
[`Engine`] but then evaluated on a different [`Engine`] with different configuration, disallowed
|
||||
features contained inside the [`AST`] will still run as normal.
|
||||
|
||||
|
||||
Runtime Behavior
|
||||
----------------
|
||||
|
||||
| Method | Description |
|
||||
| -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `set_fail_on_invalid_map_property`<br/>(not available under [`no_object`]) | sets whether to raise errors (instead of returning [`()`]) when invalid properties are accessed on [object maps] |
|
||||
| `set_default_tag` | sets the default value of the _custom state_ (which can be obtained via [`NativeCallContext::tag`][`NativeCallContext`]) for each evaluation run |
|
||||
|
||||
|
||||
Safety Limits
|
||||
-------------
|
||||
|
||||
| Method | Not available under | Description |
|
||||
| -------------------------- | :----------------------------: | --------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `set_max_expr_depths` | [`unchecked`] | sets the maximum nesting levels of an expression/statement (see [maximum statement depth]) |
|
||||
| `set_max_call_levels` | [`unchecked`], [`no_function`] | sets the maximum number of function call levels (default 50) to avoid infinite recursion (see [maximum call stack depth]) |
|
||||
| `set_max_operations` | [`unchecked`] | sets the maximum number of _operations_ that a script is allowed to consume (see [maximum number of operations]) |
|
||||
| `set_max_variables` | [`unchecked`] | sets the maximum number of [variables] that a script is allowed to define within a single [`Scope`] (see [maximum number of variables]) |
|
||||
| `set_max_functions` | [`unchecked`], [`no_function`] | sets the maximum number of [functions] that a script is allowed to define (see [maximum number of functions]) |
|
||||
| `set_max_modules` | [`unchecked`], [`no_modules`] | sets the maximum number of [modules] that a script is allowed to load (see [maximum number of modules]) |
|
||||
| `set_max_string_size` | [`unchecked`] | sets the maximum length (in UTF-8 bytes) for [strings] (see [maximum length of strings]) |
|
||||
| `set_max_array_size` | [`unchecked`], [`no_index`] | sets the maximum size for [arrays] (see [maximum size of arrays]) |
|
||||
| `set_max_map_size` | [`unchecked`], [`no_object`] | sets the maximum number of properties for [object maps] (see [maximum size of object maps]) |
|
||||
| `set_max_strings_interned` | | sets the maximum number of [strings] to be interned (if zero, the [strings interner] is disabled) |
|
@@ -1,28 +0,0 @@
|
||||
Operator Precedence
|
||||
===================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
All operators in Rhai has a _precedence_ indicating how tightly they bind.
|
||||
|
||||
A higher precedence binds more tightly than a lower precedence, so `*` and `/` binds before `+` and `-` etc.
|
||||
|
||||
When registering a custom operator, the operator's precedence must also be provided.
|
||||
|
||||
The following _precedence table_ shows the built-in precedence of standard Rhai operators:
|
||||
|
||||
| Category | Operators | Binding | Precedence (0-255) |
|
||||
| ------------------- | :--------------------------------------: | :-----: | :----------------: |
|
||||
| Logic and bit masks | <code>\|\|</code>, <code>\|</code>, `^` | left | 30 |
|
||||
| Logic and bit masks | `&&`, `&` | left | 60 |
|
||||
| Comparisons | `==`, `!=` | left | 90 |
|
||||
| Containment | [`in`] | left | 110 |
|
||||
| Comparisons | `>`, `>=`, `<`, `<=` | left | 130 |
|
||||
| Null-coalesce | `??` | left | 135 |
|
||||
| Ranges | `..`, `..=` | left | 140 |
|
||||
| Arithmetic | `+`, `-` | left | 150 |
|
||||
| Arithmetic | `*`, `/`, `%` | left | 180 |
|
||||
| Arithmetic | `**` | right | 190 |
|
||||
| Bit-shifts | `<<`, `>>` | left | 210 |
|
||||
| Unary operators | `+`, `-`, `!` | right | highest |
|
||||
| Object field access | `.`, `?.` | right | highest |
|
@@ -1,76 +0,0 @@
|
||||
Raw `Engine`
|
||||
============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
`Engine::new` creates a scripting [`Engine`] with common functionalities (e.g. printing to `stdout`
|
||||
via [`print`] or [`debug`]).
|
||||
|
||||
In many controlled embedded environments, however, these may not be needed and unnecessarily occupy
|
||||
application code storage space.
|
||||
|
||||
```admonish info.side.wide "Built-in operators"
|
||||
|
||||
Even with a raw [`Engine`], some operators are built-in and always available.
|
||||
|
||||
See [_Built-in Operators_][built-in operators] for a full list.
|
||||
```
|
||||
|
||||
Use `Engine::new_raw` to create a _raw_ [`Engine`], in which only a minimal set of
|
||||
[built-in][built-in operators] basic arithmetic and logical operators are supported.
|
||||
|
||||
To add more functionalities to a _raw_ [`Engine`], load [packages] into it.
|
||||
|
||||
Since [packages] can be _shared_, this is an extremely efficient way to create multiple instances of
|
||||
the same [`Engine`] with the same set of functions.
|
||||
|
||||
| | `Engine::new` | `Engine::new_raw` |
|
||||
| --------------------- | :------------------: | :---------------: |
|
||||
| [Built-in operators] | yes | yes |
|
||||
| [Package] loaded | `StandardPackage` | _none_ |
|
||||
| [Module resolver] | `FileModuleResolver` | _none_ |
|
||||
| [Strings interner] | yes | _no_ |
|
||||
| [`on_print`][`print`] | yes | _none_ |
|
||||
| [`on_debug`][`debug`] | yes | _none_ |
|
||||
|
||||
|
||||
```admonish warning.small "Warning: No strings interner"
|
||||
|
||||
A _raw_ [`Engine`] disables the _[strings interner]_ by default.
|
||||
|
||||
This may lead to a significant increase in memory usage if many strings are created in scripts.
|
||||
|
||||
Turn the _[strings interner]_ back on via [`Engine::set_max_strings_interned`][options].
|
||||
```
|
||||
|
||||
~~~admonish example "`Engine::new` is equivalent to..."
|
||||
```rust
|
||||
use rhai::module_resolvers::FileModuleResolver;
|
||||
use rhai::packages::StandardPackage;
|
||||
|
||||
// Create a raw scripting Engine
|
||||
let mut engine = Engine::new_raw();
|
||||
|
||||
// Use the file-based module resolver
|
||||
engine.set_module_resolver(FileModuleResolver::new());
|
||||
|
||||
// Enable the strings interner
|
||||
engine.set_max_strings_interned(1024);
|
||||
|
||||
// Default print/debug implementations
|
||||
engine.on_print(|text| println!("{text}"));
|
||||
|
||||
engine.on_debug(|text, source, pos| match (source, pos) {
|
||||
(Some(source), Position::NONE) => println!("{source} | {text}"),
|
||||
(Some(source), pos) => println!("{source} @ {pos:?} | {text}"),
|
||||
(None, Position::NONE) => println!("{text}"),
|
||||
(None, pos) => println!("{pos:?} | {text}"),
|
||||
});
|
||||
|
||||
// Register the Standard Package
|
||||
let package = StandardPackage::new();
|
||||
|
||||
// Load the package into the [`Engine`]
|
||||
package.register_into_engine(&mut engine);
|
||||
```
|
||||
~~~
|
@@ -1,215 +0,0 @@
|
||||
`Scope` – Maintaining State
|
||||
=================================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
By default, Rhai treats each [`Engine`] invocation as a fresh one, persisting only the functions
|
||||
that have been registered 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
|
||||
(a `Scope`) must be manually created and passed in.
|
||||
|
||||
All `Scope` [variables] and [constants] have values that 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.
|
||||
|
||||
```admonish info.small "Shadowing"
|
||||
|
||||
A newly-added [variable] or [constant] _[shadows][shadow]_ previous ones of the same name.
|
||||
|
||||
In other words, all versions are kept for [variables] and [constants], but only the latest ones can
|
||||
be accessed via `get_value<T>`, `get_mut<T>` and `set_value<T>`.
|
||||
|
||||
Essentially, a `Scope` is always searched in _reverse order_.
|
||||
```
|
||||
|
||||
```admonish tip.small "Tip: The lifetime parameter"
|
||||
|
||||
`Scope` has a _lifetime_ parameter, in the vast majority of cases it can be omitted and
|
||||
automatically inferred to be `'static`.
|
||||
|
||||
Currently, that lifetime parameter is not used. It is there to maintain backwards compatibility
|
||||
as well as for possible future expansion when references can also be put into the `Scope`.
|
||||
|
||||
The lifetime parameter is not guaranteed to remain unused for future versions.
|
||||
|
||||
In order to put a `Scope` into a `struct`, use `Scope<'static>`.
|
||||
```
|
||||
|
||||
~~~admonish tip.small "Tip: The `const` generic parameter"
|
||||
|
||||
`Scope` also has a _`const` generic_ parameter, which is a number that defaults to 8.
|
||||
It indicates the number of entries that the `Scope` can keep _inline_ without allocations.
|
||||
|
||||
The larger this number, the larger the `Scope` type gets, but allocations will happen far
|
||||
less frequently.
|
||||
|
||||
A smaller number makes `Scope` smaller, but allocation costs will be incurred when the
|
||||
number of entries exceed the _inline_ capacity.
|
||||
~~~
|
||||
|
||||
|
||||
`Scope` API
|
||||
-----------
|
||||
|
||||
| Method | Description |
|
||||
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `new` _instance method_ | create a new empty `Scope` |
|
||||
| `with_capacity` _instance method_ | create a new empty `Scope` with a specified initial capacity |
|
||||
| `len` | number of [variables]/[constants] currently within the `Scope` |
|
||||
| `rewind` | _rewind_ (i.e. reset) the `Scope` to a particular number of [variables]/[constants] |
|
||||
| `clear` | remove all [variables]/[constants] from the `Scope`, making it empty |
|
||||
| `is_empty` | is the `Scope` empty? |
|
||||
| `is_constant` | is the particular [variable]/[constant] in the `Scope` a [constant]? |
|
||||
| `push`, `push_constant` | add a new [variable]/[constant] into the `Scope` with a specified value |
|
||||
| `push_dynamic`, `push_constant_dynamic` | add a new [variable]/[constant] into the `Scope` with a [`Dynamic`] value |
|
||||
| `set_or_push<T>` | set the value of the last [variable] within the `Scope` by name if it exists and is not [constant]; add a new [variable] into the `Scope` otherwise |
|
||||
| `contains` | does the particular [variable] or [constant] exist in the `Scope`? |
|
||||
| `get_value<T>` | get the value of the last [variable]/[constant] within the `Scope` by name |
|
||||
| `set_value<T>` | set the value of the last [variable] within the `Scope` by name, panics if it is [constant] |
|
||||
| `remove<T>` | remove the last [variable]/[constant] from the `Scope` by name, returning its value |
|
||||
| `get` | get a reference to the value of the last [variable]/[constant] within the `Scope` by name |
|
||||
| `get_mut` | get a reference to the value of the last [variable] within the `Scope` by name, `None` if it is [constant] |
|
||||
| `set_alias` | [exported][`export`] the last [variable]/[constant] within the `Scope` by name |
|
||||
| `iter`, `iter_raw`, `IntoIterator::into_iter` | get an iterator to the [variables]/[constants] within the `Scope` |
|
||||
| `Extend::extend` | add [variables]/[constants] to the `Scope` |
|
||||
|
||||
~~~admonish info.small "`Scope` public API"
|
||||
|
||||
For details on the `Scope` API, refer to the
|
||||
[documentation](https://docs.rs/rhai/{{version}}/rhai/struct.Scope.html) online.
|
||||
~~~
|
||||
|
||||
|
||||
Serializing/Deserializing
|
||||
-------------------------
|
||||
|
||||
With the [`serde`] feature, `Scope` is serializable and deserializable via
|
||||
[`serde`](https://crates.io/crates/serde).
|
||||
|
||||
[Custom types] stored in the `Scope`, however, are serialized as full type-name strings.
|
||||
Data in [custom types] are not serialized.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
In the following example, a `Scope` is created with a few initialized variables, then it is threaded
|
||||
through multiple evaluations.
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, Scope, 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')
|
||||
// and f64 (f32 if 'f32_float').
|
||||
// Better stick to them or it gets hard working with the script.
|
||||
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!"); // 'set_value' adds a new variable when one doesn't exist
|
||||
|
||||
// First invocation
|
||||
engine.run_with_scope(&mut scope,
|
||||
"
|
||||
let x = 4 + 5 - y + z + MY_NUMBER + s.len;
|
||||
y = 1;
|
||||
")?;
|
||||
|
||||
// Second invocation using the same state.
|
||||
// Notice that the new variable 'x', defined previously, is still here.
|
||||
let result = engine.eval_with_scope::<i64>(&mut scope, "x + y")?;
|
||||
|
||||
println!("result: {result}"); // prints 1103
|
||||
|
||||
// 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);
|
||||
```
|
||||
|
||||
|
||||
`Engine` API Using `Scope`
|
||||
--------------------------
|
||||
|
||||
[`Engine`] API methods that accept a `Scope` parameter all end in `_with_scope`, making that
|
||||
`Scope` (and everything inside it) available to the script:
|
||||
|
||||
| `Engine` API | Not available under |
|
||||
| --------------------------------------- | :-----------------: |
|
||||
| `Engine::eval_with_scope` | |
|
||||
| `Engine::eval_ast_with_scope` | |
|
||||
| `Engine::eval_file_with_scope` | [`no_std`] |
|
||||
| `Engine::eval_expression_with_scope` | |
|
||||
| `Engine::run_with_scope` | |
|
||||
| `Engine::run_ast_with_scope` | |
|
||||
| `Engine::run_file_with_scope` | [`no_std`] |
|
||||
| `Engine::compile_file_with_scope` | [`no_std`] |
|
||||
| `Engine::compile_expression_with_scope` | |
|
||||
|
||||
~~~admonish danger "Don't forget to `rewind`"
|
||||
|
||||
[Variables] or [constants] defined at the global level of a script persist inside the custom `Scope`
|
||||
even after the script ends.
|
||||
|
||||
```rust
|
||||
let mut scope = Scope::new();
|
||||
|
||||
engine.run_with_scope(&mut scope, "let x = 42;")?;
|
||||
|
||||
// Variable 'x' stays inside the custom scope!
|
||||
engine.run_with_scope(&mut scope, "print(x);")?; // prints 42
|
||||
```
|
||||
|
||||
Due to [variable shadowing][shadowing], new [variables]/[constants] are simply added on top of
|
||||
existing ones (even when they already exist), so care must be taken that new [variables]/[constants]
|
||||
inside the custom `Scope` do not grow without bounds.
|
||||
|
||||
```rust
|
||||
let mut scope = Scope::new();
|
||||
|
||||
// Don't do this - this creates 1 million variables named 'x'
|
||||
// inside 'scope'!!!
|
||||
for _ in 0..1_000_000 {
|
||||
engine.run_with_scope(&mut scope, "let x = 42;")?;
|
||||
}
|
||||
|
||||
// The 'scope' contains a LOT of variables...
|
||||
assert_eq!(scope.len(), 1_000_000);
|
||||
|
||||
// Variable 'x' stays inside the custom scope!
|
||||
engine.run_with_scope(&mut scope, "print(x);")?; // prints 42
|
||||
```
|
||||
|
||||
In order to remove [variables] or [constants] introduced by a script, use the `rewind` method.
|
||||
|
||||
```rust
|
||||
// Run a million times
|
||||
for _ in 0..1_000_000 {
|
||||
// Save the current size of the 'scope'
|
||||
let orig_scope_size = scope.len();
|
||||
|
||||
engine.run_with_scope(&mut scope, "let x = 42;")?;
|
||||
|
||||
// Rewind the 'scope' to the original size
|
||||
scope.rewind(orig_scope_size);
|
||||
}
|
||||
|
||||
// The 'scope' is empty
|
||||
assert_eq!(scope.len(), 0);
|
||||
|
||||
// Variable 'x' is no longer inside 'scope'!
|
||||
engine.run_with_scope(&mut scope, "print(x);")?; // error: variable 'x' not found
|
||||
```
|
||||
~~~
|
@@ -1,75 +0,0 @@
|
||||
Strict Variables Mode
|
||||
=====================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
~~~admonish tip.side "`Scope` constants"
|
||||
|
||||
[Constants] in the external [`Scope`], when provided, count as definition.
|
||||
~~~
|
||||
|
||||
By default, Rhai looks up access to [variables] from the enclosing block scope,
|
||||
working its way outwards until it reaches the top (global) level, then it
|
||||
searches the [`Scope`] (if any) that is passed into the `Engine::eval_with_scope` call.
|
||||
|
||||
Setting [`Engine::set_strict_variables`][options] to `true` turns on _Strict Variables Mode_,
|
||||
which requires that:
|
||||
|
||||
* all [variables]/[constants] be defined within the same script before use,
|
||||
or they must be [variables]/[constants] within the provided [`Scope`] (if any),
|
||||
* [modules] must be [imported][`import`], also within the same script, before use.
|
||||
|
||||
Within _Strict Variables_ mode, any attempt to access a [variable] or [module] before
|
||||
definition/[import][`import`] results in a parse error.
|
||||
|
||||
This way, variable access errors (usually typos) are caught during compile time instead of runtime.
|
||||
|
||||
```rust
|
||||
let x = 42;
|
||||
|
||||
let y = x * z; // <- parse error under strict variables mode:
|
||||
// variable 'z' is not yet defined
|
||||
|
||||
let z = x + w; // <- parse error under strict variables mode:
|
||||
// variable 'w' is undefined
|
||||
|
||||
foo::bar::baz(); // <- parse error under strict variables mode:
|
||||
// module 'foo' is not yet defined
|
||||
|
||||
fn test1() {
|
||||
foo::bar::baz(); // <- parse error under strict variables mode:
|
||||
// module 'foo' is defined
|
||||
}
|
||||
|
||||
import "my_module" as foo;
|
||||
|
||||
foo::bar::baz(); // ok!
|
||||
|
||||
print(foo::xyz); // ok!
|
||||
|
||||
let x = abc::def; // <- parse error under strict variables mode:
|
||||
// module 'abc' is undefined
|
||||
|
||||
fn test2() {
|
||||
foo:bar::baz(); // ok!
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
```admonish question "TL;DR – Why isn't there a _Strict Functions_ mode?"
|
||||
|
||||
Why can't function calls be checked for validity as well?
|
||||
|
||||
Rust functions in Rhai can be [overloaded][function overloading]. This means that multiple versions of
|
||||
the same Rust function can exist under the same name, each accepting different numbers and/or types
|
||||
of arguments.
|
||||
|
||||
While it is possible to check, at compile time, whether a [variable] has been previously declared,
|
||||
it is impossible to predict, at compile time, the _types_ of arguments to function calls, unless the
|
||||
function in question takes no parameters.
|
||||
|
||||
Therefore, it is impossible to check, at compile time, whether a function call is valid given that
|
||||
the types of arguments are unknown until runtime. QED.
|
||||
|
||||
Not to mention that it is also impossible to check for a function called via a [function pointer].
|
||||
```
|
@@ -1,79 +0,0 @@
|
||||
Remap Tokens During Parsing
|
||||
===========================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
[`Engine::on_parse_token`]: https://docs.rs/rhai/{{version}}/rhai/struct.Engine.html#method.on_parse_token
|
||||
[`Token`]: https://docs.rs/rhai/{{version}}/rhai/enum.Token.html
|
||||
|
||||
|
||||
The Rhai [`Engine`] first parses a script into a stream of _tokens_.
|
||||
|
||||
Tokens have the type [`Token`] which is only exported under [`internals`].
|
||||
|
||||
The function [`Engine::on_parse_token`], available only under [`internals`], allows registration of a
|
||||
_mapper function_ that converts (remaps) a [`Token`] into another.
|
||||
|
||||
|
||||
```admonish tip.small "Hot Tips: Use as safety checks"
|
||||
|
||||
Since it is called for _every_ token parsed from the script, this token mapper function
|
||||
can also be used to implement _safety checks_ against, say, stack-overflow or out-of-memory
|
||||
situations during parsing.
|
||||
|
||||
See [here][memory] for more details.
|
||||
```
|
||||
|
||||
|
||||
Function Signature
|
||||
------------------
|
||||
|
||||
```admonish tip.side "Tip: Raising errors"
|
||||
|
||||
Raise a parse error by returning [`Token::LexError`](https://docs.rs/rhai/{{version}}/rhai/enum.Token.html#variant.LexError)
|
||||
as the mapped token.
|
||||
```
|
||||
|
||||
The function signature passed to [`Engine::on_parse_token`] takes the following form.
|
||||
|
||||
> ```rust
|
||||
> Fn(token: Token, pos: Position, state: &TokenizeState) -> Token
|
||||
> ```
|
||||
|
||||
where:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | :---------------------------------------------------------------------------------: | -------------------------------- |
|
||||
| `token` | [`Token`] | the next symbol parsed |
|
||||
| `pos` | `Position` | location of the [token][`Token`] |
|
||||
| `state` | [`&TokenizeState`](https://docs.rs/rhai/{{version}}/rhai/struct.TokenizeState.html) | current state of the tokenizer |
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```rust
|
||||
use rhai::{Engine, FLOAT, Token};
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register a token mapper function.
|
||||
engine.on_parse_token(|token, pos, state| {
|
||||
match token {
|
||||
// Change 'begin' ... 'end' to '{' ... '}'
|
||||
Token::Identifier(s) if &s == "begin" => Token::LeftBrace,
|
||||
Token::Identifier(s) if &s == "end" => Token::RightBrace,
|
||||
|
||||
// Change all integer literals to floating-point
|
||||
Token::IntegerConstant(n) => Token::FloatConstant((n as FLOAT).into()),
|
||||
|
||||
// Disallow '()'
|
||||
Token::Unit => Token::LexError(
|
||||
LexError::ImproperSymbol("()".to_string(), "".to_string()).into()
|
||||
),
|
||||
|
||||
// Pass through all other tokens unchanged
|
||||
_ => token
|
||||
}
|
||||
});
|
||||
```
|
@@ -1,96 +0,0 @@
|
||||
Variable Resolver
|
||||
=================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
[`Engine::on_var`]: https://docs.rs/rhai/{{version}}/rhai/struct.Engine.html#method.on_var
|
||||
|
||||
|
||||
By default, Rhai looks up access to [variables] from the enclosing block scope, working its way
|
||||
outwards until it reaches the top (global) level, then it searches the [`Scope`] that is passed into
|
||||
the `Engine::eval` call.
|
||||
|
||||
There is a built-in facility for advanced users to _hook_ into the [variable] resolution service and
|
||||
to override its default behavior.
|
||||
|
||||
To do so, provide a closure to the [`Engine`] via [`Engine::on_var`].
|
||||
|
||||
```rust
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register a variable resolver.
|
||||
engine.on_var(|name, index, context| {
|
||||
match name {
|
||||
"MYSTIC_NUMBER" => Ok(Some(42_i64.into())),
|
||||
// Override a variable - make it not found even if it exists!
|
||||
"DO_NOT_USE" => Err(EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::NONE).into()),
|
||||
// Silently maps 'chameleon' into 'innocent'.
|
||||
"chameleon" => context.scope().get_value("innocent").map(Some).ok_or_else(||
|
||||
EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::NONE).into()
|
||||
),
|
||||
// Return Ok(None) to continue with the normal variable resolution process.
|
||||
_ => Ok(None)
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
```admonish info.small "Benefits of using a variable resolver"
|
||||
|
||||
1. Avoid having to maintain a custom [`Scope`] with all [variables] regardless of need
|
||||
(because a script may not use them all).
|
||||
|
||||
2. _Short-circuit_ [variable] access, essentially overriding standard behavior.
|
||||
|
||||
3. _Lazy-load_ [variables] when they are accessed, not up-front.
|
||||
This benefits when the number of [variables] is very large, when they are timing-dependent,
|
||||
or when they are expensive to load.
|
||||
|
||||
4. Rename system [variables] on a script-by-script basis without having to construct different [`Scope`]'s.
|
||||
```
|
||||
|
||||
```admonish warning.small "Returned values are constants"
|
||||
|
||||
[Variable] values returned by a variable resolver are treated as _[constants]_.
|
||||
|
||||
This is to avoid needing a mutable reference to the underlying data provider which may not be possible to obtain.
|
||||
|
||||
To change these [variables], better push them into a custom [`Scope`] instead of
|
||||
using a variable resolver.
|
||||
```
|
||||
|
||||
```admonish tip.small "Tip: Returning shared values"
|
||||
|
||||
It is possible to return a _shared_ value from a variable resolver.
|
||||
|
||||
This is one way to implement [Mutable Global State]({{rootUrl}}/patterns/global-mutable-state.md).
|
||||
```
|
||||
|
||||
|
||||
Function Signature
|
||||
------------------
|
||||
|
||||
The function signature passed to [`Engine::on_var`] takes the following form.
|
||||
|
||||
> ```rust
|
||||
> Fn(name: &str, index: usize, context: EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>>
|
||||
> ```
|
||||
|
||||
where:
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | :-------------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `name` | `&str` | [variable] name |
|
||||
| `index` | `usize` | an offset from the bottom of the current [`Scope`] that the [variable] is supposed to reside.<br/>Offsets start from 1, with 1 meaning the last [variable] in the current [`Scope`]. Essentially the correct [variable] is at position `scope.len() - index`.<br/>If `index` is zero, then there is no pre-calculated offset position and a search through the current [`Scope`] must be performed. |
|
||||
| `context` | [`EvalContext`] | mutable reference to the current _evaluation context_ |
|
||||
|
||||
and [`EvalContext`] is a type that encapsulates the current _evaluation context_.
|
||||
|
||||
### Return value
|
||||
|
||||
The return value is `Result<Option<Dynamic>, Box<EvalAltResult>>` where:
|
||||
|
||||
| Value | Description |
|
||||
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `Ok(None)` | normal [variable] resolution process should continue, i.e. continue searching through the [`Scope`] |
|
||||
| `Ok(Some(value))` | value (a [`Dynamic`]) of the [variable], treated as a [constant] |
|
||||
| `Err(Box<EvalAltResult>)` | error that is reflected back to the [`Engine`], normally `EvalAltResult::ErrorVariableNotFound` to indicate that the [variable] does not exist, but it can be any `EvalAltResult`. |
|
Reference in New Issue
Block a user