The Formula Compiler
From expression to reactive function
When you type Ball.x + 100 in a formula field, the compiler transforms it into a dependency-tracked JavaScript function. This page explains how.
Pipeline
Source text → Acorn parse → AST transform → Astring codegen → Compiled function
- Parse — acorn parses the expression into an AST (Abstract Syntax Tree)
- Transform — node references become
ctx.resolve()calls, method calls becomectx.callMethod()calls - Codegen — astring generates JavaScript from the transformed AST
- Compile — the generated code is wrapped in a function with the formula context
Three compile modes
| Mode | Input | Output | Used for |
|---|---|---|---|
formula | Ball.x + 100 | Dependency-tracked expression | Property formulas |
event | this.x += 10; event.target.destroy() | Statement block with this and event | Event handlers |
method | this.x = 0; this.y = 0 | Callable function with this | User-defined methods |
AST transforms
Node references
Ball.x → ctx.resolve('Ball', 'x')parent.rotation → ctx.resolve('parent', 'rotation')this.width → ctx.resolve('this', 'width')
The resolver looks up the node by name in the scene, registers a dependency, and returns the current value.
Method calls
this.reset() → ctx.callMethod('this', 'reset', [])Ball.explode() → ctx.callMethod('Ball', 'explode', [])
Tag access
Tags are virtual nodes. The compiler handles bracket notation specially:
Tag.brick → ctx.resolve('Tag', 'brick') // returns VNode[]Tag.brick.length → ctx.resolve('Tag', 'brick').length // countTag.brick[0].x → ctx.resolve('Tag', 'brick')[0].x // first brick's x
Compound assignments
this.x += 10 → ctx.set('this', 'x', ctx.resolve('this', 'x') + 10)this.score++ → ctx.set('this', 'score', ctx.resolve('this', 'score') + 1)
Dependency extraction
During compilation, the transformer collects all resolve() calls. These become the formula's dependency list, used for:
- Topological sort (evaluation order)
- Dirty propagation (which formulas to re-evaluate)
- Cycle detection (finding circular references)
Globals whitelist
These globals are available in all formula contexts without import: console, JSON, Math, setTimeout, setInterval, clearTimeout, clearInterval, parseInt, parseFloat, isNaN, isFinite, Number, String, Array, Object
Source maps
Compiled formulas include inline VLQ-encoded source maps and sourceURL labels. Open browser DevTools, set a breakpoint in a formula — the debugger shows your original expression, not the compiled output.
Example
You write:
clamp(mouse.x - Face.x, -20, 20) + 70
Compiler produces:
(function(ctx) {
return clamp(ctx.resolve('mouse', 'x') - ctx.resolve('Face', 'x'), -20, 20) + 70
})
//# sourceURL=formula://LeftEye/x
//# sourceMappingURL=data:...Dependencies extracted: ['mouse.x', 'Face.x']