Chapter 11
Pragmas
A # pragma changes the behavior or structure of the immediately preceding
expression, except #cut and #operators, which stand where an expression would. Pragmas apply from left to right in written order. Parenthesize a
sequence when a pragma should apply to the complete sequence rather than its
final item. Character classes in square brackets are primary Rule Expression forms, not pragmas.
Diagnostic and metadata @ attributes are defined separately in
Attributes.
Pragmas have pragma-specific argument syntax. For example, recovery retains
the named skip argument in #recover(skip=expression); it does not use the
generic positional and named argument binding of attributes.
The name is adjacent to #, and an argument list is adjacent to its name:
#repeat(2) is valid; # repeat(2) and #repeat (2) are syntax errors.
| Pragma | Description |
|---|---|
#recover(skip=e) |
On failure, skips input matching e and continues parsing |
#missing |
With opt-in Closure parsing, inserts a nonempty literal at an empty range |
#repeat(n) |
Repeats exactly n times |
#repeat(n,) |
Repeats at least n times |
#repeat(,m) |
Repeats from zero through m times |
#repeat(n,m) |
Repeats from n through m times |
#stream |
Hands each element of a repetition to the caller as soon as it matches in a stream parse |
#atomic |
Returns one string node and suppresses construction of the values of the elements inside it |
#ignorecase |
Makes the string literals written in the annotated expression case-insensitive, for ASCII letters; see #ignorecase |
#cut |
Stands in place of an expression and commits the enclosing choice; see Cut. It takes no arguments: #cut( is an error |
#operators { ... } |
Declares an Operators Rule. It MUST be the whole body of a rule, with its { on the same line; anywhere else it is a syntax error |
#ignorecase
e #ignorecase makes every string literal written in e match the ASCII
letters of its characters in either case. A literal written with the suffix
i, such as "select"i, is case-insensitive without the pragma (see
String Literals).
rule keyword = ("select" / "from") #ignorecase
rule select = "select"i
The pragma is lexical. It reaches the literals of e at any depth: under a
sequence, a choice, a repetition, a lookahead (! and &) or a capture, and in
the expression arguments of other pragmas. It does not reach a rule that e
calls, and it does not change a character class, which is already a set of
characters. Folding is ASCII only, as for the suffix:
"é" #ignorecase matches é and not É.
The pragma takes no arguments. Its expectations show each case-insensitive
literal with its suffix, so ("select") #ignorecase expects "select"i. The
#missing pragma inserts the spelling written in the literal, without the suffix
and without any change of case. The pragma follows an expression, like the other
behavioral pragmas, and applies from left to right, so "a" #ignorecase "b"
folds only "a".
An Operators Rule is the whole body of its rule, so the pragma follows its closing brace on the same line:
rule expr = #operators {
skip " "*
operand "one" / [0-9]
level { infix left "plus" }
} #ignorecase
The pragma then covers the literals of the operands, the operators and the skip expression of the rule. It is the only pragma that may follow an Operators Rule; any other is a syntax error.
#recover
On failure, #recover(skip=e) records the syntax error and starts matching
e from the annotated expression's initial position. If e matches and
consumes input, the expression produces an error value covering the skipped
input and parsing continues.
// If a statement fails, skip through its next semicolon.
rule stmt = s:(let / assign / call) #recover(skip=[^;]+ ";") => $s
The skip expression cannot contain captures and must consume input. Recovered
errors are returned with the resulting node. Backtracking that undoes a
recovery also removes its error. A recovered expression's type is the join
of its original type and error, and an error value is assignable only to a type that admits error
(see Type System).
Parser.Parse returns a recovered value together with a SyntaxErrors error
listing the diagnostics. If parsing later fails, only the final syntax error
is returned. A no-value expression, including lookahead or a predicate,
remains no-value after recovery, though its error is recorded.
Recovery proceeds in these steps.
- The annotated expression is tried. If it matches, nothing else happens.
- If it fails, the syntax error that would have ended the parse is recorded.
- The parser returns to the position where the annotated expression started and matches the skip expression there. The skip expression is matched from that starting position, not from the position of the failure, so the error position is usually inside the skipped range.
- If the skip expression matches and consumes input, the value of the annotated
expression is an error value and parsing continues after the skipped input.
If it fails or consumes nothing, there is no recovery and the annotated
expression fails as usual. A
#recoverin a repetition therefore cannot loop on empty skips.
The error value has the skipped input as its range and the recorded error with its
position as its message (1:5: expected a name); Node.Text() returns the
message. Its Kind is KindError, and it encodes as {"start", "end", "error"}
(see Nodes and Values).
Recovery applies to every failure inside the annotated expression, however deeply
nested; the nearest enclosing #recover handles it. Attributes and pragmas apply
from the innermost outward in the order written, so @error(...) #recover(...)
labels the expression before recovery and the recorded error carries the label,
whereas in #recover(...) @error(...) the label applies to the recovered
expression, which fails only when recovery is impossible, so the label is used only
in that case.
A recovery on a path that the parser then abandons is discarded with the path; a memoized result carries its recovered errors, so a reused result still reports them. If the parse fails in the end, only the final syntax error is returned and the errors recovered earlier are not. When recovery is impossible, the ordinary syntax error is returned, and its expected items include the first items of the skip expression. The Go and command-line results are defined in Parsing.
#stream
#stream annotates a repetition at the top level of the start rule used by a
stream parse. Each matched element is handed to the caller as soon as it
matches.
// Receive a log one record at a time.
@start
rule main = ^^ header records:record* #stream $$
Handed elements are not included in the repetition's returned list. Once handed over, an element is final and the parser does not backtrack before it. If a stream diagnostic callback is selected, committed recovery errors within an element are reported immediately before that element is handed over. A diagnostic callback error stops the parse without handing over that element; errors from branches later backtracked are never reported.
Therefore a rule body contains at most one #stream, on the complete body, a
top-level sequence element, or the expression captured by such an element.
#recover cannot wrap the same repetition in either order. In an ordinary
parse, #stream has no effect. A stream parse with no #stream on its start
rule is an error. The entry points, placement rules, errors and options of stream
parsing are defined in Streaming.
A stream parse reads only as much input as needed and may discard each handed element with preceding input and memoization data. Decoding a complete character needs no future bytes; an incomplete UTF-8 prefix needs more bytes or actual EOF, while a non-EOF reader error terminates parsing. The first zero-width successful repetition iteration is handed over once and stops the repetition even below its minimum; a minimum failure then returns an error without retracting the handed value. Ordinary repetitions use the same stop and count rule.
#atomic
Postfix expr #atomic matches expr but returns a single string node
and suppresses construction of the values of the expressions inside it. It does not
commit to the match: ordinary PEG backtracking and choice behavior remain in
effect.
#repeat
#repeat takes positional nonnegative integer bounds only. Its comma
distinguishes an exact count from an unbounded maximum: #repeat(n) is exactly
n, #repeat(n,) is at least n, #repeat(,m) is at most m, and
#repeat(n,m) is bounded. The pragma applies to the immediately preceding
expression; parenthesize a sequence to repeat it as a unit. Named bounds are
not accepted.
#missing
#missing takes no arguments and may annotate only a nonempty literal. In
Closure whole-input parsing or recognition, a positive WithMissingTokens(n)
allows that literal to insert its entire expected string when the ordinary
match fails. Omission or zero disables insertion. A negative effective limit
is a configuration error; parse options resolve once in order, last value wins.
@start
rule main = name ";" #missing $$
rule name = [a-z]+ #atomic
Parsing name with insertion enabled produces an error value of type missing
at EOF and a recovered SyntaxErrors entry. The value's Start and End are
equal, and the string fields expected and message carry the expected literal
and diagnostic; it encodes with an expected member. Partial literal matches are restored completely before insertion;
no source text is invented. An enclosing @error labels the insertion diagnostic;
nested labels use the outermost applicable explicit label. Ordinary successful
consuming recovery keeps its existing label behavior.
Insertion succeeds at most once per literal site and position on the current
candidate path. Backtracking removes inserted values and diagnostics, but does
not refund attempted insertions. Installing events from a growing LR seed also
costs attempts unless already installed on the path. Returning a selected seed
can exhaust the budget. A selected Operators Rule candidate restores its already
charged synthesis without charging again. Budget exhaustion is a fatal
MissingTokenLimitError with Limit and Position; it returns no tree and is
never ordinary syntax failure. The limit bounds work, not the final missing-node
count, so abandoned alternatives can exhaust it.
Lookahead always disables insertion, including rule calls. Insertion consumes no input and does not change repetition or LR progress: one zero-progress repetition iteration counts once, then stops; later LR growth must advance input. Ordered choice still selects its first successful alternative, including one that inserts. Use lookahead when insertion must be restricted to a particular boundary.
A successful parse with insertions returns its tree and committed diagnostics in encounter order; recognition returns the same diagnostics with no tree. A failed whole-input parse returns only its final ordinary syntax error. Each invocation has fresh insertion state, including concurrent and reentrant invocations.
Only Closure whole-input Node parsing and recognition currently support this
pragma. Prefix parsing, Document/streams, bytecode VMs, generated Go/TypeScript
parsers and binary serialization reject grammars containing #missing or
references to missing, even when insertion is disabled. Positive insertion
options also fail on unsupported parse entry points, including ParsePrefix,
even when the grammar contains no #missing annotation. Zero preserves strict
prefix matching for grammars without #missing. Grammar AST JSON and formatting
preserve the annotation. missing is a built-in type, a kind of error: the
annotated literal infers node[string] | missing, and missing is assignable to
error. Declare a join or keep the Node tree when
consumers need the missing-token identity. Concrete generated typed mappings and
additional backends are outside the initial implementation.