Expressions
Three node types
Section titled “Three node types”An expression is a tree of plain data. Its JSON form is the same as its value in memory.
| Node | Form | Meaning |
|---|---|---|
lit |
{ tag: "lit", value } |
A literal value |
ref |
{ tag: "ref", path: [...] } |
A path. The resolver of the evaluation gives its value |
app |
{ tag: "app", op, args: [...] } |
An application of a named op to the values of the arguments |
The functions lit, ref, app and record make these nodes. isExpr examines a full tree, and the viewer uses it on each edit.
The formula language
Section titled “The formula language”The JSON form is exact, but it is long. The formula language is the text form of the same IR. formatExpr(expr) gives the formula of an expression, and parseExpr(text) gives the expression of a formula, or the reason of a failure and its position.
| Formula | IR |
|---|---|
42, "text", [1, 2], {"a": 1}, null, undefined, NaN, -Infinity |
lit(...). JSON, and the values that JSON cannot write |
self.cells.value |
ref("self", "cells", "value") |
self.`my key`.0 |
ref("self", "my key", "0"). A segment that is not a name goes in backticks |
max(a, 1) |
app("max", ref("a"), lit(1)) |
a * 2 + 1 |
app("+", app("*", ref("a"), lit(2)), lit(1)) |
`my op`(x) |
app("my op", ref("x")) |
The ops * and / bind more tightly than + and -, and these bind more tightly than <, >, <= and >=. Each infix op with two arguments has the infix form, and the other ops have the form of a call. For each valid expression, parseExpr(formatExpr(e)) gives e again.
The resolver
Section titled “The resolver”evaluate(expr, resolve, ops, issues?) takes a resolver: a function from a path to an optional value. Each layer gives its own resolver:
objectResolver(ctx)reads a plain object. It reads only own properties, thus["constructor"]does not find a function ofObject.prototype.- The node store gives a resolver whose first segment is a node ID. The
derefop of the store resolves the rest.
Special forms
Section titled “Special forms”The interpreter evaluates five forms before it looks for an op. Their arguments evaluate lazily.
| Form | Semantics |
|---|---|
if(cond, then, else?) |
Only the selected branch evaluates |
and(a, b, ...) |
Stops at the first false value |
or(a, b, ...) |
Stops at the first true value |
fn(lit([params]), body) |
A lambda. Its body reads the parameters as references |
record(k1, v1, k2, v2, ...) |
An object. A field with a none value is not in the object |
deps(expr) gives the free reference paths of an expression. The parameters of a fn form are not free, thus they are not dependencies.
Total evaluation
Section titled “Total evaluation”The interpreter does not throw. A failure gives none. When the caller gives an array, the interpreter adds the cause of each none:
| Code | Cause |
|---|---|
unknown-op |
The registry has no op with this name |
path-miss |
The resolver has no value at the path |
arg-none |
An argument of the op gave none |
op-threw |
The op threw an error. The message of the error is in the issue |
bad-form |
A special form has incorrect arguments |
bad-expr |
A node of the tree is not a valid expression |
The standard ops throw a type error for a value of an incorrect type, for example + for a text. The issue then names the op and the reason.
Write a formula in the playground. Open the IR to see the expression that the formula makes.
the IR of the formula: (w + 1) * h
{
"tag": "app",
"op": "*",
"args": [
{
"tag": "app",
"op": "+",
"args": [
{
"tag": "ref",
"path": [
"w"
]
},
{
"tag": "lit",
"value": 1
}
]
},
{
"tag": "ref",
"path": [
"h"
]
}
]
}The standard ops
Section titled “The standard ops”standardOps of @render/splay has these ops:
| Category | Ops |
|---|---|
| math | +, -, *, /, max, min, <, >, <=, >=, abs |
| data | get, if, not, concat, eq, typeof, map, props, array, str, toString |
| summaries | keys, count, join, brief, formula, call |
| measure | textWidth, textHeight (placeholders) |
The eq op compares plain data structurally, like the change detection of the engine. The summary ops give the boundary of a value: brief(v) gives { a, b } for an object, [3 items] for an array, and the formula of an expression. call(f, ...args) applies a function, for example call(self.dehydrate) in a summary method.