Internals · Grammar

Grammar

Every line of a Program is read against a small grammar. This page is that grammar in one place: the vocabulary each keyword is written in, before parsing ever nests the lines together into a tree. The grammar is deliberately tiny: a handful of keyword rules over a handful of shared inner rules, bottoming out in a few primitives.

Full syntax

Every keyword's Format section reuses the same handful of inner rules. This is the whole language in one grammar, in three parts: the keyword rules first, then the common inner rules they draw on, and finally the base rules for the primitive tokens everything bottoms out in. A rule written in capitals (like FIELD) is a keyword; a lowercase rule (like type) is an inner rule shared across keywords. Because every rule is defined once here, the definitions on the individual keyword pages stay consistent with one another.

Keywords

One rule per keyword. Each of these corresponds to a node in the parsed tree.

MODULE ::= "MODULE" identifier
FUNCTION ::= "FUNCTION" [ type ] identifier
INPUT ::= "INPUT" [ "BINDING" ] type identifier [ "=" constantExpression ]
HOLE ::= "HOLE" [ type ] identifier
NATIVE ::= "NATIVE" target string
TYPE ::= "TYPE" identifier
FIELD ::= "FIELD" type identifier [ "=" constantExpression ]
OPTION ::= "OPTION" type identifier
LET ::= "LET" [ type ] identifier "=" ( expression | constantExpression )
STATE ::= "STATE" [ "LOCKED" ] type identifier "=" expression
REVERSE ::= "REVERSE" type identifier
SET ::= "SET" identifier "=" expression
WITH ::= "WITH" module "=" replacement
The keyword rules. A rule in capitals is a keyword production.

Common

The shared inner rules the keyword rules draw on. Each is defined once and reused everywhere.

type ::= name [ "(" type { " " type } ")" ]
expression ::= name { " " argument }
constantExpression ::= constant | name { " " constant }
argument ::= name | constant
constant ::= string | integer | number
module ::= name
replacement ::= name
target ::= "dart" | "swift" | "js" | "nodejs" | "ts" | "go" | "rust" | "zig" | "glsl" | "php"
name ::= identifier { "." identifier }
identifier ::= letter { letter | digit }
The common inner rules, shared across keywords.

Base

The primitive tokens every other rule bottoms out in: strings, numbers, and the letters and digits they are built from.

string ::= "\"" { character } "\""
integer ::= [ "-" ] digit { digit }
number ::= integer [ "." digit { digit } ]
letter ::= "A" … "Z" | "a" … "z"
digit ::= "0" … "9"
The one remaining primitive is character — any single source code point — which string is built from.

From rules to nodes

Each keyword rule above corresponds to a node in the parsed tree. A MODULE becomes a module node; a FUNCTION becomes a function node; a LET becomes a let node. How these individual lines are nested together into that tree — and checked as they go — is parsing. The canonical JSON each one is eventually lowered to is listed under Structure.