13 KiB
Модуль 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 (пустая структура)
pub struct Interpreter;
Структура не содержит полей — все методы статические. Служит пространством имён для функций интерпретации.
Interpreter::run() — первичный парсинг байткода
pub fn run<'a>(bytecode: &'a [u8]) -> Result<Document<'a>, InterpError>
- Проверяет магическое число (
bytecode[..4] == MAGIC). - Создаёт
Reader,HashMapдляvariablesиcomponents,Vecдляrhei_scripts, пустойStyleSheetиReactiveTracker. - Вызывает
parse_block_elements()для корневого уровня, который читает поток опкодов и строит деревоElement. - После парсинга вызывает
stylesheet.build_index(). - Возвращает
Document { roots, components, variables, rhei_scripts, stylesheet, interner, tracker }.
parse_block_elements() — рекурсивный парсинг блока
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()
fn attach<'a>(stack: &mut Vec<Element<'a>>, roots: &mut Vec<Element<'a>>, el: Element<'a>)
Если стек не пуст — добавляет в parent.children, иначе в roots.
evaluate_vdom() — сборка VDOM (точка входа)
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()
pub fn evaluate_vdom_flat<'a>(...) -> FlatVDom<'a>
Оборачивает evaluate_vdom() и конвертирует результат через FlatVDom::from_elements().
evaluate_vdom_incr() — инкрементальная сборка VDOM
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>>
Предварительная обработка
-
sibling_infos— для каждого элемента изtemplatesсоздаётсяAncestorInfo(type_name, id, classes). Нужен для структурных псевдоклассов (CSS:nth-child,:first-of-typeи т.д.). -
type_counts/type_seen— подсчёт общего числа элементов каждого типа и счётчик дляStructuralContext.
Основной цикл по templates
Для каждого элемента вычисляется StructuralContext:
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:- Собирает аргументы из параметров компонента через
resolve_prop(). - Сохраняет старые значения переменных, вставляет новые.
- Создаёт
vcomp, копирует свойства, разрешая их черезresolve_prop()иresolve_string(). - Вычисляет стили:
collect_matching_styles()→stylesheet.compute_cached(). - Строит chain предков:
build_ancestor_chain(). - Рекурсивно обходит
comp.children. - Вычисляет
content_hash. - Восстанавливает переменные.
- Собирает аргументы из параметров компонента через
- Иначе (обычный элемент):
- Создаёт
vnode. - Разрешает свойства:
__on:→resolve_string(),$var→__bind:{key}, остальные →resolve_prop(). - Вычисляет стили через
collect_matching_styles()+compute_cached(). - Строит chain предков, рекурсивно обходит
el.children. - Вычисляет
content_hash.
- Создаёт
compute_content_hash()
fn compute_content_hash(el: &Element) -> u64
Хеш элемента для кэширования. Учитывает:
type_name- Все пары ключ-значение из
properties - Рекурсивно
content_hashдочерних элементов (children)
Использует DefaultHasher.
build_ancestor_chain()
fn build_ancestor_chain<'a>(ancestors: &[AncestorInfo], el: &Element) -> Vec<AncestorInfo>
Копирует текущий ancestors, добавляет AncestorInfo для текущего элемента (type_name, id, classes). Возвращает расширенную цепочку для передачи при рекурсивном обходе детей.
collect_matching_styles()
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
pub fn resolve_string<'a>(val: &'a str, scope: &HashMap<String, Value>) -> Cow<'a, str>
- Если в строке нет
$— возвращаетCow::Borrowed(val)(без аллокаций). - Иначе обходит строку посимвольно. После
$собирает имя переменной (буквы, цифры,_), ищет вscope, подставляет значение. Если переменная не найдена — оставляет$varкак есть.
resolve_prop() — разрешение значения свойства
fn resolve_prop(v: &str, variables: &HashMap<String, Value>, rhei: &RheiContext) -> String
- Если начинается с
!rhei:— вызываетrhei.eval_expr(). - Иначе —
resolve_string().
evaluate_condition() — вычисление условия @if
fn evaluate_condition(cond: &str, variables: &HashMap<String, Value>, rhei: &RheiContext) -> bool
- Если
!rhei:—rhei.eval_condition(). - Иначе:
- Удаляет фигурные скобки
{...}. - Выполняет
resolve_string(). - Проверяет
is_truthy_str(), затемfalse/0/пусто. - Пытается разобрать числовые операторы (
>=,<=,>,<,==,!=).
- Удаляет фигурные скобки
is_truthy() / is_truthy_str() — приведение к bool
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-массива
fn normalize_rhai_array(s: &str) -> String
Обрезает [...], разбивает по ,, обрезает пробелы, соединяет через ,. Используется в @each для приведения Rhai-массива к формату, ожидаемому циклом.
Document
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
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
pub struct ComponentDef<'a> {
pub name: String,
pub params: Vec<(String, String)>,
pub children: Vec<Element<'a>>,
}