Files
Glint-Runtime/docs/ru/modules/04-rhei.md

201 lines
8.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Модуль 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<Scope<'static>>,
action_cache: RefCell<HashMap<String, AST>>,
expr_cache: RefCell<HashMap<String, AST>>,
}
```
| Поле | Назначение |
|---|---|
| `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<String, Value>)
```
Синхронизирует значения из внешнего `HashMap` в Rhai `Scope`:
- Если переменная уже есть в скопе и её значение не изменилось — пропускает.
- Если переменная есть — обновляет через `set_value()`.
- Если переменной нет — добавляет через `push_dynamic()`.
Преобразование `Value → Dynamic` выполняется через `value_to_dynamic()`.
---
## `initialize()` — инициализация
```rust
pub fn initialize(&self, variables: &mut HashMap<String, Value>)
```
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<String, Value>) -> Value
```
1. Синхронизирует переменные.
2. Получает (компилирует или берёт из кеша) AST выражения через `get_or_compile_expr()`.
3. Выполняет через `eval_ast_with_scope::<Dynamic>()`.
4. Преобразует результат `Dynamic → Value` через `dynamic_to_value()`.
5. При ошибке возвращает `Value::None`.
---
## `eval_condition()` — вычисление условия
```rust
pub fn eval_condition(&self, expr: &str, variables: &HashMap<String, Value>) -> bool
```
Аналогичен `eval_expr()`, но типизирован как `bool`. При ошибке возвращает `false`.
---
## `execute_action()` — выполнение скрипта
```rust
pub fn execute_action(&self, script: &str, variables: &mut HashMap<String, Value>)
```
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<AST>
```
Проверяет `action_cache`. При промахе компилирует через `engine.compile()`, сохраняет в кеш.
### `get_or_compile_expr(expr)`
```rust
fn get_or_compile_expr(&self, expr: &str) -> Option<AST>
```
Проверяет `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::<i64>()` — целое число
2. `s.parse::<f64>()` — дробное число
3. `s.parse::<bool>()` — булево значение
4. Иначе — `Dynamic::from(s)` как строка