# 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>, action_cache: RefCell>, expr_cache: RefCell>, } ``` | 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) ``` 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) ``` 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) -> 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::()`. 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) -> 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) ``` 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 ``` 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 ``` 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::()` — integer 2. `s.parse::()` — float 3. `s.parse::()` — boolean 4. Otherwise — `Dynamic::from(s)` as a string