201 lines
5.9 KiB
Markdown
201 lines
5.9 KiB
Markdown
# Rhai Module: `src/interpreter/rhei.rs`
|
|
|
|
Integration of the [Rhai](https://rhai.rs/) scripting engine — compilation, AST caching, and expression/script execution.
|
|
|
|
---
|
|
|
|
## `RHEI_PREFIX` — Rhai expression prefix
|
|
|
|
```rust
|
|
pub const RHEI_PREFIX: &str = "__rhei:";
|
|
```
|
|
|
|
A marker constant for element properties whose content should be interpreted as Rhai expressions. Used in `collect_and_precompile()` to select properties of the form `!rhei:...`.
|
|
|
|
---
|
|
|
|
## `RheiContext` — Rhai execution context
|
|
|
|
```rust
|
|
pub struct RheiContext {
|
|
engine: Engine,
|
|
init_ast: AST,
|
|
scope: RefCell<Scope<'static>>,
|
|
action_cache: RefCell<HashMap<String, AST>>,
|
|
expr_cache: RefCell<HashMap<String, AST>>,
|
|
}
|
|
```
|
|
|
|
| Field | Purpose |
|
|
|---|---|
|
|
| `engine` | Configured `rhai::Engine` instance |
|
|
| `init_ast` | Merged AST of all initialization scripts (including function definitions) |
|
|
| `scope` | Shared scope (`Scope`) shared across calls; wrapped in `RefCell` for interior mutability |
|
|
| `action_cache` | Cache of compiled scripts (actions), keyed by source code |
|
|
| `expr_cache` | Cache of compiled expressions, keyed by source code |
|
|
|
|
---
|
|
|
|
## Constructors
|
|
|
|
### `new(scripts)`
|
|
|
|
```rust
|
|
pub fn new(scripts: &[String]) -> Self
|
|
```
|
|
|
|
Creates a context via `new_empty()` and immediately precompiles all provided scripts by calling `precompile_scripts()`.
|
|
|
|
### `new_empty(scripts)`
|
|
|
|
```rust
|
|
fn new_empty(scripts: &[String]) -> Self
|
|
```
|
|
|
|
1. Creates `Engine::new()`.
|
|
2. Configures `on_print` (outputs to stdout with `[rhei]` prefix) and `on_debug` (outputs to stderr) handlers.
|
|
3. Compiles all scripts and merges their ASTs into a single tree via `merge()`. Compilation errors for individual blocks are logged but do not abort the process.
|
|
4. Creates a module (`Module::eval_ast_as_new`) from the merged AST with an empty scope — this registers global functions defined in the scripts. The module is registered in the engine as a global module (`register_global_module`).
|
|
5. Initializes an empty `Scope`, empty `action_cache` and `expr_cache` caches.
|
|
|
|
---
|
|
|
|
## `sync_scope()` — variable synchronization
|
|
|
|
```rust
|
|
pub fn sync_scope(&self, variables: &HashMap<String, Value>)
|
|
```
|
|
|
|
Synchronizes values from an external `HashMap` into the Rhai `Scope`:
|
|
- If a variable already exists in the scope and its value has not changed — skip.
|
|
- If a variable exists — update it via `set_value()`.
|
|
- If a variable does not exist — add it via `push_dynamic()`.
|
|
|
|
Conversion `Value → Dynamic` is performed via `value_to_dynamic()`.
|
|
|
|
---
|
|
|
|
## `initialize()` — initialization
|
|
|
|
```rust
|
|
pub fn initialize(&self, variables: &mut HashMap<String, Value>)
|
|
```
|
|
|
|
1. Synchronizes variables via `sync_scope()`.
|
|
2. Runs `init_ast` (merged AST of all scripts) via `run_ast_with_scope()`.
|
|
3. Iterates over all scope variables via `iter_raw()` and writes back into the `HashMap` those whose values have changed.
|
|
|
|
---
|
|
|
|
## `eval_expr()` — expression evaluation
|
|
|
|
```rust
|
|
pub fn eval_expr(&self, expr: &str, variables: &HashMap<String, Value>) -> Value
|
|
```
|
|
|
|
1. Synchronizes variables.
|
|
2. Obtains (compiles or fetches from cache) the expression AST via `get_or_compile_expr()`.
|
|
3. Executes via `eval_ast_with_scope::<Dynamic>()`.
|
|
4. Converts the result `Dynamic → Value` via `dynamic_to_value()`.
|
|
5. On error returns `Value::None`.
|
|
|
|
---
|
|
|
|
## `eval_condition()` — condition evaluation
|
|
|
|
```rust
|
|
pub fn eval_condition(&self, expr: &str, variables: &HashMap<String, Value>) -> bool
|
|
```
|
|
|
|
Similar to `eval_expr()`, but typed as `bool`. On error returns `false`.
|
|
|
|
---
|
|
|
|
## `execute_action()` — script execution
|
|
|
|
```rust
|
|
pub fn execute_action(&self, script: &str, variables: &mut HashMap<String, Value>)
|
|
```
|
|
|
|
1. Synchronizes variables.
|
|
2. Obtains the script AST via `get_or_compile_action()`.
|
|
3. Executes via `run_ast_with_scope()`.
|
|
4. After execution iterates over the scope and writes changed variables back into the `HashMap`.
|
|
|
|
---
|
|
|
|
## AST Caching
|
|
|
|
### `get_or_compile_action(script)`
|
|
|
|
```rust
|
|
fn get_or_compile_action(&self, script: &str) -> Option<AST>
|
|
```
|
|
|
|
Checks `action_cache`. On miss compiles via `engine.compile()`, stores in cache.
|
|
|
|
### `get_or_compile_expr(expr)`
|
|
|
|
```rust
|
|
fn get_or_compile_expr(&self, expr: &str) -> Option<AST>
|
|
```
|
|
|
|
Checks `expr_cache`. On miss compiles via `engine.compile_expression()`, stores in cache.
|
|
|
|
Both methods log the error on compilation failure and return `None`.
|
|
|
|
---
|
|
|
|
## Batch Precompilation
|
|
|
|
```rust
|
|
pub fn precompile_scripts(&self, scripts: &[String])
|
|
pub fn precompile_actions(&self, actions: &[String])
|
|
pub fn precompile_exprs(&self, exprs: &[String])
|
|
pub fn precompile_all_from_doc(&self, doc: &super::Document)
|
|
```
|
|
|
|
| Method | Action |
|
|
|---|---|
|
|
| `precompile_scripts` | Compiles each script as an action |
|
|
| `precompile_actions` | Same as `precompile_scripts` (alias) |
|
|
| `precompile_exprs` | Compiles each expression |
|
|
| `precompile_all_from_doc` | Compiles all `doc.rhei_scripts` and recursively traverses the element tree |
|
|
|
|
### `collect_and_precompile()`
|
|
|
|
```rust
|
|
fn collect_and_precompile(el: &super::Element, ctx: &RheiContext)
|
|
```
|
|
|
|
Recursively traverses the `Element` tree:
|
|
- For properties starting with `__on:*` and non-empty — compiles as an action.
|
|
- For properties starting with `RHEI_PREFIX` (`!rhei:`) — compiles the remaining part as an expression.
|
|
|
|
---
|
|
|
|
## Type Conversion
|
|
|
|
### `value_to_dynamic(v: &Value) -> Dynamic`
|
|
|
|
```rust
|
|
Value::Int(i) → Dynamic::from(*i)
|
|
Value::Float(f) → Dynamic::from(*f)
|
|
Value::Bool(b) → Dynamic::from(*b)
|
|
Value::Str(s) → str_to_dynamic(s)
|
|
Value::None → Dynamic::UNIT
|
|
Value::Array(a) → Dynamic::from_iter(value_to_dynamic of each element)
|
|
```
|
|
|
|
### `dynamic_to_value(d: &Dynamic) -> Value`
|
|
|
|
Checks the type via `is_string()`, `is_int()`, `is_float()`, `is_bool()`, `is_array()` in priority order. If the type is not recognized — returns `Value::None`.
|
|
|
|
### `str_to_dynamic(s: &str) -> Dynamic`
|
|
|
|
A heuristic string parser that tries sequentially:
|
|
1. `s.parse::<i64>()` — integer
|
|
2. `s.parse::<f64>()` — float
|
|
3. `s.parse::<bool>()` — boolean
|
|
4. Otherwise — `Dynamic::from(s)` as a string
|