reorganize module

This commit is contained in:
Timur Gordon
2025-04-04 08:28:07 +02:00
parent 1ea37e2e7f
commit 939b6b4e57
375 changed files with 7580 additions and 191 deletions

View File

@@ -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 &ndash; 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) |

View File

@@ -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. `+`).
```

View File

@@ -1,275 +0,0 @@
Call Rhai Functions from Rust
=============================
{{#include ../links.md}}
Rhai also allows working _backwards_ from the other direction &ndash; 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);
```

View File

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

View File

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

View File

@@ -1,246 +0,0 @@
Really Advanced &ndash; 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 &ndash; 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 &ndash; 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 &ndash; 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`] &ndash; 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}"))
}
}
);
```

View File

@@ -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 &ndash; 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$` &ndash; any valid expression, statement or statements block.
* `$block$` &ndash; any valid statements block (i.e. must be enclosed by `{` ... `}`).
* `$func$` &ndash; any valid [closure], or any valid statements block as the body of a [closure] with no parameters (if not [`no_function`]).
* `$ident$` &ndash; any [variable] name.
* `$symbol$` &ndash; any [symbol][operator], active or reserved.
* `$bool$` &ndash; a boolean value.
* `$int$` &ndash; an integer number.
* `$float$` &ndash; a floating-point number (if not [`no_float`]).
* `$string$` &ndash; 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 &ndash; 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 &ndash; 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 &ndash; 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™ &ndash; unless you are certain
of what you're doing.
### Step Five &ndash; Document
For custom syntax, documentation is crucial.
Make sure there are _lots_ of examples for users to follow.
### Step Six &ndash; Profit!
Practical Example &ndash; 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 &ndash; 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 &ndash; 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 &ndash; 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())
}
})?;
```

View File

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

View File

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

View File

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

View File

@@ -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 &ndash; 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 &ndash;
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)
}
}
```

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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] &ndash;
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"` &ndash; math operator
* `inputs[1] = "price"` &ndash; field name
* `inputs[2] = "row"` &ndash; loop variable name
* `inputs[3] = Expression(table)` &ndash; data source
* `inputs[4] = Expression(row.weight > 50)` &ndash; 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].

View File

@@ -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_ &ndash; 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.
```

View File

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

View File

@@ -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`) &ndash;
not even [variable] assignment &ndash; 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,
}
")?;
```
~~~

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 */",
...
]
}
```

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 &ndash; 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.

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 &ndash;
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 &ndash; 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.

View File

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

View File

@@ -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 &ndash; 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 {
// ...
}
}
```
~~~

View File

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

View File

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

View File

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

View File

@@ -1,215 +0,0 @@
`Scope` &ndash; 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
```
~~~

View File

@@ -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 &ndash; 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].
```

View File

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

View File

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