Files
Glint-Runtime/docs/ru/modules/06-mod.md

306 lines
13 KiB
Markdown
Raw 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.
# Модуль `interpreter` — ядро интерпретатора байткода
## Структура модуля
```
interpreter/
├── mod.rs — Интерпретатор: парсинг байткода, VDOM, стили
├── opcodes.rs — Определения opcode (OP_ELEM_PUSH, OP_IF, OP_EACH, …)
├── reactive.rs — ReactiveTracker, ElementId — отслеживание грязных узлов
├── reader.rs — Reader — чтение байткода (str_ref, i64, f64, value, …)
├── rhei.rs — RheiContext — выполнение Rhai-скриптов и выражений
├── style.rs — StyleSheet, AncestorInfo, ComputedStyle, StructuralContext
└── types.rs — Element, Document, ComponentDef, Value, FlatVDom, InterpError
```
## `Interpreter` (пустая структура)
```rust
pub struct Interpreter;
```
Структура не содержит полей — все методы статические. Служит пространством имён для функций интерпретации.
## `Interpreter::run()` — первичный парсинг байткода
```rust
pub fn run<'a>(bytecode: &'a [u8]) -> Result<Document<'a>, InterpError>
```
1. Проверяет магическое число (`bytecode[..4] == MAGIC`).
2. Создаёт `Reader`, `HashMap` для `variables` и `components`, `Vec` для `rhei_scripts`, пустой `StyleSheet` и `ReactiveTracker`.
3. Вызывает `parse_block_elements()` для корневого уровня, который читает поток опкодов и строит дерево `Element`.
4. После парсинга вызывает `stylesheet.build_index()`.
5. Возвращает `Document { roots, components, variables, rhei_scripts, stylesheet, interner, tracker }`.
## `parse_block_elements()` — рекурсивный парсинг блока
```rust
fn parse_block_elements<'a>(
r: &mut Reader<'a>,
variables: &mut HashMap<String, Value>,
components: &mut HashMap<String, ComponentDef<'a>>,
rhei_scripts: &mut Vec<String>,
stylesheet: &mut SS,
tracker: &mut ReactiveTracker,
is_root: bool,
) -> Result<Vec<Element<'a>>, InterpError>
```
Читает опкоды в цикле, используя стек для построения вложенности (`stack: Vec<Element>`). На `OP_END_BLOCK` завершает текущий блок (если `!is_root`).
### Обрабатываемые опкоды
| Опкод | Действие |
|---|---|
| `OP_ELEM_PUSH` | Создаёт `Element` с `tracker.alloc_id()`, кладёт на стек |
| `OP_ELEM_POP` | Снимает элемент со стека, вызывает `attach()` |
| `OP_GLOBAL` / `OP_LET` | Читает имя и значение, вставляет в `variables` |
| `OP_SINGLETON` | Пропускает данные синглтона |
| `OP_CONTENT` | Текстовое содержимое — свойство `text`. Если `OP_PROP_RHEI` — префикс `!rhei:` |
| `OP_PROP_STR`, `OP_PROP_VAR`, `OP_PROP_INT`, `OP_PROP_FLOAT`, `OP_PROP_BOOL`, `OP_PROP_RHEI`, `OP_PROP_UNIT`, `OP_PROP_CALL`, `OP_PROP_IDENT`, `OP_PROP_FSPATH`, `OP_PROP_COLOR` | Читает ключ и значение, вызывает `el.push_prop()` |
| `OP_RHEI_BLK` | На корневом уровне без родителя — скрипт; иначе — элемент `#text` с `!rhei:` |
| `OP_COMPONENT` | Читает имя, параметры, рекурсивно парсит дочерний блок; сохраняет `ComponentDef` |
| `OP_IF` | Читает условие, парсит true-блок, проверяет `has_else`, парсит false-блок. Создаёт `@if` с `@else` как последним child |
| `OP_EACH` | Читает имя переменной, источник (массив или `$var` или Rhai), парсит блок-шаблон. Создаёт `@each` |
| `OP_ON` | Читает имя события, аргументы, ищет `OP_RHEI_BLK` — скрипт обработчика; устанавливает `__on:{event}` |
| `OP_STYLE_RULE` | Читает селектор и свойства, вызывает `stylesheet.add_rule()` |
### `attach()`
```rust
fn attach<'a>(stack: &mut Vec<Element<'a>>, roots: &mut Vec<Element<'a>>, el: Element<'a>)
```
Если стек не пуст — добавляет в `parent.children`, иначе в `roots`.
---
## `evaluate_vdom()` — сборка VDOM (точка входа)
```rust
pub fn evaluate_vdom<'a>(
templates: &[Element<'a>],
variables: &mut HashMap<String, Value>,
components: &HashMap<String, ComponentDef<'a>>,
rhei: &RheiContext,
stylesheet: &SS,
ancestors: &[AncestorInfo],
) -> Vec<Element<'a>>
```
Делегирует `evaluate_vdom_incr()` с пустым `dirty_set` — полный пересчёт.
### `evaluate_vdom_flat()`
```rust
pub fn evaluate_vdom_flat<'a>(...) -> FlatVDom<'a>
```
Оборачивает `evaluate_vdom()` и конвертирует результат через `FlatVDom::from_elements()`.
---
## `evaluate_vdom_incr()` — инкрементальная сборка VDOM
```rust
pub fn evaluate_vdom_incr<'a>(
templates: &[&Element<'a>],
variables: &mut HashMap<String, Value>,
components: &HashMap<String, ComponentDef<'a>>,
rhei: &RheiContext,
stylesheet: &SS,
ancestors: &[AncestorInfo],
dirty_set: &HashSet<ElementId>,
) -> Vec<Element<'a>>
```
### Предварительная обработка
1. **`sibling_infos`** — для каждого элемента из `templates` создаётся `AncestorInfo` (type_name, id, classes). Нужен для структурных псевдоклассов (CSS `:nth-child`, `:first-of-type` и т.д.).
2. **`type_counts`** / **`type_seen`** — подсчёт общего числа элементов каждого типа и счётчик для `StructuralContext`.
### Основной цикл по `templates`
Для каждого элемента вычисляется `StructuralContext`:
```rust
let structural = StructuralContext {
sibling_index: i,
sibling_total: templates.len(),
type_index: *type_idx,
type_total,
has_children: ...,
is_root: ancestors.is_empty(),
};
```
#### `@if`
- Читает `condition`, вызывает `evaluate_condition()`.
- Проходит по `child` элементам: если `@else` — активна когда условие ложно; иначе активна когда истинно.
- Рекурсивно вызывает `evaluate_vdom_incr()` для активной ветки.
#### `@each`
- Получает `var_name` и `source`.
- Разрешает источник: если с префиксом `!rhei:` — вызывает `normalize_rhai_array()`, иначе — `resolve_string()`.
- Разбивает результат по `,`, для каждого элемента:
- Вставляет `var_name` в `variables`, рекурсивно обходит шаблон, восстанавливает предыдущее значение переменной.
#### Обычный элемент / Компонент
- Если `el.type_name` найден в `components`:
1. Собирает аргументы из параметров компонента через `resolve_prop()`.
2. Сохраняет старые значения переменных, вставляет новые.
3. Создаёт `vcomp`, копирует свойства, разрешая их через `resolve_prop()` и `resolve_string()`.
4. Вычисляет стили: `collect_matching_styles()``stylesheet.compute_cached()`.
5. Строит chain предков: `build_ancestor_chain()`.
6. Рекурсивно обходит `comp.children`.
7. Вычисляет `content_hash`.
8. Восстанавливает переменные.
- Иначе (обычный элемент):
1. Создаёт `vnode`.
2. Разрешает свойства: `__on:``resolve_string()`, `$var``__bind:{key}`, остальные → `resolve_prop()`.
3. Вычисляет стили через `collect_matching_styles()` + `compute_cached()`.
4. Строит chain предков, рекурсивно обходит `el.children`.
5. Вычисляет `content_hash`.
---
## `compute_content_hash()`
```rust
fn compute_content_hash(el: &Element) -> u64
```
Хеш элемента для кэширования. Учитывает:
- `type_name`
- Все пары ключ-значение из `properties`
- Рекурсивно `content_hash` дочерних элементов (`children`)
Использует `DefaultHasher`.
---
## `build_ancestor_chain()`
```rust
fn build_ancestor_chain<'a>(ancestors: &[AncestorInfo], el: &Element) -> Vec<AncestorInfo>
```
Копирует текущий `ancestors`, добавляет `AncestorInfo` для текущего элемента (type_name, id, classes). Возвращает расширенную цепочку для передачи при рекурсивном обходе детей.
---
## `collect_matching_styles()`
```rust
fn collect_matching_styles<'a>(
el: &Element,
stylesheet: &'a SS,
active_pseudo: &[&str],
structural: &StructuralContext,
ancestors: &[AncestorInfo],
preceding_siblings: &[AncestorInfo],
) -> Vec<&'a HashMap<String, String>>
```
Извлекает `id`, `classes`, все атрибуты элемента, делегирует `stylesheet.matching_rules()` с полным контекстом для CSS-селекторов.
---
## `resolve_string()` — подстановка `$var`
```rust
pub fn resolve_string<'a>(val: &'a str, scope: &HashMap<String, Value>) -> Cow<'a, str>
```
- Если в строке нет `$` — возвращает `Cow::Borrowed(val)` (без аллокаций).
- Иначе обходит строку посимвольно. После `$` собирает имя переменной (буквы, цифры, `_`), ищет в `scope`, подставляет значение. Если переменная не найдена — оставляет `$var` как есть.
## `resolve_prop()` — разрешение значения свойства
```rust
fn resolve_prop(v: &str, variables: &HashMap<String, Value>, rhei: &RheiContext) -> String
```
- Если начинается с `!rhei:` — вызывает `rhei.eval_expr()`.
- Иначе — `resolve_string()`.
## `evaluate_condition()` — вычисление условия `@if`
```rust
fn evaluate_condition(cond: &str, variables: &HashMap<String, Value>, rhei: &RheiContext) -> bool
```
- Если `!rhei:``rhei.eval_condition()`.
- Иначе:
1. Удаляет фигурные скобки `{...}`.
2. Выполняет `resolve_string()`.
3. Проверяет `is_truthy_str()`, затем `false`/`0`/пусто.
4. Пытается разобрать числовые операторы (`>=`, `<=`, `>`, `<`, `==`, `!=`).
## `is_truthy()` / `is_truthy_str()` — приведение к bool
```rust
fn is_truthy(v: &Value) -> bool
fn is_truthy_str(s: &str) -> bool
```
- `Value::Bool` — по значению.
- `Value::Int` — ненулевой.
- `Value::Float` — ненулевой.
- `Value::Str` — делегирует `is_truthy_str()`.
- `Value::None``false`.
- `Value::Array` — не пустой.
- Строка: `""`, `"false"`, `"0"`, `"null"``false`; `"true"`, `"1"``true`; иначе парсит как `f64`.
## `normalize_rhai_array()` — нормализация Rhai-массива
```rust
fn normalize_rhai_array(s: &str) -> String
```
Обрезает `[...]`, разбивает по `,`, обрезает пробелы, соединяет через `,`. Используется в `@each` для приведения Rhai-массива к формату, ожидаемому циклом.
---
## `Document`
```rust
pub struct Document<'a> {
pub roots: Vec<Element<'a>>,
pub components: HashMap<String, ComponentDef<'a>>,
pub variables: HashMap<String, Value>,
pub rhei_scripts: Vec<String>,
pub stylesheet: StyleSheet,
pub interner: Interner,
pub tracker: ReactiveTracker,
}
```
## `Element`
```rust
pub struct Element<'a> {
pub type_name: &'a str,
pub properties: Vec<(String, String)>,
pub children: Vec<Element<'a>>,
pub element_id: ElementId,
pub computed_style: Option<ComputedStyle>,
pub content_hash: u64,
}
```
## `ComponentDef`
```rust
pub struct ComponentDef<'a> {
pub name: String,
pub params: Vec<(String, String)>,
pub children: Vec<Element<'a>>,
}
```