Appendix A — Grammar summary
The whole surface grammar of wolf, in one place. It is not written here: it is extracted from the specification’s grammar document and copied into this book by CI, so a production on this page and a production the parser implements cannot disagree. Where this appendix and a chapter differ about what parses, this appendix is right and the chapter has a bug.
Terminals are quoted. IDENT, INT, FLOAT, STRING and the
character classes they are built from come first; the item, type,
statement and expression grammars follow. TERM is a statement
terminator: a newline, or a semicolon where one line holds two
statements.
IDENT ::= ('_' XID_Continue+) | (XID_Start XID_Continue*)
INT ::= DEC_LIT | '0x' HEX_DIGIT ('_' | HEX_DIGIT)* | '0o' OCT_DIGIT ('_' | OCT_DIGIT)* | '0b' BIN_DIGIT ('_' | BIN_DIGIT)*
DEC_LIT ::= DIGIT ('_' | DIGIT)*
FLOAT ::= DEC_LIT '.' DEC_LIT EXPONENT? | DEC_LIT EXPONENT
EXPONENT ::= ('e' | 'E') ('+' | '-')? DEC_LIT
STRING ::= '"' STR_PART* '"'
STR_PART ::= STR_TEXT | '{{' | '}}' | INTERP
INTERP ::= '{' expr FORMAT_SPEC? '}'
FORMAT_SPEC ::= ':' /* fill/align/sign/width/precision/type, spec §7.4 */
unit ::= inner_doc* item*
item ::= attribute* visibility? bare_item
bare_item ::= fn_item | let_item | var_item | type_item | trait_item
| impl_item | use_item | import_c_item | const_item
visibility ::= 'pub' ('(' 'pkg' ')')?
use_item ::= 'use' path ( '.' '{' IDENT (',' IDENT)* ','? '}' )? ('as' IDENT)? TERM
import_c_item ::= 'import' 'c' STRING TERM
path ::= IDENT ('.' IDENT)*
fn_item ::= fn_qual* 'fn' IDENT generics? '(' params? ')' fn_ret? (block | TERM)
fn_qual ::= 'comptime' | 'extern' STRING | 'export'
generics ::= '[' generic_param (',' generic_param)* ','? ']'
generic_param ::= IDENT (':' bound)? | IDENT ':' 'type'
bound ::= path ('+' path)*
params ::= param (',' param)* ','?
param ::= param_mode? IDENT ':' type | param_mode? 'self' view_set?
param_mode ::= 'mut' | 'take'
view_set ::= '.' '{' IDENT (',' IDENT)* '}'
fn_ret ::= '->' ret_type
ret_type ::= type ('!' error_row)? /* `-> !T` parses via type's '!' type */
let_item ::= 'let' pattern (':' type)? '=' expr TERM
var_item ::= 'var' pattern (':' type)? '=' expr TERM
const_item ::= 'const' IDENT (':' type)? '=' expr TERM
type_item ::= 'type' IDENT generics? '=' type_def TERM?
type_def ::= struct_def | enum_def | type /* `distinct T` via prefix_type_kw */
struct_def ::= 'struct' '{' field* '}'
field ::= attribute* visibility? IDENT ':' type ','?
enum_def ::= 'enum' '{' variant (',' variant)* ','? '}'
variant ::= IDENT ('(' type (',' type)* ')')?
struct_item ::= 'struct' IDENT generics? '{' field* '}'
enum_item ::= 'enum' IDENT generics? '{' variant (',' variant)* ','? '}'
trait_item ::= 'trait' IDENT generics? '{' trait_member* '}'
trait_member ::= fn_item | type_item | const_item
impl_item ::= 'impl' generics? type ('for' type)? '{' impl_member* '}'
impl_member ::= fn_item | type_item | const_item
attribute ::= '#[' attr (',' attr)* ']'
attr ::= path attr_input?
attr_input ::= '(' attr_arg (',' attr_arg)* ')' | '=' literal
attr_arg ::= attr | literal /* `key = "v"` is attr with '=' input */
block ::= '{' stmt* expr? '}'
stmt ::= attribute* stmt_base
stmt_base ::= let_item | var_item | const_item | assign_stmt | defer_stmt
| expr_stmt | item
assign_stmt ::= place assign_op expr TERM
assign_op ::= '=' | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>='
defer_stmt ::= ('defer' | 'errdefer') expr TERM
expr_stmt ::= expr TERM
place ::= expr /* must be a place-expression; checked in sema, not grammar */
/* [gram.expr.prec] — the authoritative climb, tightest first,
rendered from the §3.2 table by `cargo xtask spec-extract`.
Tiers 1/2/14/15 are spelled in [gram.expr.primary]: primary,
postfix_expr, range_expr, else_expr. Comparison operators
(tier 11) do not chain. */
prefix_operand ::= prefix_op prefix_operand | postfix_expr /* tier 3, prefix */
prefix_op ::= '!' | '-' | '&' | '&mut' | '*' | 'move' | 'copy' | 'shared'
cast_expr ::= prefix_operand ('as' type)* /* tier 4, left */
mul_expr ::= cast_expr (('*' | '/' | '%') cast_expr)* /* tier 5, left */
add_expr ::= mul_expr (('+' | '-') mul_expr)* /* tier 6, left */
shift_expr ::= add_expr (('<<' | '>>') add_expr)* /* tier 7, left */
bitand_expr ::= shift_expr ('&' shift_expr)* /* tier 8, left */
bitxor_expr ::= bitand_expr ('^' bitand_expr)* /* tier 9, left */
bitor_expr ::= bitxor_expr ('|' bitxor_expr)* /* tier 10, left */
cmp_expr ::= bitor_expr (cmp_op bitor_expr)? /* tier 11, none — no chaining */
cmp_op ::= '==' | '!=' | '<' | '>' | '<=' | '>=' | '<=>'
and_expr ::= cmp_expr ('&&' cmp_expr)* /* tier 12, left */
or_expr ::= and_expr ('||' and_expr)* /* tier 13, left */
expr ::= else_expr | jump_expr
else_expr ::= range_expr ('else' (block | '|' closed_pattern '|' (expr | block) | expr))?
range_expr ::= r_end (('..' | '..=') r_end?)? | ('..' | '..=') r_end
r_end ::= or_expr | '^' or_expr
/* `^n` marks a from-end endpoint (D25): s[^1], s[^13..], s[..^1]. */
/* tiers 3–13 (or_expr ↓ prefix_operand) are rendered into the extracted
grammar from the §3.2 climb table by `cargo xtask spec-extract`. */
postfix_expr ::= receiver (call_args | index_args | '.' member | '?')*
receiver ::= primary | '(' param_mode expr ')'
/* the moded form is receiver position only: '.' member must follow. */
call_args ::= '(' (call_arg (',' call_arg)* ','?)? ')'
call_arg ::= ('mut' | 'take')? expr
index_args ::= '[' (index_arg (',' index_arg)* ','?)? ']'
index_arg ::= call_arg | prefix_type_kw type | 'region'
member ::= IDENT | INT | reserved_kw
/* tuple access: pair.0, pair.1. Member position is keyword-transparent:
nothing competes with a member name after `.`, so `.take(n)` and
`s.spawn(…)` parse — member names live in their own namespace. */
primary ::= literal | path | struct_lit | '(' expr (',' expr)* ','? ')' | block
| if_expr | match_expr | loop_expr | closure | region_expr
| scope_expr | select_expr | when_expr | unsafe_expr | spawn_expr
| asm_expr | borrow_expr
struct_lit ::= path '{' (field_init (',' field_init)* ','?)? '}'
field_init ::= IDENT ':' expr | IDENT
literal ::= INT | FLOAT | STRING | MULTILINE_STRING | RAW_STRING
| GENERALIZED_STRING | 'true' | 'false'
if_expr ::= 'if' expr block ('else' (if_expr | block))?
match_expr ::= 'match' expr '{' arm* '}'
match_arm ::= pattern ('if' expr)? '=>' (expr | block)
arm ::= match_arm arm_sep?
arm_sep ::= ',' | TERM
loop_expr ::= 'for' pattern 'in' expr block
| 'while' expr block
| 'loop' block
jump_expr ::= 'return' expr? | 'break' expr? | 'continue'
closure ::= 'fn' '(' params_untyped? ')' (block | expr)
params_untyped ::= closure_param (',' closure_param)* ','?
closure_param ::= param_mode? IDENT (':' type)?
region_expr ::= 'region' IDENT? (':' region_strategy)? block /* sugar */
| 'region' '(' region_strategy? ')' /* value */
| 'in' expr block /* into r */
| 'freeze' prefix_operand
region_strategy ::= 'rc' | 'pool' '(' type ')'
scope_expr ::= 'scope' IDENT? block
spawn_expr ::= 'spawn' 'proc' path call_args
select_expr ::= 'select' '{' select_arm (arm_sep select_arm)* arm_sep? '}'
select_arm ::= pattern 'from' expr '=>' (expr | block)
| 'timeout' '(' expr ')' '=>' (expr | block)
when_expr ::= 'when' '(' expr (',' expr)+ ','? ')' block
unsafe_expr ::= 'unsafe' block
| 'unsafe' 'c' capture_list? block /* inline C */
asm_expr ::= 'asm' '{' STRING (',' asm_operand)* ','? '}'
asm_operand ::= IDENT '=' asm_dir '(' asm_constraint ')' expr
| asm_dir '(' asm_constraint ')' expr
asm_dir ::= 'in' | 'out' | 'inout' | 'lateout'
asm_constraint ::= IDENT
capture_list ::= '[' IDENT (',' IDENT)* ','? ']'
assume_stmt ::= 'assume' 'noalias' expr (',' expr)+ TERM
borrow_expr ::= 'borrow' expr 'from' expr
type ::= path type_args?
| '!' type
| type '!' error_row /* postfix row: T ! {row}, any type position */
| prefix_type_kw type
| '*' type /* raw pointer, unsafe tier */
| 'dyn' path
| '(' type (',' type)* ','? ')'
| 'fn' '(' (type (',' type)*)? ')' ('->' ret_type)?
| 'type' /* the type of types, comptime */
| 'region' /* the type of first-class regions (X4) */
prefix_type_kw ::= 'shared' | 'handle' | 'weak' | 'distinct'
type_args ::= '[' type_arg (',' type_arg)* ','? ']'
type_arg ::= type | expr /* const generics; disambiguated in sema */
error_row ::= '{' row_entry (',' row_entry)* (',' '..')? ','? '}'
row_entry ::= path ('(' type (',' type)* ')')?
pattern ::= closed_pattern ('|' closed_pattern)*
closed_pattern ::= '_' | literal | IDENT
| path '(' pattern (',' pattern)* ','? ')'
| '(' pattern (',' pattern)* ','? ')'
| IDENT '@' closed_pattern
reserved_kw ::= 'as' | 'asm' | 'assume' | 'borrow' | 'break' | 'comptime'
| 'const' | 'continue' | 'copy' | 'defer' | 'distinct' | 'dyn' | 'else'
| 'enum' | 'errdefer' | 'export' | 'extern' | 'false' | 'fn' | 'for'
| 'freeze' | 'handle' | 'if' | 'impl' | 'import' | 'in' | 'let' | 'loop'
| 'match' | 'move' | 'mut' | 'proc' | 'pub' | 'region' | 'return'
| 'scope' | 'select' | 'shared' | 'spawn' | 'struct' | 'take' | 'trait'
| 'true' | 'type' | 'unsafe' | 'use' | 'var' | 'weak' | 'when' | 'while'