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

306 lines
10 KiB
Markdown

# Module `interpreter` — bytecode interpreter core
## Module structure
```
interpreter/
├── mod.rs — Interpreter: bytecode parsing, VDOM, styles
├── opcodes.rs — Opcode definitions (OP_ELEM_PUSH, OP_IF, OP_EACH, …)
├── reactive.rs — ReactiveTracker, ElementId — dirty node tracking
├── reader.rs — Reader — bytecode reading (str_ref, i64, f64, value, …)
├── rhei.rs — RheiContext — Rhai script and expression execution
├── style.rs — StyleSheet, AncestorInfo, ComputedStyle, StructuralContext
└── types.rs — Element, Document, ComponentDef, Value, FlatVDom, InterpError
```
## `Interpreter` (empty struct)
```rust
pub struct Interpreter;
```
The struct has no fields — all methods are static. It serves as a namespace for interpretation functions.
## `Interpreter::run()` — primary bytecode parsing
```rust
pub fn run<'a>(bytecode: &'a [u8]) -> Result<Document<'a>, InterpError>
```
1. Checks the magic number (`bytecode[..4] == MAGIC`).
2. Creates a `Reader`, `HashMap` for `variables` and `components`, `Vec` for `rhei_scripts`, an empty `StyleSheet`, and a `ReactiveTracker`.
3. Calls `parse_block_elements()` for the root level, which reads the opcode stream and builds the `Element` tree.
4. After parsing, calls `stylesheet.build_index()`.
5. Returns `Document { roots, components, variables, rhei_scripts, stylesheet, interner, tracker }`.
## `parse_block_elements()` — recursive block parsing
```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>
```
Reads opcodes in a loop, using a stack for nesting (`stack: Vec<Element>`). On `OP_END_BLOCK` it finishes the current block (if `!is_root`).
### Handled opcodes
| Opcode | Action |
|---|---|
| `OP_ELEM_PUSH` | Creates an `Element` with `tracker.alloc_id()`, pushes onto the stack |
| `OP_ELEM_POP` | Pops an element from the stack, calls `attach()` |
| `OP_GLOBAL` / `OP_LET` | Reads name and value, inserts into `variables` |
| `OP_SINGLETON` | Skips singleton data |
| `OP_CONTENT` | Text content — `text` property. If `OP_PROP_RHEI` — prefix `!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` | Reads key and value, calls `el.push_prop()` |
| `OP_RHEI_BLK` | At root level without a parent — script; otherwise — `#text` element with `!rhei:` |
| `OP_COMPONENT` | Reads name, parameters, recursively parses child block; stores `ComponentDef` |
| `OP_IF` | Reads condition, parses true-block, checks `has_else`, parses false-block. Creates `@if` with `@else` as the last child |
| `OP_EACH` | Reads variable name, source (array or `$var` or Rhai), parses template block. Creates `@each` |
| `OP_ON` | Reads event name, arguments, looks for `OP_RHEI_BLK` — handler script; sets `__on:{event}` |
| `OP_STYLE_RULE` | Reads selector and properties, calls `stylesheet.add_rule()` |
### `attach()`
```rust
fn attach<'a>(stack: &mut Vec<Element<'a>>, roots: &mut Vec<Element<'a>>, el: Element<'a>)
```
If the stack is not empty — adds to `parent.children`, otherwise to `roots`.
---
## `evaluate_vdom()` — VDOM assembly (entry point)
```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>>
```
Delegates to `evaluate_vdom_incr()` with an empty `dirty_set` — full recomputation.
### `evaluate_vdom_flat()`
```rust
pub fn evaluate_vdom_flat<'a>(...) -> FlatVDom<'a>
```
Wraps `evaluate_vdom()` and converts the result via `FlatVDom::from_elements()`.
---
## `evaluate_vdom_incr()` — incremental VDOM assembly
```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>>
```
### Preprocessing
1. **`sibling_infos`** — for each element from `templates` creates an `AncestorInfo` (type_name, id, classes). Needed for structural pseudo-classes (CSS `:nth-child`, `:first-of-type`, etc.).
2. **`type_counts`** / **`type_seen`** — count of total elements of each type and a counter for `StructuralContext`.
### Main loop over `templates`
For each element, a `StructuralContext` is computed:
```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`
- Reads `condition`, calls `evaluate_condition()`.
- Iterates over `child` elements: if `@else` — active when condition is false; otherwise active when true.
- Recursively calls `evaluate_vdom_incr()` for the active branch.
#### `@each`
- Gets `var_name` and `source`.
- Resolves the source: if prefixed with `!rhei:` — calls `normalize_rhai_array()`, otherwise — `resolve_string()`.
- Splits the result by `,`, for each element:
- Inserts `var_name` into `variables`, recursively processes the template, restores the previous variable value.
#### Regular element / Component
- If `el.type_name` is found in `components`:
1. Collects arguments from component parameters via `resolve_prop()`.
2. Saves old variable values, inserts new ones.
3. Creates `vcomp`, copies properties, resolving them via `resolve_prop()` and `resolve_string()`.
4. Computes styles: `collect_matching_styles()``stylesheet.compute_cached()`.
5. Builds ancestor chain: `build_ancestor_chain()`.
6. Recursively processes `comp.children`.
7. Computes `content_hash`.
8. Restores variables.
- Otherwise (regular element):
1. Creates `vnode`.
2. Resolves properties: `__on:``resolve_string()`, `$var``__bind:{key}`, others → `resolve_prop()`.
3. Computes styles via `collect_matching_styles()` + `compute_cached()`.
4. Builds ancestor chain, recursively processes `el.children`.
5. Computes `content_hash`.
---
## `compute_content_hash()`
```rust
fn compute_content_hash(el: &Element) -> u64
```
Element hash for caching. Considers:
- `type_name`
- All key-value pairs from `properties`
- Recursively `content_hash` of child elements (`children`)
Uses `DefaultHasher`.
---
## `build_ancestor_chain()`
```rust
fn build_ancestor_chain<'a>(ancestors: &[AncestorInfo], el: &Element) -> Vec<AncestorInfo>
```
Copies the current `ancestors`, adds `AncestorInfo` for the current element (type_name, id, classes). Returns the extended chain for passing during recursive child traversal.
---
## `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>>
```
Extracts `id`, `classes`, and all attributes of the element, delegates to `stylesheet.matching_rules()` with the full context for CSS selectors.
---
## `resolve_string()` — `$var` substitution
```rust
pub fn resolve_string<'a>(val: &'a str, scope: &HashMap<String, Value>) -> Cow<'a, str>
```
- If the string has no `$` — returns `Cow::Borrowed(val)` (no allocations).
- Otherwise, traverses the string character by character. After `$`, collects the variable name (letters, digits, `_`), looks it up in `scope`, substitutes the value. If the variable is not found — leaves `$var` as is.
## `resolve_prop()` — property value resolution
```rust
fn resolve_prop(v: &str, variables: &HashMap<String, Value>, rhei: &RheiContext) -> String
```
- If it starts with `!rhei:` — calls `rhei.eval_expr()`.
- Otherwise — `resolve_string()`.
## `evaluate_condition()` — `@if` condition evaluation
```rust
fn evaluate_condition(cond: &str, variables: &HashMap<String, Value>, rhei: &RheiContext) -> bool
```
- If `!rhei:``rhei.eval_condition()`.
- Otherwise:
1. Strips curly braces `{...}`.
2. Performs `resolve_string()`.
3. Checks `is_truthy_str()`, then `false`/`0`/empty.
4. Attempts to parse numeric operators (`>=`, `<=`, `>`, `<`, `==`, `!=`).
## `is_truthy()` / `is_truthy_str()` — conversion to bool
```rust
fn is_truthy(v: &Value) -> bool
fn is_truthy_str(s: &str) -> bool
```
- `Value::Bool` — by value.
- `Value::Int` — non-zero.
- `Value::Float` — non-zero.
- `Value::Str` — delegates to `is_truthy_str()`.
- `Value::None``false`.
- `Value::Array` — not empty.
- String: `""`, `"false"`, `"0"`, `"null"``false`; `"true"`, `"1"``true`; otherwise parses as `f64`.
## `normalize_rhai_array()` — Rhai array normalization
```rust
fn normalize_rhai_array(s: &str) -> String
```
Trims `[...]`, splits by `,`, trims whitespace, joins with `,`. Used in `@each` to convert a Rhai array to the format expected by the loop.
---
## `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>>,
}
```