Attributes
An attribute, written #name or #name(key=value, ...), is attached to a
parsing expression and changes how that expression is parsed.
An attribute binds as tightly as a postfix operator and applies to the expression immediately before it. When several attributes follow an expression, they are applied in the order written, from the innermost outward.
def hello = "hello" / _|_ #error(message="expect 'hello'") // #error applies to _|_
def stmt = (let / expr) #error(message="expected a statement") #recover(skip=(?^;)+ ";")
Argument values are parsing expressions; a string argument is written as a
literal. The ( MUST immediately follow the attribute name. Unknown attributes
and unknown arguments are errors.
| Attribute | Description |
|---|---|
#error(message="...") |
Replaces the expected items of a syntax error inside the expression with a message |
#recover(skip=e) |
When the expression fails, skips input matching e and continues parsing |
#stream |
In a stream parse, hands each element of a repetition to the caller as soon as it matches |
#error
When the expression fails, #error(message="...") replaces all the items that
were expected inside the expression with message. The message is reported at
the farthest position reached inside the expression.
def let = "let" " " name #error(message="expected a name") ";"
// "let 1;" → 1:5: expected a name
If several messages apply to the same position, they are joined with ; . When
a position has a message, the expected items at that position are not shown.
If the expression succeeds, the expected items and messages recorded inside it
are kept as usual.
#recover
When the expression fails, #recover(skip=e) records the syntax error and then,
starting from the position where the expression began, skips the input that e
matches. The value of the expression becomes an Error node that covers the
skipped input, and parsing continues after it.
// If a statement cannot be parsed, skip up to and including the next ";".
def stmt = s:(let / assign / call) #recover(skip=(?^;)+ ";") -> $s
Error node |
Value |
|---|---|
| Range and text | From the start of the expression to the end of the skipped input |
message field |
The recorded syntax error, including its position, as a string |
- If
efails, or matches without consuming input, there is no recovery and the expression fails. Therefore#recoverinside a repetition cannot loop forever. eMUST NOT contain captures.- Recovered errors are returned together with the resulting node. In the Go API,
Parser.Parsereturns the node and aSyntaxErrorserror that lists them. - If parsing still fails after recovery, only the final syntax error is returned.
- An error recovered inside a match that was later undone by backtracking is not reported.
- An expression that has no value (a lookahead or a predicate, for example) has none after a recovery either; the error is still recorded.
- The type of an expression with
#recoveris the union of its original type andError.Erroris assignable to every node type (see Assignability).
See examples/minilang for a complete example of error recovery.
#stream
#stream is attached to a repetition (*, + or {n,m}) at the top level of
a rule body. When that rule is the start rule of a stream parse (Go API
Parser.ParseStream, command line pego parse -stream), each element of the
repetition is handed to the caller as soon as it matches.
// Receive a log with one record per line, record by record.
def main = ^^ header records:record* #stream $$
- A stream parse reads only as much input as it needs, and discards each handed element together with the input and memoization data before it, so the input need not fit in memory.
- Handed elements are not included in the value (
List) of the repetition. - Once an element is handed over it is final: the parser never backtracks to
a position before it. For this reason,
#streamMUST appear only at the top level of a rule body: on the body itself, on an element of the body's sequence, or on the expression captured by such an element. A rule body MUST contain at most one#stream. - Starting a stream parse with a rule that has no
#streamis an error. #streamtakes no arguments.- In an ordinary parse (
Parser.Parse),#streamhas no effect: the repetition behaves as usual.