Skip to content

Grammar (EBNF)

Generated from grammar/sema.ebnf, the single source of truth kept in sync with the spec by grammar/check-drift.sh.

file = { statement } ;
statement = import_stmt | ported_import | native_import | def | ported_def
| agent_decl | circuit_decl | operator_decl | bridge_decl
| template_decl | context_decl | args_decl | config_decl
| container_decl | component_decl | provide_decl | collector_decl
| worker_decl | struct | enum_decl | trait_decl | impl_decl
| model_decl | policy_decl | policy_attach | service_decl
| monitor_decl | protocol_decl | supervise | event_decl
| subscriber_decl | sem_decl | assure_decl | test_decl
| equation_decl | equation_stmt | theorem_decl | solve_stmt
| if_stmt | while_stmt | loop_until_stmt | for_stmt
| match_stmt | with_stmt | expect_stmt | scope_block
| simple_stmt ;
import_stmt = [ "pub"? ] ( "import" qualified_name [ "as" IDENT ]
| "from" qualified_name "import" IDENT [ "as" IDENT ]
{ "," IDENT [ "as" IDENT ] } ) NEWLINE ;
struct = [ "pub"? ] "struct" IDENT [ type_params ] [ "(" trait_list ")" ] ":" NEWLINE INDENT
{ field_decl | contract_clause | sem_decl | def | const_bind } DEDENT ;
enum_decl = [ "pub"? ] "enum" IDENT [ type_params ] [ "(" trait_list ")" ] ":" NEWLINE INDENT
{ variant_decl | sem_decl | def } DEDENT
| "enum" IDENT ":" IDENT { "|" IDENT } ; (* inline sugar *)
type_params = "[" type_param { "," type_param } "]" ; (* generics, erased §5.29 *)
type_param = IDENT [ ":" IDENT { "+" IDENT } ] ; (* optional trait bounds §3.9 *)
variant_decl = IDENT [ "(" params ")" ] NEWLINE ;
trait_decl = [ "pub"? ] "trait" IDENT [ "(" trait_list ")" ] ":" NEWLINE INDENT
{ def_sig | def | law_clause | sem_decl | "sem"? STRING NEWLINE } DEDENT ;
(* "(" trait_list ")" = supertraits; `def_sig` = required method,
`def` (with a block) = default (provided) method, §3.9 *)
law_clause = "law" IDENT ":" expr NEWLINE ;
impl_decl = "impl" IDENT "for" type ":" NEWLINE INDENT { def } DEDENT ;
trait_list = IDENT { "," IDENT } ;
const_bind = IDENT "=" expr NEWLINE ;
field_decl = IDENT ":" type [ "=" expr ] [ "sem" STRING ] [ "where" expr ]
[ "coerce" "by" qualified_name ] [ "named" STRING ] NEWLINE ;
(* Clause ORDER is fixed — each clause is accepted only in
this position, so `x: int sem "d" = 3` is a parse error
while `x: int = 3 sem "d" where value > 0 coerce by f
named "q"` parses. *)
def = { decorator } [ "pub"? ] [ "simulate" ] [ "stream"? ] [ "mut"? ]
"def" IDENT [ type_params ] "(" [ params ] ")" [ "->" type ] [ "!" effect_row ]
[ "by" expr ] ":" block ;
agent_decl = { decorator } [ "pub"? ] "agent" IDENT [ type_params ]
"(" [ params ] ")" [ "->" type ] [ "!" effect_row ]
[ "by" expr ] ":" block ;
circuit_decl = { decorator } [ "pub"? ] "circuit" IDENT [ type_params ]
"(" [ params ] ")" [ "->" type ] [ "!" effect_row ]
":" block ;
params = param { "," param } ;
param = [ "*" | "**" ] IDENT [ ":" type ] [ "=" expr ] ; (* *args / **kwargs §5.29 *)
(* `pub` is REJECTED on `operator` and on `theorem`: neither binds an
importable name, so a visibility marker would mean nothing (§5.18
Granularity). Every other declaration kind below accepts it. *)
operator_decl = { decorator } [ "simulate" ] "operator" operator_head
"(" [ params ] ")" [ "->" type ] [ "!" effect_row ]
[ "by" expr ] ":" block ;
operator_head = operator_token
| "infix" STRING "precedence" precedence_class ;
operator_token = "+" | "-" | "*" | "/" | "//" | "%" | "&" | "|" | "^" | "<<"
| ">>" | "==" | "~=" ;
precedence_class= "additive" | "multiplicative" | "comparison" | "logical" ;
(* Parsed as a bare IDENT and DISCARDED: any word is accepted
and none of them changes precedence yet. *)
block = simple_line { ";" simple_line } NEWLINE (* inline suite *)
| NEWLINE INDENT { contract_clause | statement } DEDENT ;
contract_clause = ( "require" | "ensure" | "invariant" ) expr NEWLINE
| "check" expr NEWLINE
| "sem"? STRING NEWLINE
| "budget" kwargs NEWLINE
| "repair" kwargs NEWLINE (* simulate def bodies only, §5.22 *)
| "use" ( "template" call_expr | "context" call_expr
| "protocol" qualified_name
| "tools" "[" [ expr { "," expr } ] "]" ) NEWLINE ;
if_stmt = "if" expr ":" block { "elif" expr ":" block }
[ "else" ":" block ] ;
while_stmt = "while" expr ":" block ;
loop_until_stmt = "loop" "until" expr [ "max_iters" expr ] ":" block ;
for_stmt = "for" pattern "in" expr ":" block ;
simple_stmt = simple_line NEWLINE ;
simple_line = assign_stmt | return_stmt | yield_stmt | emit_stmt
| breakpoint_stmt | "pass" | "break" | "continue" | expr ;
assign_stmt = "mut" IDENT [ ":" type ] "=" expr
| assign_target [ ":" type ] "=" expr ;
assign_target = IDENT | tuple_pattern | struct_pattern | enum_pattern
| mutation_target ;
mutation_target = atom { call_suffix | "." IDENT | subscript_suffix }
( "." IDENT | subscript_suffix ) ;
return_stmt = "return" [ expr ] ;
effect_row = "{" [ effect { "," effect } ] "}" ;
(* "*" is the all-effects wildcard (top); §3.6. An OMITTED row
is inferred/fail-closed, not a wildcard, and is required
explicitly at `assure silver`+. *)
effect = "*"
| ("model" | "fs" | "net" | "proc" | "code" | "memory" | "db"
| "env" | "config" | "observe" | "event" | "policy" | "package"
| "ui" | "human" | "agent") "." IDENT [ "(" [ args ] ")" ]
| "clock" | "random" | "ffi.call" ;
match_stmt = "match" expr ":" NEWLINE INDENT { case_clause } DEDENT ;
case_clause = "case" pattern [ "if" expr ] ":" block ;
pattern = or_pattern ;
or_pattern = base_pattern { "|" base_pattern } ;
base_pattern = "_" | literal_pattern | regex_pattern | struct_pattern
| enum_pattern | tuple_pattern | bind_pattern ;
literal_pattern = literal ;
struct_pattern = qualified_name "(" [ IDENT "=" pattern
{ "," IDENT "=" pattern } ] ")" ;
enum_pattern = qualified_name [ "(" [ pattern { "," pattern } ] ")" ] ;
tuple_pattern = "(" pattern { "," pattern } [ "," ] ")" ;
bind_pattern = IDENT ;
regex_pattern = "re" STRING ;
template_expr = ( "f" | "rf" | "fr" | "sql" ) STRING ; (* rf/fr = raw template, D126 *)
validate_expr = "validate" expr ":" block ;
with_stmt = "with" ( "policy" "(" expr ")"
| qualified_name "=" expr
| expr [ "as" IDENT ] ) ":" block ;
expect_stmt = "expect" ( semantics_pred | IDENT "=" expr | expr ) ":" block
{ "except" IDENT [ "as" IDENT ] ":" block } ;
comprehension = "[" expr "for" pattern "in" expr [ "if" expr ] "]"
| "{" expr ":" expr "for" pattern "in" expr [ "if" expr ] "}"
| "{" expr "for" pattern "in" expr [ "if" expr ] "}" ;
list_comp = "[" expr "for" pattern "in" expr [ "if" expr ] "]" ;
template_decl = [ "pub"? ] "template" IDENT "(" [ params ] ")" "->" prompt_type
":" template_block ;
template_block = NEWLINE INDENT { template_item | contract_clause | statement } DEDENT ;
template_item = "role" role_name ":" template_block | "text" text_expr NEWLINE
| "use" "template" call_expr NEWLINE ;
text_expr = STRING | "f" STRING ;
role_name = "system" | "developer" | "user" | "assistant" | "tool" | "data"
| IDENT ;
prompt_type = "Prompt" "[" type "]" ;
context_decl = [ "pub"? ] "context" IDENT ":" NEWLINE INDENT
{ context_item } DEDENT ;
context_item = "model" expr NEWLINE
| "state" IDENT { "|" IDENT } NEWLINE
| "slot" IDENT "role" role_name [ "retention" expr ]
[ "budget" kwargs ] "=" expr NEWLINE
| "transition" IDENT "->" IDENT "on" call_sig ":"
context_block ;
context_block = NEWLINE INDENT { context_action | contract_clause | statement } DEDENT ;
context_action = ( "replace" | "append" | "drop" ) "slot" IDENT
[ "role" role_name ] [ "=" expr ] NEWLINE ;
sem_decl = "sem"? qualified_name "=" STRING NEWLINE ;
model_decl = [ "pub"? ] "model"? IDENT "=" "model" "(" args ")" NEWLINE ;
args_decl = [ "pub"? ] "args" IDENT ":" NEWLINE INDENT { arg_item } DEDENT ;
arg_item = IDENT ":" type "=" ( "option" | "flag" ) "(" args ")" NEWLINE ;
config_decl = [ "pub"? ] "config" IDENT ":" NEWLINE INDENT
{ config_item } DEDENT ;
config_item = "source" config_source NEWLINE
| config_field | contract_clause ;
config_field = IDENT ":" ( config_leaf | config_block ) ;
config_leaf = type [ "=" expr ] [ "sem" STRING ] [ "where" expr ] NEWLINE ;
config_block = NEWLINE INDENT { config_item } DEDENT ;
config_source = ( ( "yaml" | "json" | "toml" ) expr [ "optional" ]
| "env" "prefix" STRING
| "cli" expr ) [ "as" IDENT ] ;
container_decl = [ "pub"? ] "container" IDENT ":" NEWLINE INDENT
{ container_item } DEDENT ;
container_item = "args" IDENT NEWLINE
| "config" IDENT NEWLINE
| "bind" type [ "named" STRING ] [ "=" expr ] [ "lifetime" lifetime ] NEWLINE
| "expose" IDENT NEWLINE ;
component_decl = [ "pub"? ] "component" IDENT ":" NEWLINE INDENT
{ component_item } DEDENT ;
component_item = "lifetime" lifetime NEWLINE
| "inject" ":" NEWLINE INDENT { inject_field } DEDENT
| sem_decl | def ;
inject_field = IDENT ":" type [ "named" STRING ] [ "=" expr ] NEWLINE ;
provide_decl = [ "pub"? ] "provide" IDENT "(" [ params ] ")" "->" type
[ "lifetime" lifetime ] ":" block ;
lifetime = "transient" | "singleton" | "scoped" "(" IDENT ")" ;
(* Parsed and DISCARDED wherever it appears as a trailing
clause: any word parses, none is enforced yet. *)
inject_expr = "inject" type [ "named" STRING ] ;
collector_decl = [ "pub"? ] "collector" IDENT ":" NEWLINE INDENT
{ collector_item } DEDENT ;
collector_item = IDENT ":" type [ "mode" collector_mode ] [ "retention" expr ] NEWLINE
| "export" qualified_name kwargs NEWLINE
| "strict" NEWLINE ;
collector_mode = "series" | "histogram" | "stack" | "set" | "counts" | "bag"
| "last" ;
(* Parsed and DISCARDED: `collector_item` swallows the whole
trailing `mode`/`retention` tail, so any word parses. *)
tap_expr = expr "|>" collector_sink ;
collector_sink = qualified_name [ "(" [ args ] ")" ] ;
worker_decl = [ "pub"? ] "worker" IDENT ":" NEWLINE INDENT
{ worker_item } DEDENT ;
worker_item = "lane" IDENT NEWLINE
| "workers" ( "auto" | expr ) NEWLINE
| "batch" kwargs NEWLINE | queue_clause
| "merge" parallel_merge NEWLINE
| "on_error" parallel_error NEWLINE
| "budget" kwargs NEWLINE ;
parallel_expr = "parallel" [ "stream" ] postfix_expr parallel_op lambda_expr
[ parallel_opts ]
| "parallel" postfix_expr "reduce" postfix_expr "with" lambda_expr
[ parallel_opts ]
| "parallel" list_comp ;
parallel_op = "map" | "filter" | "find" | "any" | "all" ;
parallel_opts = [ "by" expr ] [ parallel_merge ] [ "limit" expr ]
[ "chunk" expr ] [ "on_error" parallel_error ] ;
parallel_merge = "ordered" | "unordered" | "stable" ;
parallel_error = "fail_fast" | "collect" | "skip" ;
expr = lambda_expr ;
lambda_expr = conditional_expr
| IDENT "=>" lambda_expr
| "(" [ params ] ")" "=>" lambda_expr
| "lambda" [ IDENT { "," IDENT } ] ":" lambda_expr ;
conditional_expr= tap_chain [ "if" tap_chain "else" conditional_expr ] ;
tap_chain = iff_expr { "|>" postfix_expr } ;
iff_expr = implication_expr { "⇔" implication_expr } ;
implication_expr= or_expr [ "⇒" implication_expr ] ;
or_expr = and_expr { ( "or" | "xor" | "∨" | "⊕"
| "~" "or" | "~" "xor" ) and_expr } ;
and_expr = not_expr { ( "and" | "∧" | "~" "and" ) not_expr } ;
not_expr = [ "not" | "¬" | "~" "not" | "bitnot" ] not_expr
| comparison_expr ;
comparison_expr = set_expr { comparison_tail } ;
comparison_tail = ( "==" | "!=" | "<" | "<=" | ">" | ">="
| "in" | "not" "in" | "∈" | "∉" | "⊆" | "⊂" | "⊇"
| "~<" | "~>" | "~<=" | "~>=" | "~!=" | "~" "in" ) set_expr
| "~=" set_expr [ "with" kwargs ]
| "is" [ "not" ] qualified_name ;
set_expr = bit_or_expr { ( "∪" | "∩" | "∖" | "△" ) bit_or_expr } ;
bit_or_expr = bit_xor_expr { "|" bit_xor_expr } ;
bit_xor_expr = bit_and_expr { "^" bit_and_expr } ;
bit_and_expr = shift_expr { "&" shift_expr } ;
shift_expr = additive_expr { ( "<<" | ">>" ) additive_expr } ;
additive_expr = multiplicative_expr
{ ( "+" | "-" | "~+" | "~-" ) multiplicative_expr } ;
multiplicative_expr
= power_expr { ( "*" | "/" | "//" | "%" ) power_expr } ;
power_expr = unary_expr [ "**" power_expr ] ;
unary_expr = [ "+" | "-" ] unary_expr | postfix_expr ;
postfix_expr = atom { call_suffix | "." IDENT | subscript_suffix
| semantic_index_suffix | "?" } ;
call_expr = postfix_expr ;
call_suffix = "(" [ args ] ")" ;
subscript_suffix= "[" ( expr | [ expr ] ":" [ expr ] [ ":" [ expr ] ] ) "]" ;
semantic_index_suffix = "~" "[" expr "]" ;
atom = literal | qualified_name | template_expr | list_expr
| dict_or_set_expr | tuple_or_group | comprehension
| parallel_expr | spawn_expr | validate_expr | semantics_pred
| inject_expr ;
list_expr = "[" [ list_elem { "," list_elem } [ "," ] ] "]" ;
list_elem = "..." expr | expr ;
dict_or_set_expr= "{" [ ( expr ":" expr { "," expr ":" expr }
| list_elem { "," list_elem } ) [ "," ] ] "}" ;
tuple_or_group = "(" [ expr { "," expr } [ "," ] ] ")" ;
semantics_pred = "semantics" "(" STRING { "," expr } [ "," kwargs ] ")" ;
(* `~` is the semantic sigil: `xs ~[q]`, `a ~< b`, `a ~+ b`, `a ~in b`, `a ~and
b`, `~not a`, etc. all derive `model.invoke`. Bitwise NOT is `bitnot` (since
`~` is reserved for semantics); strict boolean xor is `xor`. Semantic
primitives are the `semantic.<verb>(subject, query, ...)` namespace
(filter/rank/map/extract/summarize/translate/choose/query/combine/correct/
unique/similar/select). A type controls its semantic representation via the
coercion protocol — methods `sem_text(self) -> str` and/or `embed(self) ->
Embedding` (§5.30); pipelines attach via `with pipeline(pre=[..], post=[..])`. *)
policy_decl = [ "pub"? ] "policy" IDENT ":" NEWLINE INDENT
{ policy_rule } DEDENT ;
policy_attach = "policy" "attach" IDENT NEWLINE ; (* module-level attachment, §5.8 *)
policy_rule = policy_single | policy_group | example_single | example_group
| "budget" IDENT comparator expr NEWLINE
| "justification" STRING NEWLINE ;
policy_single = "allow" effect_list [ "except" except_list ] [ "where" expr ] NEWLINE
| "forbid" [ "cap" ] effect_list [ "except" except_list ]
[ "where" expr ] NEWLINE ;
policy_group = "allow" ":" NEWLINE INDENT
{ effect_list [ "except" except_list ] [ "where" expr ] NEWLINE } DEDENT
| "forbid" [ "cap" ] ":" NEWLINE INDENT
{ effect_list [ "except" except_list ] [ "where" expr ] NEWLINE } DEDENT ;
effect_list = effect { "," effect } ;
except_list = ( effect | STRING ) { "," ( effect | STRING ) } ;
comparator = "<=" | "<" | "==" ;
example_single = "example" ( "allow" | "deny" ) ":" expr NEWLINE ;
example_group = "examples" ":" NEWLINE INDENT { example_case } DEDENT ;
example_case = ( "allow" | "deny" ) ":" NEWLINE INDENT { expr NEWLINE } DEDENT ;
monitor_decl = [ "pub"? ] "monitor"? IDENT "on" qualified_name ":"
NEWLINE INDENT
"capture" expr_list NEWLINE
"baseline" ( "from" IDENT | STRING ) NEWLINE
"test" expr NEWLINE
{ "on" IDENT ":" block } DEDENT ;
supervise = [ "pub"? ] "supervise"? IDENT ":" NEWLINE INDENT
[ "restart" kwargs NEWLINE ] [ "fallback" expr NEWLINE ]
[ "heal" kwargs ":" heal_block ] { statement } DEDENT ;
heal_block = NEWLINE INDENT { "require" expr NEWLINE }
[ "rollout" IDENT { "->" IDENT } NEWLINE ] DEDENT ;
event_decl = [ "pub"? ] "event"? IDENT ":" NEWLINE INDENT
{ field_decl | sem_decl | contract_clause
| "key" qualified_name NEWLINE } DEDENT ;
emit_stmt = "emit" qualified_name "(" [ args ] ")" ;
subscriber_decl = [ "pub"? ] "subscriber"? IDENT "on" qualified_name ":"
NEWLINE INDENT
[ "sem"? STRING NEWLINE ] [ "where" expr NEWLINE ]
[ queue_clause ]
"handle" IDENT [ "!" effect_row ] ":" block DEDENT ;
queue_clause = "queue" expr [ "," "on_full" "=" IDENT ] NEWLINE ;
ported_def = "ported"? "def" IDENT "(" [ params ] ")" [ "->" type ]
"from" STRING ":" ported_block ;
ported_block = NEWLINE INDENT { contract_clause
| "differential" "against" IDENT NEWLINE } DEDENT ;
ported_import = "ported"? "import" STRING "as" IDENT NEWLINE ;
native_import = "native"? "import" ( qualified_name | STRING ) [ "as" IDENT ] NEWLINE ;
bridge_decl = [ "pub"? ] "bridge" bridge_mode IDENT [ "from" STRING ] ":"
NEWLINE
INDENT { bridge_expose | foreign_block | bridge_meta } DEDENT ;
bridge_mode = "python.inline" | "python.isolated" | "js.component"
| "js.host" | "node.host" | "c.abi" | "cpp.abi" ;
bridge_expose = "expose" def
| "expose" ":" NEWLINE INDENT { def } DEDENT ;
foreign_block = "begin" IDENT NEWLINE FOREIGN_TEXT "end" IDENT NEWLINE ;
bridge_meta = ( "deps" STRING { [ "," ] STRING } | "link" STRING kwargs
| "checksum" STRING | "symbol" STRING
| "timeout_ms" ( NUMBER | STRING ) ) NEWLINE ;
(* One `deps` line may pin several requirements and the
separating commas are optional. `timeout_ms` is the
per-request ceiling: at most one per bridge, one base-10
integer or a quoted string holding one. *)
scope_block = "scope" ":" block ;
spawn_expr = "spawn" call_expr ;
protocol_decl = [ "pub"? ] "protocol"? IDENT ":" NEWLINE INDENT
{ proto_transition } DEDENT ;
proto_transition= IDENT ":" type "->" IDENT { "|" IDENT } NEWLINE ;
assure_decl = "assure"? ( "bronze" | "silver" | "gold" ) NEWLINE ;
test_decl = "test"? STRING ":" block ; (* verification entry point, §5.7 *)
service_decl = [ "pub"? ] "service"? IDENT "at" expr ":" NEWLINE INDENT
{ def_sig | sem_decl | "sem"? STRING NEWLINE
| "budget" kwargs NEWLINE
| "use" "protocol" qualified_name NEWLINE } DEDENT ;
yield_stmt = "yield" expr ; (* stream def bodies only, §5.25 *)
breakpoint_stmt = "breakpoint" [ "when" ( semantics_pred | expr ) ] ; (* §5.26 *)
equation_decl = [ "pub"? ] "equation" IDENT "(" [ params ] ")" [ "->" type ]
":" eq_block ;
equation_stmt = "equation" ":" eq_block ; (* bindings flow outward, §5.28 *)
theorem_decl = "theorem" IDENT "(" [ params ] ")" ":" eq_block ;
(* §5.28/D109. No "->" type: a theorem states a proposition
rather than computing a value. Declaration position only —
there is no inline `theorem:` form. *)
eq_block = NEWLINE INDENT { eq_item } DEDENT ;
eq_item = IDENT [ "(" [ params ] ")" ] ":=" math_expr NEWLINE
| "return" math_expr NEWLINE
| math_expr NEWLINE ;
(* math_expr is the §5.28 notation: quantifiers ∀/∃/∃! with `x ∈ D :` binders,
big operators Σ Π ⋃ ⋂ ∫ with _{binder} and ^{bound}, ∇/∂/d-dx/∇²/Δ,
min/max/argmin/argmax/sup/inf with binder subscripts and `s.t.` constraint
lists, ‖·‖_p, ⟨·,·⟩, |·|, set builder { x ∈ D : P }, ∪ ∩ ∖ △ ∈ ∉ ⊆ ⊂ ⊇, ¬ ∧ ∨ ⊕ ⇒ ⇔,
`^` as power, postfix `!` and `^T`, `:=` definitions, ranges a..b. ASCII
spellings (forall, exists, sum, prod, integral, grad, norm, inner, ...) are
token-equivalent. Machine-readable sub-grammar: grammar/math.ebnf. *)
(* Neurosymbolic finite-domain constraint search (§5.51). `solve`, `var` and
`constraint` are ordinary statement directives, so the domain and the
constraint are plain Sema expressions; the block is legal anywhere a
statement is. *)
solve_stmt = "solve" [ "all" ] ":" NEWLINE INDENT { solve_item } DEDENT ;
solve_item = "var" IDENT "in" expr NEWLINE
| "constraint" expr NEWLINE ;
(* String literals (§5.13, D126). Prefixes bind lowercase and immediately
adjacent to the quote; either quote character works everywhere:
STRING = [ prefix ] ( quoted | triple ) ;
prefix = "f" | "rf" | "fr" | "r" | "sql" | "re" ;
quoted = '"' { escape | CHAR } '"' | "'" { escape | CHAR } "'" ;
triple = '"""' RAW '"""' | "'''" RAW "'''" ; (* raw, multi-line *)
escape = "\" ( "n" | "t" | "r" | "\" | '"' | "'" | "0" | "a" | "b"
| "f" | "v" | "{" | "}"
| "x" HEX HEX | "u" HEX HEX HEX HEX
| "U" HEX HEX HEX HEX HEX HEX HEX HEX
| NEWLINE ) ; (* non-raw bodies only *)
Raw bodies (r/rf/fr/re) keep every backslash; `\<quote>` keeps both
characters and does not terminate. Unknown escapes are lex errors.
f/rf/fr/sql bodies carry `{expr[:format]}` interpolation holes with
`{{`/`}}` literal braces (non-raw f also accepts `\{`/`\}`). *)
(* Shared syntactic and lexical productions. *)
decorator = "@" qualified_name [ "(" [ args ] ")" ] NEWLINE ;
def_sig = { decorator } [ "pub" ] [ "stream" ] [ "mut" ]
"def" IDENT [ type_params ] "(" [ params ] ")"
[ "->" type ] [ "!" effect_row ] NEWLINE ;
args = argument { "," argument } [ "," ] ;
argument = "..." expr | IDENT "=" expr | expr ;
kwargs = IDENT "=" expr { "," IDENT "=" expr } [ "," ] ;
expr_list = expr { "," expr } [ "," ] ;
call_sig = qualified_name "(" [ params ] ")" ;
type = union_type ;
union_type = primary_type { "|" primary_type } ;
primary_type = qualified_name [ "[" type { "," type } "]" ]
| "(" [ type { "," type } [ "," ] ] ")"
| "(" [ type { "," type } ] ")" "->" type [ "!" effect_row ]
| "None" ;
qualified_name = IDENT { "." IDENT } ;
literal = NUMBER | STRING | "true" | "false" | "None" ;
(* Interpolated token bodies are lexer-delimited; the parser recursively parses
each expression and preserves the optional format suffix verbatim. *)
interpolation = "{" expr [ ":" format_spec ] "}" ;
format_spec = { FORMAT_CHAR } ;
(* INDENT/DEDENT are emitted from leading spaces after NEWLINE. Blank and
comment-only lines do not affect indentation. IDENT, NUMBER, STRING,
FORMAT_CHAR, CHAR, RAW, and HEX are lexer tokens; `#` starts a line comment. *)