Actions
An action is an expression written after -> in a rule definition. It computes
the value of the rule, typically an AST node, from what the parsing expression
matched.
def <rule name>: <type> = <parsing expression> -> <action>
When the parsing expression succeeds, the action is evaluated and its result
becomes the value of the rule. The result MUST be a node or nil.
The action applies to the whole right-hand side of the rule. To compute different values for different alternatives, give each alternative its own rule.
Capture references
| Syntax | Refers to |
|---|---|
$label |
The value captured with label: |
$0 |
A list of the values of all elements of the rule body that have a value |
$n |
The n-th (1-based) element with a value of the sequence that forms the rule body |
def key_value = k:(?a-z)+ op:"=" v:(?0-9)+
-> new KV{Key: $1, Op: $2, Value: $3} // same as $k, $op and $v
$n counts only elements that have a value (see
Values and the concrete syntax tree).
If the rule body is not a sequence, $1 is the value of the body. It is an
error if n exceeds the number of elements with a value.
Captures are visible only within the rule that makes them; an action cannot
refer to the captures of the rules that called it. A capture that did not match
is nil.
Variable references
An action can read variables by name, like a predicate. It sees the definitions in effect when the rule body has matched: those made by the rule itself and by the rules that called it, but not those of the rules it called, which ended with their invocations. Reading a variable that is not defined is a runtime error of the action.
type Item struct { Depth int, Name Match }
def item: Item = [depth = 1] n:name -> new Item{Depth: depth, Name: $n}
Values
Action expressions operate on the following values.
| Value | Examples |
|---|---|
| Node | $label, new T{...} |
Integer (int) |
1, len($x) |
String (string) |
"abc", text($x) |
Boolean (bool) |
true, false, 1 < 2 |
| Nil | nil |
| Function | (acc, i) => ... (only as an argument of foldl, foldr and map) |
String literals in actions use the same escape sequences as string literals in
parsing expressions (see Escape sequences). Integer literals are
decimal; a negative number is written with the unary - operator.
A list is a List node. Lists are produced by repetitions and by the built-in
functions list, map and concat.
Creating struct nodes
new T{Field: value, ...} creates a node of the struct type T. Fields are
separated by commas. A field that is not given is absent and reads as nil.
def rule = name:ident -> new IdentNode{Name: $name}
The range of a node created by new:
- If the node is the result of the action, its range is the range that the rule matched.
- Otherwise (for example, a node created inside the function passed to
foldl), its range is the smallest range that covers the ranges of its fields whose values are nodes, or the range that the rule matched if there are no such fields.
A list created by list, map or concat has the smallest range that covers
its elements; an empty list has an empty range at the start of the rule's
match.
Field access
x.Name reads the field Name of the node x.
type Name terminal
type A struct {
FieldName Name
}
def name: Name = (?a-z)+
def a: A = n:name -> new A{FieldName: $n}
def foo = a -> $1.FieldName // the Name node stored in the A node
A field that was not set, and a capture that did not match, read as nil.
Accessing a field that the struct type does not declare is an error.
When x has a union type, x.Name is allowed if at least one member type
declares Name. If only some members declare it, the static type of the access
is optional (*T). Reading a field from a value whose struct type does not
declare it is a run-time error.
The following fields are available on every node:
| Field | Type | Value |
|---|---|---|
startPos |
int |
The start position of the node in the position unit |
endPos |
int |
The end position of the node (exclusive) |
children |
[]*node |
The children of a Seq, List or Operator node; empty for other nodes |
Operators
| Operator | Operands |
|---|---|
+ |
integers (addition), strings (concatenation) |
-, *, /, % |
integers |
<, <=, >, >= |
integers, strings |
==, != |
any values; nodes are compared by identity |
&&, ||, ! |
booleans; && and || short-circuit |
unary - |
integers |
Integer division truncates toward zero; % takes the sign of the dividend.
Comparing nodes by identity tells whether two values are the same node, such as a capture and an element of a
list. Whether two separate calls of a rule at the same position return the same node is not specified: it
depends on memoization. To compare what two nodes matched, compare text(...) or their fields.
Strings are compared lexicographically.
Operators bind in the following order, from loosest to tightest:
| Precedence | Operators | Associativity |
|---|---|---|
| 1 (loosest) | || |
left |
| 2 | && |
left |
| 3 | ==, !=, <, <=, >, >= |
not chainable: a < b < c is a syntax error |
| 4 | +, - |
left |
| 5 | *, /, % |
left |
| 6 (tightest) | unary -, ! |
— |
Field access (.) and function calls bind tighter than all operators.
Parentheses group subexpressions.
Built-in functions
| Function | Description |
|---|---|
len(x) |
The length of a string or a terminal in the position unit; for any other node, the number of children; 0 for nil |
text(x) |
The input text that the node x covers (for a terminal, its text); "" for nil |
foldl(init, list, (acc, item) => ...) |
Folds list from the left |
foldr(init, list, (acc, item) => ...) |
Folds list from the right |
map(list, (item) => ...) |
A list of the results of applying the function to each element |
list(a, b, ...) |
A list of the arguments, which MUST be nodes or nil |
concat(l1, l2, ...) |
The concatenation of the lists; a nil argument, and an Error node produced by #recover, count as empty lists. An argument whose type is a union of list types (such as `[]A |
foldl and foldr
foldl and foldr fold a list into a single value. They are typically used to
turn a flat list into a recursive tree.
init: the initial value.list: the list to fold, usually the capture of a*or+repetition.(acc, item) => expression: the function applied to each element.accis the result so far anditemis the current element.foldlapplies it from the first element to the last,foldrfrom the last element to the first.
Inside the function, parameters are referred to with $, as in $acc.
// "1+2+3" → Op{Left: Op{Left: 1, Op: "+", Right: 2}, Op: "+", Right: 3}
def expr: Node =
l:term rest:(op:term_binary_op r:term)*
-> foldl($l, $rest, (acc, i) => new Op{Left: $acc, Op: $i.op, Right: $i.r})
map, list and concat
These functions are typically used to collect a separated list into a single
list. The function passed to map MUST return a node or nil.
def args = first:expr rest:(-"," e:expr)*
-> concat(list($first), map($rest, (r) => $r.e))
Types
Action expressions are type-checked. For example, a value that is not assignable to the type of a struct field, or an access to a field that does not exist, is a compile-time error.
Run-time errors
An error during the evaluation of an action, such as reading a field that the node's type does not declare or dividing by zero, aborts the whole parse with that error. It is not a syntax error and is not subject to backtracking or error recovery.