# Модуль Rhai: `src/interpreter/rhei.rs` Интеграция скриптового движка [Rhai](https://rhai.rs/) — компиляция, кеширование AST и выполнение выражений/скриптов. --- ## `RHEI_PREFIX` — префикс Rhai-выражений ```rust pub const RHEI_PREFIX: &str = "__rhei:"; ``` Константа-маркер для свойств элементов, содержимое которых должно интерпретироваться как Rhai-выражение. Используется в `collect_and_precompile()` для выборки свойств вида `!rhei:...`. --- ## `RheiContext` — контекст выполнения Rhai ```rust pub struct RheiContext { engine: Engine, init_ast: AST, scope: RefCell>, action_cache: RefCell>, expr_cache: RefCell>, } ``` | Поле | Назначение | |---|---| | `engine` | Настроенный экземпляр `rhai::Engine` | | `init_ast` | Объединённое AST всех скриптов инициализации (включая определения функций) | | `scope` | Общая область видимости (`Scope`), разделяемая между вызовами; обёрнута в `RefCell` для interior mutability | | `action_cache` | Кеш скомпилированных скриптов (действий), ключ — исходный код | | `expr_cache` | Кеш скомпилированных выражений, ключ — исходный код | --- ## Конструкторы ### `new(scripts)` ```rust pub fn new(scripts: &[String]) -> Self ``` Создаёт контекст через `new_empty()` и сразу прекомпилирует все переданные скрипты вызовом `precompile_scripts()`. ### `new_empty(scripts)` ```rust fn new_empty(scripts: &[String]) -> Self ``` 1. Создаёт `Engine::new()`. 2. Настраивает обработчики `on_print` (вывод в stdout с префиксом `[rhei]`) и `on_debug` (вывод в stderr). 3. Компилирует все скрипты и сливает их AST в единое дерево через `merge()`. Ошибки компиляции отдельных блоков логируются, но не прерывают процесс. 4. Из объединённого AST создаёт модуль (`Module::eval_ast_as_new`) с пустым скопом — в нём регистрируются глобальные функции, определённые в скриптах. Модуль регистрируется в движке как глобальный (`register_global_module`). 5. Инициализирует пустой `Scope`, пустые кеши `action_cache` и `expr_cache`. --- ## `sync_scope()` — синхронизация переменных ```rust pub fn sync_scope(&self, variables: &HashMap) ``` Синхронизирует значения из внешнего `HashMap` в Rhai `Scope`: - Если переменная уже есть в скопе и её значение не изменилось — пропускает. - Если переменная есть — обновляет через `set_value()`. - Если переменной нет — добавляет через `push_dynamic()`. Преобразование `Value → Dynamic` выполняется через `value_to_dynamic()`. --- ## `initialize()` — инициализация ```rust pub fn initialize(&self, variables: &mut HashMap) ``` 1. Синхронизирует переменные через `sync_scope()`. 2. Запускает `init_ast` (объединённое AST всех скриптов) через `run_ast_with_scope()`. 3. Обходит все переменные скопа через `iter_raw()` и записывает обратно в `HashMap` те, чьи значения изменились. --- ## `eval_expr()` — вычисление выражения ```rust pub fn eval_expr(&self, expr: &str, variables: &HashMap) -> Value ``` 1. Синхронизирует переменные. 2. Получает (компилирует или берёт из кеша) AST выражения через `get_or_compile_expr()`. 3. Выполняет через `eval_ast_with_scope::()`. 4. Преобразует результат `Dynamic → Value` через `dynamic_to_value()`. 5. При ошибке возвращает `Value::None`. --- ## `eval_condition()` — вычисление условия ```rust pub fn eval_condition(&self, expr: &str, variables: &HashMap) -> bool ``` Аналогичен `eval_expr()`, но типизирован как `bool`. При ошибке возвращает `false`. --- ## `execute_action()` — выполнение скрипта ```rust pub fn execute_action(&self, script: &str, variables: &mut HashMap) ``` 1. Синхронизирует переменные. 2. Получает AST скрипта через `get_or_compile_action()`. 3. Выполняет через `run_ast_with_scope()`. 4. После выполнения обходит скоп и записывает обратно в `HashMap` изменившиеся переменные. --- ## Кеширование AST ### `get_or_compile_action(script)` ```rust fn get_or_compile_action(&self, script: &str) -> Option ``` Проверяет `action_cache`. При промахе компилирует через `engine.compile()`, сохраняет в кеш. ### `get_or_compile_expr(expr)` ```rust fn get_or_compile_expr(&self, expr: &str) -> Option ``` Проверяет `expr_cache`. При промахе компилирует через `engine.compile_expression()`, сохраняет в кеш. Оба метода при ошибке компиляции логируют её и возвращают `None`. --- ## Пакетная прекомпиляция ```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) ``` | Метод | Действие | |---|---| | `precompile_scripts` | Компилирует каждый скрипт как действие | | `precompile_actions` | То же, что `precompile_scripts` (алиас) | | `precompile_exprs` | Компилирует каждое выражение | | `precompile_all_from_doc` | Компилирует все `doc.rhei_scripts` и рекурсивно обходит дерево элементов | ### `collect_and_precompile()` ```rust fn collect_and_precompile(el: &super::Element, ctx: &RheiContext) ``` Рекурсивно обходит дерево `Element`: - Для свойств, начинающихся с `__on:*` и непустых — компилирует как действие. - Для свойств, начинающихся с `RHEI_PREFIX` (`!rhei:`) — компилирует оставшуюся часть как выражение. --- ## Конвертация типов ### `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 каждого элемента) ``` ### `dynamic_to_value(d: &Dynamic) -> Value` Проверяет тип через `is_string()`, `is_int()`, `is_float()`, `is_bool()`, `is_array()` в порядке приоритета. Если тип не распознан — возвращает `Value::None`. ### `str_to_dynamic(s: &str) -> Dynamic` Эвристический парсер строки, пробует последовательно: 1. `s.parse::()` — целое число 2. `s.parse::()` — дробное число 3. `s.parse::()` — булево значение 4. Иначе — `Dynamic::from(s)` как строка