306 lines
13 KiB
Markdown
306 lines
13 KiB
Markdown
# Модуль `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>>,
|
||
}
|
||
```
|