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>
- Checks the magic number (
bytecode[..4] == MAGIC). - Creates a
Reader,HashMapforvariablesandcomponents,Vecforrhei_scripts, an emptyStyleSheet, and aReactiveTracker. - Calls
parse_block_elements()for the root level, which reads the opcode stream and builds theElementtree. - After parsing, calls
stylesheet.build_index(). - 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
-
sibling_infos— for each element fromtemplatescreates anAncestorInfo(type_name, id, classes). Needed for structural pseudo-classes (CSS:nth-child,:first-of-type, etc.). -
type_counts/type_seen— count of total elements of each type and a counter forStructuralContext.
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, callsevaluate_condition(). - Iterates over
childelements: if@else— active when condition is false; otherwise active when true. - Recursively calls
evaluate_vdom_incr()for the active branch.
@each
- Gets
var_nameandsource. - Resolves the source: if prefixed with
!rhei:— callsnormalize_rhai_array(), otherwise —resolve_string(). - Splits the result by
,, for each element:- Inserts
var_nameintovariables, recursively processes the template, restores the previous variable value.
- Inserts
Regular element / Component
- If
el.type_nameis found incomponents:- Collects arguments from component parameters via
resolve_prop(). - Saves old variable values, inserts new ones.
- Creates
vcomp, copies properties, resolving them viaresolve_prop()andresolve_string(). - Computes styles:
collect_matching_styles()→stylesheet.compute_cached(). - Builds ancestor chain:
build_ancestor_chain(). - Recursively processes
comp.children. - Computes
content_hash. - Restores variables.
- Collects arguments from component parameters via
- Otherwise (regular element):
- Creates
vnode. - Resolves properties:
__on:→resolve_string(),$var→__bind:{key}, others →resolve_prop(). - Computes styles via
collect_matching_styles()+compute_cached(). - Builds ancestor chain, recursively processes
el.children. - Computes
content_hash.
- Creates
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_hashof 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
$— returnsCow::Borrowed(val)(no allocations). - Otherwise, traverses the string character by character. After
$, collects the variable name (letters, digits,_), looks it up inscope, substitutes the value. If the variable is not found — leaves$varas is.
resolve_prop() — property value resolution
fn resolve_prop(v: &str, variables: &HashMap<String, Value>, rhei: &RheiContext) -> String
- If it starts with
!rhei:— callsrhei.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:
- Strips curly braces
{...}. - Performs
resolve_string(). - Checks
is_truthy_str(), thenfalse/0/empty. - Attempts to parse numeric operators (
>=,<=,>,<,==,!=).
- Strips curly braces
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 tois_truthy_str().Value::None—false.Value::Array— not empty.- String:
"","false","0","null"→false;"true","1"→true; otherwise parses asf64.
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>>,
}