Skip to content

Expressions

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 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.

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 of Object.prototype.
  • The node store gives a resolver whose first segment is a node ID. The deref op of the store resolves the rest.

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.

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 expression playgroundEdit the formula. The result changes as you type.
some(9)
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"
      ]
    }
  ]
}

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.