Chapter 12
Grammar Syntax
This chapter collects the syntax of a grammar file in one place. The grammar below is written in PEGO itself and summarizes token structure and expression forms. The chapters that define each construct remain normative for validation and meaning.
The repository holds a full grammar of the language, pego.pego, which is the input
of the generated parser of grammar files. Its actions produce a typed source tree
with comments, source forms, and recoverable error nodes. This tree is separate from
the public grammar.Grammar AST.
The grammar is checked by tests, which run both views on every
.pego file of the repository and on every pego code block of the
documentation and require them to agree with the parser of the reference
implementation on whether the text is a valid grammar.
Simple Grammar
This syntax view omits result types, Value Actions, captures, metadata attributes
and recovery handling. Unnecessary grouping is removed while retaining
parentheses required by precedence or source layout. Predicate Actions and matching pragmas remain, including
the state that controls physical newlines. Generate the view with
pego fmt -syntax pego.pego or grammar.FormatSyntax(g) in Go. This is a
readable projection: in other grammars, a predicate may depend on captures,
constructed values or types that the simplified view omits.
// PEGO grammar syntax and typed source tree.
// Source forms are preserved before semantic validation and compilation.
package pego
// ml reports whether line breaks are allowed between tokens inside the enclosing delimiters.
var ml bool
// File and statements. Delimiter-owned expressions enable multiline trivia;
// declaration and item lists keep physical newlines as statement boundaries.
@start(default)
rule main = file
@start
rule file = &source_comments &source_gaps &.* #atomic file_contents
rule file_contents = (
{ml := false}
&source_head
package_entry?
decl*
comments $$
)
rule package_entry = &(comments "package" !ident_char) package_stmt
rule package_stmt = (
&source_remaining comments
package_body &source_item_tail stmt_end
)
rule package_body = "package" -word_end #cut ident
rule decl = (
!(comments $$)
(type_decl / var_decl / rule_decl / _|_)
)
rule stmt_end = horizontal stmt_sep
rule item_end = horizontal item_sep
rule field_end = horizontal field_sep
rule stmt_sep = block_sep / empty_stmt_sep
rule item_sep = block_sep / empty_item_sep
rule field_sep = block_sep / empty_field_sep
rule block_sep = block_with_line
rule empty_stmt_sep = ";" / nl / &$$
rule empty_item_sep = ";" / nl / &"}"
rule empty_field_sep = ";" / nl / &"}"
// Recovery consumes its own separator and stops before the next item.
rule stmt_sync = -comments (sync_token / !(";" / nl) .)* (";" / nl / &$$)
rule item_sync = -comments (sync_token / !(";" / nl / "}") .)* (";" / nl / &"}" / &$$)
rule field_sync = -comments (sync_token / !(";" / nl / "}") .)* (";" / nl / &"}" / &$$)
// Completed literals, nested comments and groups do not end recovery.
rule sync_token = string / char_class / inline_comment / comment_line / sync_paren / sync_brace
rule sync_paren = "(" (sync_token / !("(" / ")" / nl) .)* ")"
rule sync_brace = "{" (sync_token / !("{" / "}" / nl) .)* "}"
// Types. Join is the weakest operation; list and optional prefixes nest.
// A type declaration is a type alias (type Name ~ Type) or a type definition
// (type Name = { ... } or type Name = node[T]).
rule type_decl = type_alias / type_def
rule type_alias_head = comments "type" -word_end ident -ws "~"
rule type_alias = &type_alias_head comments type_alias_body &source_item_tail stmt_end
rule type_def = comments type_def_body &source_item_tail stmt_end
// A body starts at the keyword and ends before the statement separator.
// Its typed node retains the actual parse span without repeating recovery.
rule type_alias_body = "type" -word_end #cut ident -ws "~" -ws type_expr
rule type_def_body = (
"type" -word_end #cut ident -ws
-("=" -ws &("{" / "node["))
(struct_type / node_type)
)
// The node type of a named node type takes one type argument.
rule node_type = (
&"node[" ident "[" {ml := true} -ws type_expr -ws ","? -ws
-"]"
)
rule field_spec = !(comments "}") field
rule field = comments field_body &source_item_tail field_end
rule field_body = ident -ws type_expr
rule type_expr = #operators {
skip ws
operand bottom
operand !retired_type ident type_args
operand !retired_type ident
operand type_group
operand struct_type
level join { infix left "|" }
level meet { infix left "&" }
level prefix {
prefix "[]"
prefix "*"
}
}
// An unnamed struct type has the fields of a struct definition.
rule struct_type = (
"{" {ml := false} &source_head_open field_spec* comments
-"}"
)
// The node type names of earlier versions, and terminal, are not types.
rule retired_type = ("terminal" / "Match" / "Seq" / "List" / "Operator" / "Error") !ident_char
// The brackets of type arguments follow the name directly.
rule type_args = "[" {ml := true} -ws type_expr (-ws "," -ws type_expr)* -ws ","? -ws "]"
// A rule of its own keeps ml from reaching the rest of the type. Parentheses
// around one type without a comma group it; any other parentheses form a tuple.
rule type_group = type_paren / type_tuple
rule type_paren = "(" {ml := true} -ws type_expr -ws ")"
rule type_tuple = "(" {ml := true} -ws (type_expr -ws "," -ws)* type_expr? -ws ")"
// A variable definition gives a name, a type, an initial value, or a type with a value.
rule var_decl = comments var_def_body &source_item_tail stmt_end
rule var_def_body = "var" -word_end #cut ident var_type? var_value?
// The whitespace before the type and the value belongs to them, so a declaration with neither
// ends at its name and does not take in a trailing comment.
rule var_type = -ws type_expr
rule var_value = -ws "=" -ws action_expr
// Rule expressions. The empty infix operator represents adjacency, so
// juxtaposition does not need a separate sequence parser or require a space.
rule rule_decl = comments rule_def_body &source_item_tail stmt_end
rule rule_def_body = (
(attr decl_attr_gap)*
"rule" -word_end #cut ident -ws type_expr? -ws
-"=" -ws (ops_rule_body / rule_expr) trailing_action?
)
// Declaration attributes may share a line; the last one ends its line before "rule".
rule decl_attr_gap = decl_attr_same_line / decl_attr_line_end
rule decl_attr_same_line = horizontal &"@"
rule decl_attr_line_end = horizontal -nl comments
rule whole_action = "=>" -ws action
// The spacing before an action belongs to the action, so a rule without one ends at its expression.
rule trailing_action = -ws whole_action
rule rule_expr = #operators {
skip ws
operand rule_group / predicate / level_call / bottom / top / begin_input / begin_line / end_input / end_line / any / cut / string / char_class / rule_ref
level ordered_choice { infix left "/" }
level sequence { infix left _ }
level prefix {
prefix ident ":"
prefix "&" / "!"
prefix "-" !("-" / ">")
}
level suffix {
postfix "?"
postfix "*"
postfix "+"
postfix "#atomic" !ident_char (empty_args / !"(")
postfix "#stream" !ident_char (empty_args / !"(")
postfix "#missing" !ident_char !"("
postfix "#ignorecase" !ident_char !"("
postfix "#repeat" !ident_char repeat_args
postfix "#recover" !ident_char recover_args
postfix attr
}
}
rule rule_group = (
"(" {ml := true} -ws (inline_action / rule_expr) -ws
-")"
)
rule inline_action = rule_expr -ws "=>" -ws action
rule rule_ref = ident !"("
rule level_call = (
"level(" {ml := true} -ws #cut ident -ws
-"," -ws
ident -ws
-")"
)
rule top = ("_" !ident_char) #atomic
rule bottom = "_|_" #atomic
rule begin_input = "^^" #atomic
rule begin_line = "^" #atomic
rule end_input = "$$" #atomic
rule end_line = ("$" !(ident_start / digit)) #atomic
rule any = "." #atomic
rule cut = ("#cut" !ident_char !"(") #atomic
rule predicate = action_block
rule repeat_args = (
"(" {ml := true} -ws (repeat_range / repeat_count) -ws
-")"
)
rule repeat_range = repeat_from / repeat_to
rule repeat_from = repeat_int -ws "," -ws repeat_int?
rule repeat_to = "," -ws repeat_int
rule repeat_count = repeat_int
rule repeat_int = (digit+ / "-" -ws "0"+ !digit) #atomic
rule recover_args = (
"(" {ml := true} -ws -"skip" -word_end
-"=" -ws rule_expr -ws
-")"
)
rule empty_args = "(" {ml := true} -ws ")"
rule attr = "@" ident (attr_args / attr_no_args)
rule attr_args = (
"(" {ml := true} -ws (attr_no_values / attr_named_args / attr_mixed_args) -ws
-")"
)
rule attr_named_args = attr_named (-ws "," -ws attr_named)* (-ws "," -ws)?
rule attr_mixed_args = attr_slot (-ws "," -ws attr_slot)* (attr_named_tail / attr_trailing_comma)
rule attr_no_args = !"("
rule attr_no_values = &")"
rule attr_trailing_comma = (-ws "," -ws)?
rule attr_named_tail = -ws "," -ws attr_named_args
rule attr_slot = !(ident -ws "=") attr_value
rule attr_named = ident -ws "=" -ws attr_value
rule attr_value = true_lit / false_lit / nil_lit / signed_int / string / rule_expr
rule signed_int = (("-" -ws)? digit+ #atomic) #atomic
// #ignorecase may follow the block of an Operators Rule on its line, and covers the literals of the
// whole rule. No other pragma may.
rule ops_rule_body = ops_rule_ignorecase / ops_rule_plain
rule ops_rule_ignorecase = ops_rule_expr -ws "#ignorecase" !ident_char !"("
rule ops_rule_plain = ops_rule_expr -(-ws &("#" !("ignorecase" !ident_char)) _|_)?
// Operators Rule items and each level recover independently. An absent level
// name remains nil; level(rule, name) can target only named levels.
rule ops_rule_expr = (
"#operators" !ident_char -ws &"{" #cut "{" {ml := false} &source_head_open ops_stmt*
comments -"}"
)
rule ops_stmt = (
!(comments "}")
(ops_skip_stmt / ops_operand_stmt / ops_level_stmt / _|_)
)
rule ops_skip_stmt = comments ops_skip_body &source_item_tail item_end
rule ops_skip_body = "skip" -word_end #cut rule_expr
rule ops_operand_stmt = comments ops_operand_body &source_item_tail item_end
rule ops_operand_body = "operand" -word_end #cut rule_expr trailing_action?
rule ops_level_stmt = comments ops_level_body &source_item_tail item_end
rule ops_level_body = (
"level" -word_end #cut ident? -ws
-"{" &source_head_open ops_operator*
comments -"}"
)
rule ops_operator = (
!(comments "}")
(ops_prefix / ops_postfix / ops_infix / _|_)
)
rule ops_prefix = comments ops_prefix_body &source_item_tail item_end
rule ops_prefix_body = "prefix" -word_end #cut rule_expr trailing_action?
rule ops_postfix = comments ops_postfix_body &source_item_tail item_end
rule ops_postfix_body = "postfix" -word_end #cut rule_expr trailing_action?
rule ops_infix = comments ops_infix_body &source_item_tail item_end
rule ops_infix_body = (
"infix" -word_end #cut
("left" / "right" / "none")
-word_end rule_expr trailing_action?
)
// Predicate Actions and Value Actions share statement syntax. The source AST
// retains unsupported blocks for later compilation checks.
rule action = default_action / action_block / action_expr
rule default_action = "..." #atomic
rule action_block = (
"{" {ml := false} action_block_stmt* comments
-"}"
)
rule action_block_stmt = !(comments "}") action_block_value
rule action_block_value = -comments action_stmt -item_end
rule action_stmt = action_bind_stmt / action_expr
rule action_bind_stmt = ident -ws ":=" -ws action_expr
rule action_expr = lambda / action_ops
rule action_ops = #operators {
skip !(ws "[" !"]") ws
operand list_constructor / constructor / call / action_tuple / action_group / int / string / true_lit / false_lit / nil_lit / capture_ref / var_ref
level disjunction { infix left "||" }
level conjunction { infix left "&&" }
level comparison { infix none "==" / "!=" / "<=" / ">=" / "<" / ">" }
level addition { infix left "+" / "-" !("-" / ">") }
level multiplication { infix left "*" / "/" / "%" }
level unary { prefix "-" !"-" / "!" !"=" }
// x[i] indexes a list. The "[" of an index follows its operand directly: the skip leaves
// white space and comments before a "[" unconsumed, so "x [i]" is an error. A list
// constructor starts with "[]" and keeps its skip.
level field_access {
postfix "." -ws capture_ref
postfix "." -ws cast_type
postfix "." -ws ident
postfix index_suffix
}
}
// The brackets of an index open their own delimiter, and a rule of its own keeps ml from
// reaching the rest of the action.
rule index_suffix = "[" {ml := true} -ws action_expr -ws "]"
// A rule of its own keeps ml from reaching the rest of the action.
rule cast_type = "(" {ml := true} -ws type_expr -ws ")"
rule lambda = lambda_head -ws "=>" -ws action_expr
// A rule of its own keeps ml from reaching the arrow and the body.
rule lambda_head = "(" {ml := true} -ws lambda_params? -ws ")"
rule lambda_params = lambda_param (-ws "," -ws lambda_param)* (-ws ",")?
// A parameter cannot take the name of a literal, which the literal would hide.
rule lambda_param = !((true_lit / false_lit / nil_lit) -ws &("," / ")")) ident
// Parentheses around one expression without a comma group it; any other
// parentheses form a tuple. The tuple rules fail on a group without an error.
rule action_tuple = action_unit / action_elems
rule action_unit = "(" {ml := true} -ws ")"
rule action_elems = (
"(" {ml := true} -ws (action_expr -ws "," -ws)+ action_expr? -ws
-")"
)
rule action_group = (
"(" {ml := true} -ws action_expr -ws
-")"
)
rule call = (
var_name "(" {ml := true} -ws #cut action_args? -ws
-")"
)
rule action_args = action_expr (-ws "," -ws action_expr)* (-ws "," -ws)?
rule list_constructor = (
"[]" -ws #cut level(type_expr, prefix) -ws
-"{" {ml := true} -ws
action_args? comments -"}"
)
rule constructor = (
var_name -ws "{" &source_head_open {ml := true} -ws #cut (constructor_fields / action_args)? comments
-"}"
)
rule constructor_fields = init_field (-ws "," -ws init_field)* (-ws "," -ws)?
rule init_field = comments init_field_body &source_comma_tail
rule init_field_body = (
ident -ws -":" -ws
action_expr
)
rule int = digit+ #atomic
rule true_lit = ("true" !ident_char) #atomic
rule false_lit = ("false" !ident_char) #atomic
rule nil_lit = ("nil" !ident_char) #atomic
rule capture_ref = ("$" ident #atomic) #atomic
rule var_name = ident #atomic
rule var_ref = (ident !"(") #atomic
// Trivia. Space is discarded before and after each retained comment, keeping
// the resulting list homogeneous without introducing nil whitespace elements.
rule ws = {ml} comments / horizontal
rule comments = -space* (comment -space*)*
rule horizontal = -space_no_line* ((line_comment / block_no_line) -space_no_line*)*
rule word_end = !ident_char -ws
rule space_no_line = !nl space
rule nl = "\r\n" / [\r\n]
rule space = [ \t\n\u{b}\u{c}\r\u{85}\u{a0}\u{1680}\u{2000}-\u{200a}\u{2028}\u{2029}\u{202f}\u{205f}\u{3000}]
// A lexical comment view retains trivia inside parsing and action expressions.
// Incomplete tokens remain available to the error-recovering structural pass.
rule source_comments = -source_non_comment* (comment -source_non_comment*)* $$
rule source_non_comment = source_string / source_class / incomplete_comment / !("//" / "/*") .
rule source_string = "\"" ("\\" . / [^"\\\n])* "\""?
rule source_class = "[" ("\\" . / [^\]\\\n])* "]"?
rule incomplete_comment = !inline_comment "/*" .* $$
rule comment = comment_group / inline_comment
rule comment_group = comment_line (-nl -space_no_line* comment_line)*
rule line_comment = comment_line
rule comment_line = ("//" [^\n\r]*) #atomic
rule inline_comment = "/*" (inline_comment / !("/*" / "*/") .)* #atomic "*/"
rule block_no_line = "/*" (block_no_line / !("/*" / "*/" / nl) .)* #atomic "*/"
rule block_with_line = (
"/*"
((block_no_line / !("/*" / "*/" / nl) .)* (nl / block_with_line) (inline_comment / !("/*" / "*/") .)*)
#atomic "*/"
)
rule ident = (ident_start ident_char*) #atomic
rule ident_start = [a-zA-Z_]
rule ident_char = [a-zA-Z0-9_]
rule digit = [0-9]
rule string = ("\"" ("\\" escape_char / [^"\\\n])* "\"" #atomic ("i" !ident_char)?) #atomic
rule char_class = (
"[" "^"? (char_class_rng / char_class_chr)+
-"]"
)
rule char_class_rng = char_class_chr "-" !"]" char_class_chr
rule char_class_chr = ("\\" escape_char / [^\]\\\n]) #atomic
rule escape_char = "u{" [0-9a-fA-F]+ "}" / "u" [0-9a-fA-F] #repeat(4) / [ntrfvab0] / [^a-zA-Z0-9]
// Normalized declaration ownership, independent of raw formatter comments.
// The lookahead rules never fail, so recording ownership cannot change what a
// statement accepts: an unrecognizable boundary has no comments and no next owner.
rule source_head = source_remaining / source_empty_boundary
rule source_head_open = source_after_delimiter_boundary / source_empty_boundary
rule source_empty_boundary = _
// The boundary after a statement. A separator, when present, is a token of its own
// with its own boundary after it: a trailing group there follows the groups before
// the separator, and the next sibling's leading groups come from the boundary after it.
rule source_item_tail = source_tail_after_separator_trailing / source_tail_after_separator / source_tail_plain / source_tail_empty
rule source_tail_after_separator_trailing = source_after_boundary source_semis source_after_boundary_trailing
rule source_tail_after_separator = source_after_boundary source_semis source_after_boundary
rule source_tail_plain = source_after_boundary
rule source_tail_empty = _
rule source_semis = ";" (-source_trivia* ";")*
rule source_trivia = source_space / source_line_comment / source_block_comment
// Constructor fields have a comma as their separator.
rule source_comma_tail = source_comma_after_separator_trailing / source_comma_after_separator / source_tail_plain / source_tail_empty
rule source_comma_after_separator_trailing = source_after_boundary "," source_after_boundary_trailing
rule source_comma_after_separator = source_after_boundary "," source_after_opener_boundary
rule source_after_boundary = (
source_inline_line_boundary / source_inline_run_boundary / source_inline_block_boundary / source_ambiguous_block_boundary
/ source_newline_boundary / source_remaining
)
rule source_inline_line_boundary = -source_horizontal source_line_comment source_remaining
// The comments on the line of the previous token all trail it when a line break or
// the end of the scope follows them: block comments, then at most one line comment.
rule source_inline_run_boundary = (-source_horizontal source_block_comment)+ source_run_tail source_remaining
rule source_run_tail = source_run_line / source_run_end
rule source_run_line = -source_horizontal source_line_comment
rule source_run_end = &(source_horizontal (source_nl / source_scope_end))
rule source_inline_block_boundary = (
-source_horizontal source_block_comment &(source_horizontal (source_nl / source_comment_start / source_scope_end))
source_remaining
)
// The boundary after an opening delimiter or a comma. One inline block ending on
// the next token's line leads that token there.
rule source_after_opener_boundary = (
source_inline_line_boundary / source_inline_run_boundary / source_inline_block_boundary / source_opener_block_boundary
/ source_newline_boundary / source_remaining
)
rule source_opener_block_boundary = -source_horizontal source_block_comment -source_horizontal &source_next_token
// The boundary after an opening delimiter, which is not a node. The comments on its line lead
// the next token as if they were on lines of their own; before a closing delimiter they stay
// the group of the delimiter.
rule source_after_delimiter_boundary = source_delimiter_scope_boundary / source_remaining
rule source_delimiter_scope_boundary = &source_scope_run source_after_opener_boundary
rule source_scope_run = -source_space* (source_comment -source_space*)* source_scope_end
// One inline block ending on the next token's line is ambiguous, not trailing.
rule source_ambiguous_block_boundary = -source_horizontal source_block_comment -source_horizontal &source_next_token
rule source_newline_boundary = -source_horizontal -source_nl -source_horizontal source_first_trailing source_remaining
// A boundary that has a trailing group, even an empty one. After a separator it
// replaces the trailing group found before the separator.
rule source_after_boundary_trailing = source_inline_line_boundary / source_inline_run_boundary / source_inline_block_boundary / source_newline_trailing_boundary
rule source_newline_trailing_boundary = -source_horizontal -source_nl -source_horizontal source_first_trailing source_remaining
// A first standalone group can trail the previous declaration when a blank
// line, another group, or the end of a scope flushes it before the next token.
rule source_first_trailing = source_line_group &source_line_flush / source_block_comment &source_block_flush
rule source_remaining = source_scope_tail / source_next_boundary
rule source_scope_tail = -source_space* (source_comment -source_space*)* &source_scope_end
rule source_next_boundary = -source_space* (source_detached_group -source_space*)* (source_leading_group / source_empty_comment) -source_space* &source_next_token
rule source_empty_comment = _
rule source_detached_group = source_line_group &source_line_flush / source_block_comment &source_block_flush
rule source_leading_group = source_comment -source_space* &source_next_token
rule source_line_flush = source_horizontal (source_nl / "/*" / source_scope_end)
rule source_block_flush = source_horizontal (source_nl source_horizontal (source_nl / source_comment_start / source_scope_end) / source_comment_start / source_scope_end)
rule source_next_token = !(source_space / source_comment_start / source_scope_end) .
rule source_scope_end = ")" / "]" / "}" / ";" / $$
rule source_comment_start = "//" / "/*"
rule source_comment = source_line_group / source_block_comment
rule source_line_group = source_line_comment (-source_horizontal source_line_comment)*
rule source_line_comment = "//" [^\r\n]* source_nl?
rule source_block_comment = "/*" source_block_line (source_nl -source_horizontal -source_block_star? source_block_line)* "*/"
rule source_block_star = "*" !"/"
rule source_block_line = source_block_piece*
rule source_block_piece = source_nested_piece / source_character_piece
rule source_nested_piece = source_block_comment
rule source_character_piece = (!(source_nl / "*/" / "/*") .) #atomic
rule source_nl = "\r\n" / [\r\n]
rule source_horizontal = source_horizontal_char*
rule source_horizontal_char = [ \t\u{b}\u{c}\u{85}\u{a0}\u{1680}\u{2000}-\u{200a}\u{2028}\u{2029}\u{202f}\u{205f}\u{3000}]
rule source_space = source_horizontal_char / source_nl
// Every gap between tokens that holds a comment, in source order. Tokens are
// the characters between trivia; strings and classes hide comment markers.
// A gap at the start of the input has no token before it and so no trailing
// group. The scan never fails, so recording gaps cannot change what is accepted.
rule source_gaps = source_gap_head? -source_gap_skip* ((-source_delimiter_char source_delimiter_gap / -"," source_open_gap / source_gap) -source_gap_skip*)* $$
// A bracket directly after a name, ")", "]" or "}" is a bracket, not a character class.
rule source_gap_skip = !source_gap_opens (source_string / source_bracket / source_class / incomplete_comment / source_space+ !(source_line_comment / source_block_comment) / !(source_space / source_comment_start) .)
// An opening delimiter or a comma followed by a comment: the gap after it belongs to the opener.
rule source_gap_opens = source_open_char &source_gap_run
rule source_open_char = source_bracket / [({,]
rule source_delimiter_char = source_bracket / [({]
rule source_bracket = (ident_char / ")" / "]" / "}") "["
// A gap starts with a comment, after any space, and runs to the next token.
rule source_gap_run = -source_space* (source_line_comment / source_block_comment) (source_space / source_line_comment / source_block_comment)*
rule source_gap_head = ^^ &source_gap_run &(source_remaining / source_empty_boundary) -source_gap_run
rule source_gap = &source_gap_run &(source_after_boundary / source_empty_boundary) source_gap_flag? -source_gap_run
rule source_open_gap = &source_gap_run &(source_after_opener_boundary / source_empty_boundary) source_gap_flag? -source_gap_run
rule source_delimiter_gap = &source_gap_run &(source_after_delimiter_boundary / source_empty_boundary) source_gap_flag? -source_gap_run
rule source_gap_flag = &source_after_boundary_trailing
What the Grammar Does Not Express
The grammar includes the ASCII identifier form. Names beginning with _ are
parsed and preserved. Semantic validation rejects them, except as a rule name. The following
additional constraints are checked outside this source grammar.
- Reserved identifiers. Names beginning with
_are reserved, except the name of a Rule Definition and a reference to a rule, which hide the rule (see Lexical). The standalone Rule Expression_is Top, which is distinct from a name. - Integer literals. An integer literal
MUST be representable as an
int. - Character class ranges. In
lo-hi,hiMUST NOT be less thanlo, and a class MUST contain at least one item. - Repetition bounds. In
a #repeat(n,m),mMUST NOT be less thann. - Statement boundaries. Newlines and semicolons separate Declaration Statements,
Operators Rule items, struct fields and action-block statements. A newline ends a
statement, and a semicolon ends it only on the same line:
a string // tfollowed by a line holding; b stringreads asa string, an empty statement, thenb string. An empty statement, such as a repeated semicolon or a semicolon that starts a line, is an error in every statement list; comments between statements are fine. An expression may cross a newline only within its own open delimiter, so a type definition keeps=and its{ornode[on one line,=>keeps its action or action block on its line, and in an action a newline before a binary operator,.,=>, a constructor{or an index[ends the expression. Statement-list braces do not enable continuation of every nested expression. Indentation is not syntax. - Index brackets. In an action expression the
[of an index follows the indexed value directly, such asx[0],f(x)[0]or[]int{1, 2}[0]. White space or a comment before it is an error; the brackets are an open delimiter, so their contents may span lines. - Attribute arguments. Positional arguments follow the generic value forms and precede named arguments; repeated named keys are rejected. The grammar models argument shape and ordering, not key uniqueness.
- Contextual words. Declaration Statement and Operators Rule item introducers have their syntactic meaning only at those statement positions. They remain identifiers elsewhere.
- Where constructs may appear. An Operators Rule MUST be a complete Rule Declaration Statement; a lambda MUST be an argument of
foldl,foldrormap; and#streamMUST be at the top level of a rule body. A comment in a Declaration Statement or Operators Rule statement header is rejected by semantic analysis with a positioned diagnostic. These checks follow parsing. - Action blocks. Multiple-statement Predicate Actions and every braced Value Action are retained in the source AST but compilation rejects them.
- Inline actions.
(expression => action)is recognized in the source grammar and represented in the AST. Compilation executes it as a hidden rule at its position (see Actions); an inline braced Value Action is rejected. - Attribute placement. Attributes are postfix Rule Expression annotations,
or declaration attributes on the
lines before
rule, where a newline separates the last attribute fromrule. Other Declaration Statements take no attributes.
Operators
Rule Expressions bind in the order of the precedence
table. Action Expressions in Value Actions
and Predicate Actions bind in the order of the operator
table:
||, &&, comparison (==, !=, <, <=, >, >=, which do not chain),
+ and -, *, / and %, and the unary - and !. Field access (.),
capture access (.$), indexing ([i]) and function calls bind tighter than
the operators. A [
written directly after a name, a capture reference, ) or ] is an index
bracket; elsewhere it begins a character class. Each Operators Rule defines
precedence by the order of its level declarations; associativity and operator
order come from entries within each level. The Operators Rule represents
adjacency with an empty infix operator and gives postfix modifiers tighter
binding than capture,
lookahead and discard. Ordered choice remains the weakest operation.