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

10 KiB

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)

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

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

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()

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)

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()

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

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:

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()

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()

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()

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

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

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

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

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::Nonefalse.
  • Value::Array — not empty.
  • String: "", "false", "0", "null"false; "true", "1"true; otherwise parses as f64.

normalize_rhai_array() — Rhai array normalization

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

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>>,
}