PEGO

Chapter 2

Lexical Structure

This chapter defines how grammar-file text is divided into characters, line terminators, white space, comments, identifiers, keywords, literals, escape sequences, and punctuation. The structure built from these tokens is described in Grammar Files; the complete syntax is summarized in Grammar Syntax.

Source Text

A grammar file is Unicode text encoded as UTF-8. A character outside a comment, string literal, or character class that is neither white space nor the start of a token is an error. The diagnostic includes following characters up to the next white space or token. Grammar errors use 1-based line and character-column positions; a tab counts as one character, not its byte width. Positions in parsed input are defined separately in Positions.

Line Terminators

A line ends at LF (U+000A), CRLF, or CR (U+000D). A line terminator ends a line comment and determines diagnostic line numbers. LF MUST NOT occur inside a string literal or character class. Line terminators otherwise count as white space.

White Space

White space is any Unicode character with the White_Space property, including line terminators. It separates tokens and is insignificant except where this chapter requires adjacency.

Comments

// outside a string or character data starts a line comment through, but not including, the line terminator. /* starts a nested block comment, which ends at its matching */. Comment markers inside strings or character data are ordinary content. The parser records comment trivia and may accept it as syntactic trivia; semantic analysis reports a positioned error when a comment occurs in a Declaration Statement or Operators Rule statement header. Comments are allowed at top-level statement boundaries, at statement and field boundaries inside braces, and freely inside Rule Expressions and Action Expressions, including predicates and function arguments.

rule digits = [0-9]+ // one or more digits
/* outer comment /* nested comment */ continues */

Tokens

Tokens are identifiers, string literals, character classes, integer literals, capture references, and punctuation. They are formed from left to right using the longest token available at each position.

Identifiers

An identifier matches [a-zA-Z_][a-zA-Z0-9_]*. Identifiers are case-sensitive and have no length limit. Identifier syntax is ASCII-only: non-ASCII letters and digits do not continue an identifier. Unicode remains available in string literals, character classes, comments, and parsed input. A Rule Definition name and a reference to a rule MAY begin with _: such a rule is hidden (see Hidden rules). Every other name beginning with _ is reserved and MUST NOT be used as an identifier, including type and variable names, captures, level names, attribute names and named arguments. Parsing preserves these names in the source AST; semantic validation rejects their use before compilation. The standalone primary Rule Expression form _ is the Top expression, not an available name. The words true, false and nil are the literals of the action language. A rule, type, type alias, variable, capture, or lambda parameter MUST NOT be named true, false or nil, because an action reading the name would get the literal instead. Validation rejects such a declaration with an error such as rule name true is reserved; it is a literal, and a lambda parameter with such a name is a syntax error. Longer names that merely begin with a literal, such as truely, are ordinary identifiers. Identifiers name rules, types, fields, captures, variables, levels, attributes, and lambda parameters. The initial ASCII letter's case matters for type and field names; see Names.

Contextual Words

The words infix, level, operand, package, postfix, prefix, rule, skip, struct, terminal, type, and var are contextual words, not globally reserved identifiers. They are interpreted as syntax only where the grammar expects a Declaration Statement or Operators Rule item. In a name position, rule reference, capture, field, variable, or expression, they remain ordinary identifiers. No Declaration Statement name is excluded solely because it is one of these words.

The following identifiers are also contextual:

Identifier Meaning Context
_ The Top Rule Expression Rule Expression
left, right, none Associativity After infix in an Operators Rule
true, false, nil Literal values Actions and predicates
len, text, foldl, foldr, map, filter, list, concat Built-in functions Actions and predicates
Start, End, Value Built-in node members Field access

String Literals

A string literal is enclosed in double quotes and matches exactly its decoded characters. It may contain a literal CR, but MUST NOT contain a literal LF. The quote and backslash are written as \" and \\ respectively. String literals may also be values in actions.

"if" "\n" "say \"hi\"" "😀"

A string literal MAY be followed directly by the suffix i, written "select"i. The suffix makes the ASCII letters of the literal match in either case. The i MUST immediately follow the closing quote, with no space between them, and no identifier character may follow it: in "x" i the i is a rule reference, and in "x"ia the identifier ia is a rule reference. Folding is ASCII only: A-Z and a-z match each other, and every other character matches only itself, so "é"i matches é and not É. A suffix inside the #ignorecase pragma is redundant and allowed. An expectation shows a case-insensitive literal with its suffix, as written: "select"i.

