The specification
This document is the specification of Vex 1.0. Each normative statement has a requirement ID in square brackets, for example [EVAL.TOTAL]. Each requirement ID has at least one test that names it. The test spec-coverage.test.ts makes CI fail when an ID has no test, or when a test names an ID that this document does not define.
The design and the reasons for it are in docs/REVIEW.md. The v0.9 specification is in docs/archive/spec-v0.9.md.
1. Model
Section titled “1. Model”A Vex program is an expression. It evaluates at one key of a space. The key where it starts is the origin. The key where relative references read is the focus. At the start, the focus is the origin.
| Term | Meaning |
|---|---|
| space | An immutable collection of records with keys: a record space, an array space or a grid space |
| origin | The key where an evaluation starts |
| focus | The key where a reference without an address reads |
| address | A list of moves from the focus |
| axis | A relation between the focus and a list of target keys |
| domain | A set of values and the ops on them |
| result | A value, or an error value with a code |
2. Spaces
Section titled “2. Spaces”- A record space has the property names of an object as keys, in their order.
- An array space has the keys
"0","1"and so on. - A grid space has the key
"row,column"for each cell. A row can be shorter than the others. - SPACE.TREE6 tests pass A tree space has records by key, and a parent for each key that is not a root. A parent that is not a key, or a cycle of parents, makes the constructor throw. The children of a key are in key order.
- A space copies its input. A later change to the input does not change the space.
3. Addresses
Section titled “3. Addresses”A move changes the key where a reference reads.
- NAV.KEY1 tests pass The move
key(k)goes to the keyk. If the space has no keyk, the result is the error#REF!. - NAV.INDEX1 tests pass The move
index(i)goes to the key at positioniin the key order. A position outside the keys gives#REF!. - NAV.OTHER.PAIR2 tests pass The move
othergoes to the other key of a space with two keys. In a space with a different number of keys, it gives#REF!. - NAV.OFFSET1 tests pass The move
offset(d)addsdto the position of the focus: one number in an array space, two numbers in a grid space. A position outside the space, or an offset in a record space, gives#REF!. - NAV.ORIGIN1 tests pass The move
origingoes back to the origin of the evaluation. - NAV.PARENT5 tests pass The move
parentgoes to the parent of the focus in a tree space. A root gives#REF!, and a space that is not a tree gives#REF!. - NAV.SEQUENCE1 tests pass The moves of an address apply in order. If one move fails, the address fails. Vex does not simplify addresses, because a simplification could hide a failure.
4. Expressions
Section titled “4. Expressions”An expression is plain JSON data. It has one of these kinds.
| Kind | Fields | Value |
|---|---|---|
lit |
value |
the value |
ref |
path, at |
the field at the path, in the record at the address |
app |
op, args |
the op applied to the values of the arguments |
let |
bind, body |
the body, with names bound to the values of the bindings |
var |
name |
the value of a bound name |
rec |
fields |
a record of the values of the fields |
each |
axis, body |
a list: the body at each target of the axis |
ext |
kind, data |
the value of an extension handler |
- IR.JSON10 tests pass An expression whose literals are JSON data gives the same expression after
serializeandparse. A literal that is a domain value needsencodeanddecodein its domain.
5. Evaluation
Section titled “5. Evaluation”- EVAL.TOTAL6 tests pass The interpreter does not throw. It gives a value or an error value. A value is not
undefined. - EVAL.PURE1 tests pass An evaluation does not change any state. Two evaluations of the same expression at the same key give the same result.
- EVAL.ORIGIN1 tests pass An origin that is not a key of the space gives
#REF!. - EVAL.LOCATION1 tests pass An error value has the path of the node that made it, and the origin and the focus of the evaluation at that node. An extension handler can give an error without an origin. Then the interpreter adds the path, the origin and the focus of the extension node.
- EVAL.COMPILE3 tests pass
compile(e, options)gives a program. Itsrunandexplaingive the same results and the same traces asevaluateandexplain. One program evaluates at each origin of each space, and one evaluation does not change a later evaluation. - EVAL.REFERENCE107 tests pass The interpreter of
@mark1russell7/vexand the reference interpreter of@mark1russell7/vex-testkitgive the same value or the same error code.
5.1 References
Section titled “5.1 References”- REF.FOCUS1 tests pass A reference without an address reads the field at the focus.
- REF.PATH1 tests pass A path with more than one segment reads nested fields. The empty path reads the whole record.
- REF.MISSING2 tests pass A missing field, or a field with the value
null, gives#N/A. - REF.FORBIDDEN2 tests pass The segments
__proto__,constructorandprototypeare not fields. They give#N/A. - REF.ADDRESS1 tests pass A reference with an address reads at the key where the address points. A failed address gives
#REF!.
5.2 Ops
Section titled “5.2 Ops”- CALL.METHOD1 tests pass For an op without
fn, the interpreter calls the method of the receiver with the same name. - CALL.FN1 tests pass For an op with
fn, the interpreter callsfnwith the receiver and the other arguments. - CALL.UNKNOWN-OP1 tests pass An op that the domain of the receiver does not declare gives
#NAME?. An inherited member, for exampletoString, is not an op. - CALL.RECEIVER1 tests pass A receiver that no domain accepts gives
#VALUE!, if no free function has the name of the op. - CALL.FREE1 tests pass A free function applies when no domain accepts the receiver.
- CALL.PARAMS3 tests pass If an op declares the kinds of its parameters, an argument of the wrong kind gives
#VALUE!. - CALL.LIFT3 tests pass If an op declares
liftScalar, the interpreter changes each number argument into a domain value withfromScalar. - CALL.THROW1 tests pass An op that throws gives
#CALC!. The error keeps the thrown value. - CALL.RESULT3 tests pass An op result that is
undefinedornullgives#CALC!. A number that is not finite, or a domain value that fails thevalidcheck of its domain, gives#NUM!. - CALL.ARGS1 tests pass The interpreter evaluates all arguments. One failed argument gives its own error. Two or more failed arguments give
#ARGS, with each error as a cause. - CALL.STRING-ARGS1 tests pass A string literal is a value. The builder makes a field reference only from a bare string.
5.3 Bindings and records
Section titled “5.3 Bindings and records”- LET.BIND1 tests pass
letbinds each name to the value of its binding, for the body. - LET.LAZY-ERROR1 tests pass A binding that fails has an effect only where a
varreads it. - LET.UNBOUND1 tests pass A name without a binding gives
#NAME?. - LET.VARS1 tests pass The caller of the interpreter can give variables.
- REC.FIELDS1 tests pass
recmakes a record of its fields. Two or more failed fields give#ARGS.
5.4 Axes
Section titled “5.4 Axes”- AXIS.ALL1 tests pass The axis
allhas each key of the space as a target. - AXIS.OTHERS1 tests pass The axis
othershas each key except the focus as a target. - AXIS.OTHERS.ORIGIN1 tests pass Inside an axis, the focus is the target, and the origin does not change. A value bound outside the axis keeps its value. Thus a base value from the origin and a field of the target can meet in one body.
- AXIS.OTHER.PAIR1 tests pass The axis
otherhas the other key of a pair as a target. Outside a pair, it gives#REF!. - AXIS.NEIGHBORS2 tests pass The axis
neighbors(4)orneighbors(8)has the neighbor cells of the focus in a grid as targets. Outside a grid, it gives#REF!. - AXIS.TREE8 tests pass In a tree space, the axis
childrenhas the children of the focus as targets, in key order.ancestorshas the parent first and the root last.descendantshas each key under the focus, depth first, with each parent before its children.siblingshas the other children of the parent, or the other roots for a root. In a space that is not a tree, each of these axes gives#REF!. - AXIS.WHERE2 tests pass The axis
where(axis, test)keeps the targets ofaxiswheretestgivestrue. A test that fails, or that does not give a boolean, gives an error item for that target. - AXIS.OTHERS-UNION1 tests pass The targets of
othersand the focus are the targets ofall. - AXIS.OTHER-TWICE1 tests pass In a pair, the address
[other, other]points to the focus. - AXIS.EXTEND1 tests pass For an expression that does not read the origin, the item at key
kofeach(all, e)equalseevaluated at origink. This is the law of a comonad: extract after extend gives the program.
5.5 Lists
Section titled “5.5 Lists”- LIST.LENIENT2 tests pass A list op skips error items by default. With
{ strict: true }, the first error item is the result. - LIST.EMPTY3 tests pass
min,max,meanandfirstof an empty list give#N/A.sumgives 0 andcountgives 0.reducegives the identity of the op, or#N/Aif the op has no identity. - LIST.KINDS3 tests pass
sum,mean,minandmaxneed numbers.any,allandnoneneed booleans. A value of another kind gives#VALUE!. - LIST.REDUCE1 tests pass
reducefolds the values from the left with a domain op. For an associative op, any order of reduction gives the same value.
5.6 Special forms
Section titled “5.6 Special forms”- FORM.IF2 tests pass
ifevaluates only the branch that its condition selects. A condition that is not a boolean gives#VALUE!. - FORM.AND-OR1 tests pass
andandorstop at the first argument that decides the result. - FORM.IFERROR1 tests pass
ifErrorgives its first argument, or its second argument when the first is an error.
6. Folds
Section titled “6. Folds”- TRACE.EVENTS9 tests pass
explainrecords one event for each node evaluation, in the order that the nodes finish. A reference event records its reads. - TRACE.AXIS1 tests pass Inside an axis, the focus of each event is the target.
- DEPS.READS1 tests pass
depsgives each reference of an expression, with its address and the axes around it.
7. Domains
Section titled “7. Domains”- DOMAIN.OWNED3 tests pass A domain owns its op table.
defineDomaindoes not change a class or a prototype, and an import has no side effects. - DOMAIN.LAWS5 tests pass Each law that a domain declares holds for the values of the domain.
@mark1russell7/vex-testkitchecks each law with property tests.
8. The builder
Section titled “8. The builder”- BUILD.IMMUTABLE2 tests pass A builder call gives a new chain. An earlier chain does not change.
- BUILD.CALL3 tests pass
call(name, ...args)applies a free function ofwithOptions: the expression isapp(name, value, ...args). A domain op with the same name for the value comes first at run time. - TYPE.FROM1 tests pass
from(p)accepts only the fields of the record type, and the chain value has the type of the field. - TYPE.OPS1 tests pass
._has only the ops of the domain of the current value, with the parameter types of each op. - TYPE.LIFT1 tests pass A number argument for a domain parameter needs
liftScalaron the op. - TYPE.STATE1 tests pass After an op gives a value that no domain of the chain accepts, the chain has no
._. - TYPE.OTHER1 tests pass
other()exists only on a chain over a space with two keys, or with keys that the compiler does not know. - TYPE.KEYS1 tests pass Evaluation accepts only the keys of the space.
- TYPE.LIST1 tests pass A number reduction needs a list of numbers.
- TYPE.NO-ANY1 tests pass No value type of the builder is
any. - TYPE.CALL1 tests pass
callaccepts only the names of the free functions whose first parameter accepts the current value. The other arguments have the types of the other parameters, and the chain value has the return type of the function.
9. Sheets
Section titled “9. Sheets”A sheet has named columns of formulas over a space. A cell is one column at one key. A formula reads a cell with a cell reference: the extension node ext("vex.cell", { column, at }).
- SHEET.CELL9 tests pass A cell reference resolves its address from the focus, then gives the result of that cell. An unknown column gives
#NAME?, and an address outside the space gives#REF!. - SHEET.RECURRENCE2 tests pass A column can read itself at another key. A recurrence over the key order, for example a running total, gives the same result as a loop.
- SHEET.DECLARE1 tests pass
declare<T>()gives the types of columns before their formulas. A formula can read a declared column with its type, also the column itself or a later column. The formula of a declared column must give a value of the declared type.declarehas no effect at run time. - SHEET.CYCLE2 tests pass Each cell on a cycle of cell references gives
#CYCLE!. A cycle can contain one cell, or cells at different keys. - SHEET.ORDER2 tests pass The result of a cell does not depend on the order of the evaluations.
ifErrordoes not hide a cycle from the cells on that cycle.
10. Examples
Section titled “10. Examples”- EXAMPLE.SEPARATION3 tests pass The test is
position + size - other.positionin a pair of boxes. A component that is not positive shows that the box at the origin ends before the other box starts. - EXAMPLE.NEAREST3 tests pass The minimum of the distances from the origin to the others is the distance to the nearest other record.
- EXAMPLE.OFFSETS3 tests pass The reduction with
addof the offsets from the origin to the others is the sum of the offsets. - EXAMPLE.TREE2 tests pass In a tree of people, the salary of a person plus the sum over
descendantsis the cost of the team of that person. Thecountoverancestorsis the depth of the person.