Character Classes

A character class matches one Unicode character selected by its contents. Its matching rules are defined in Character Classes. Literal characters, ranges, negation and escapes are defined there. [ begins a character class.

[a-z_] [^"\\] [\-()?]

Integer Literals

An integer literal contains one or more ASCII decimal digits (0–9). It is used for repetition bounds and in actions and predicates. A value outside the range of the implementation's int is an error. Write negative action values with unary -. The signed minimum int is also accepted as a single value: its positive magnitude cannot be a separate integer token. This preserves the full native integer range in generic attribute arguments.

"a" #repeat(2, 3) {len($s) > 4}

Capture References

$ immediately followed by an identifier forms a capture reference ($label) in actions and predicates. $ followed by ASCII decimal digits is a removed positional reference ($1): it is still a token, so that $1 is never read as an anchor followed by a number, but the parser rejects it. $ followed by any other character is the end-of-line anchor; $$ is the end-of-input anchor.

Punctuation

Punctuation tokens include:

_|_  ^^  $$  --  =>  ==  !=  <=  >=  &&  ||  :=
:  =  /  (  )  {  }  [  ]  ,  ;  .  *  +  ?  &  !  @  -  ^  $  #  |  <  >  %  //  /*  */
Token Use
`_ _, ^^, $$, ^, $, .`
-- Reserved: rejected with a diagnostic that names #cut, the cut syntax
/, *, +, ?, &, !, @, -, :, (, ), {, }, ,, ; Rule Expression operators, repetition, captures, annotations, and Declaration Statement separators
{, } Predicate delimiters where a primary Rule Expression form is expected; they also delimit source action blocks in their respective contexts
//, /*, */ Line and nested block comments
=> Whole-rule or Operators Rule action in those Declaration Statement positions; lambda arrow inside an action
:= Variable binding in an action block
==, !=, <, <=, >, >=, &&, `
=, ~, ` , [], *`
. Action field access
@ Postfix expression attribute
# Behavioral pragma

Escape Sequences

Strings and character classes accept these escapes:

Escape Character
\n LF (U+000A)
\t Horizontal tab (U+0009)
\r CR (U+000D)
\f Form feed (U+000C)
\v Vertical tab (U+000B)
\a Alert (U+0007)
\b Backspace (U+0008)
\0 NUL (U+0000)
\u{h...} Unicode code point given by hexadecimal digits, e.g. \u{1F600}
\uhhhh Unicode code point given by exactly four hexadecimal digits
\c For a character c that is not an ASCII letter or digit, the character itself; other letter or digit escapes are errors

A hexadecimal escape MUST denote a Unicode scalar value from U+0000 through U+10FFFF, excluding U+D800–U+DFFF. Invalid hexadecimal text, surrogates, and values above U+10FFFF are errors. Adjacent surrogate escapes do not combine; write the scalar directly, such as \u{1F600}. Unicode noncharacters such as U+FFFF are valid scalar values.

Adjacency

An attribute name MUST immediately follow @, and an attribute argument list MUST immediately follow its name: @name or @name(...). White space or comments between @ and the name are syntax errors. A separate group after @name (expr) is a following Rule Expression, not an argument list.

A pragma name MUST immediately follow #, and its argument list MUST immediately follow its name. White space or comments within #repeat(...) or #recover(...) are allowed inside the parentheses, but not between # and the name or between the name and (. For the argument-free #atomic and #stream forms, a separate group after the name is a following Rule Expression, just as it is after an argument-free attribute.

These forms require adjacency; intervening white space or comments change the parse:

Construct Adjacent form Separated form
Capture label:expr x:expr x :expr is invalid
Pragma #atomic, #repeat(2), #cut # atomic, #repeat (2), # cut are invalid
Action function call len($s) len ($s) is a variable followed by a grouped expression
Operators Rule level call level(expr, assignment) level (expr, assignment) is not a level call

The lexer takes the longest punctuation token. Thus -- is one reserved token, $$ is one end-of-input anchor, and $ followed by a letter, _, or digit begins a capture reference. A { begins a Predicate Action where a primary Rule Expression form is expected; [] remains a character class in Rule Expressions and a type-list prefix in Type Expressions. @ attributes, # pragmas and comment markers are recognized outside shielded string and character data.

Limits

An implementation MAY limit source nesting depth and reported errors. The reference implementation reports at most 100 errors, with the last indicating that more exist, and rejects parenthesis nesting deeper than about 500 levels.