WHITESPACE = _{ " " | "\t" | "\r" | "\n" }
COMMENT = { "#" ~ (!"\n" ~ ANY)* }
program = { SOI ~ (struct_decl ~ ";"?)* ~ (top_varnode_list? ~ fn_decl+ | statement_list?) ~ EOI }
struct_decl = { "type" ~ ident ~ "{" ~ struct_field ~ ("," ~ struct_field)* ~ ","? ~ "}" }
struct_field = { ident ~ ":" ~ (struct_ptr_ty | integer) }
struct_ptr_ty = @{ ident ~ "*" }
top_varnode_list = { local_decl ~ (";" ~ local_decl)* ~ ";"* }
fn_decl = { fn_kind ~ ident ~ ":" ~ fn_body? }
fn_kind = { "fn" | "lambda" }
fn_body = { fn_stmt ~ (";" ~ fn_stmt)* ~ ";"* }
fn_stmt = { !((fn_kind ~ ident ~ ":")) ~ compound_stmt }
statement_list = { compound_stmt ~ (";" ~ compound_stmt)* ~ ";"* }
compound_stmt = { label_decl+ ~ inner_stmt? | inner_stmt }
inner_stmt = { local_decl | assignment_ssa | terminator | assert_stmt | expr }
local_decl = { ("varnode" | "local") ~ ty ~ ident ~ ("as" ~ ident)? }
assignment_ssa = { &(decl_ty? ~ ssa_name ~ eq) ~ decl_ty? ~ ssa_name ~ eq ~ expr }
decl_ty = { struct_ptr_ty | ty }
assignment_stmt = { SOI ~ assignment_ssa ~ EOI }
label = { "<" ~ (ident | integer) ~ ">" }
label_decl = { "<" ~ (ident ~ block_param_decl* | integer) ~ ">" }
block_param_decl = { block_param_name ~ (":" ~ ty)? }
label_stmt = { SOI ~ label ~ EOI }
terminator = { badinsn_stmt | return_stmt | switch_stmt | branchind_stmt | callind_stmt | cbranch_stmt | branch_stmt | tailcall_stmt | call_stmt }
terminator_stmt = { SOI ~ terminator ~ EOI }
branch_stmt = { "goto" ~ branch_label }
branch_label = { "<" ~ (ident | integer) ~ branch_arg* ~ ">" }
branch_arg = { block_param_name ~ "=" ~ typed_atom }
branchind_stmt = { "goto" ~ "[" ~ typed_atom ~ "]" ~ edge_hint? }
// An explicit successor-edge annotation for a terminator whose targets are not
// encoded in its own syntax: a direct/indirect `call`'s return (fall-through)
// block, or an indirect `goto [v]`'s resolved jump targets. Written comment-style
// (`// -> <a>, <b>`) since these edges are reconstructed CFG metadata, not part of
// the instruction. `#` remains the general comment prefix; `//` is exclusive to
// this hint.
edge_hint = { "//" ~ "->" ~ label ~ ("," ~ label)* }
cbranch_stmt = { "if" ~ typed_atom ~ "goto" ~ branch_label ~ "else" ~ "goto" ~ branch_label }
// Multi-way dispatch on an integer scrutinee: the resolved form of a jump table.
// Targets reuse `branch_label`, so each arm carries block arguments exactly as a
// `goto` does. `default` is optional — a table guarded by a preceding bounds
// check is total over the values it lists.
switch_stmt = { "switch" ~ typed_atom ~ "{" ~ (switch_arm ~ ("," ~ switch_arm)*)? ~ "}" }
switch_arm = { switch_default | switch_case }
switch_case = { integer ~ "=>" ~ branch_label }
switch_default = { "default" ~ "=>" ~ branch_label }
// `call fn name(@p0=arg, …)` is canonical (the printed form). A direct callee
// may instead be an unresolved `<minted:N>` placeholder. `call <name>` is an
// accepted arg-less shorthand kept for hand-written IR / older tests.
call_stmt = { "call" ~ (call_direct | call_legacy) ~ edge_hint? }
call_direct = { "fn" ~ callee ~ "(" ~ (call_arg ~ ("," ~ call_arg)*)? ~ ")" }
call_legacy = { label }
call_arg = { block_param_name ~ "=" ~ typed_atom }
tailcall_stmt = { "tailcall" ~ "fn" ~ callee ~ "(" ~ (typed_atom ~ ("," ~ typed_atom)*)? ~ ")" }
callind_stmt = { "call" ~ "[" ~ typed_atom ~ "]" ~ callind_args? ~ edge_hint? }
callind_args = { "(" ~ (typed_atom ~ ("," ~ typed_atom)*)? ~ ")" }
// Bytes that did not decode to a valid instruction: a terminator with no
// successors. Operand-less, so the keyword alone.
badinsn_stmt = { "badinsn" }
return_stmt = { return_at_stmt | return_value_at_stmt | return_value_stmt }
return_at_stmt = { "return" ~ "at" ~ typed_atom }
return_value_at_stmt = { "return" ~ typed_atom ~ "at" ~ typed_atom }
return_value_stmt = { "return" ~ typed_atom }
assert_stmt = { "assert" ~ typed_atom }
expr = { memory | cast | extract | gep | unop | func_unop | intrinsic_call | apply | scan | map | func_call | range | binary | tuple | atom_expr }
expr_stmt = { SOI ~ expr ~ EOI }
atom_expr = { typed_atom }
unop = { unop_op ~ typed_atom }
func_unop = { func_unop_op ~ "(" ~ typed_atom ~ ")" }
func_call = { func_ident ~ "(" ~ typed_atom ~ ("," ~ typed_atom)? ~ ")" }
intrinsic_call = { intrinsic_name ~ "(" ~ typed_atom ~ ("," ~ typed_atom)* ~ ")" }
intrinsic_name = @{ "$" ~ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* }
apply = { "apply" ~ callee ~ "(" ~ (typed_atom ~ ("," ~ typed_atom)*)? ~ ")" }
// `body <$> src` / `(body c0 c1) <$> src` (Haskell `fmap`): a `map` over an
// array. The body is a named function or `<minted:N>` placeholder; the optional
// parenthesized list supplies loop-invariant captures.
map = { map_app ~ "<$>" ~ typed_atom }
map_app = { "(" ~ callee ~ typed_atom* ~ ")" | callee }
// `scanl @body init src` / `scanl (@body c0 c1) init src`: a left-scan over the
// array `src`. A named body is printed with a leading `@`; a minted body uses
// `<minted:N>` directly. The optional parenthesized list supplies loop-invariant
// captures; `init` is the initial accumulator, `src` the scanned array.
scan = { "scanl" ~ scan_app ~ typed_atom ~ typed_atom }
scan_app = { "(" ~ scan_callee ~ typed_atom* ~ ")" | scan_callee }
scan_callee = _{ scan_body | minted_callee }
scan_body = @{ "@" ~ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* }
binary = { typed_atom ~ op ~ typed_atom }
// `v[start:end]` — extract bytes `[start, end)` of `v`. `start` defaults to 0
// and `end` defaults to `v`'s byte width, so `v[:4]`, `v[1:]` and `v[:]` are all
// valid. Lowers to a `Range` instruction (size = end - start).
range = { typed_atom ~ "[" ~ range_start? ~ ":" ~ range_end? ~ "]" }
range_start = { integer }
range_end = { integer }
memory = { load | store }
// Memory accesses name their space and byte size explicitly via `mem_loc`
// (`space:bytes`): `load(ram:4, ptr)` reads, `store(ram:4, ptr <- value)` writes.
load = { "load" ~ "(" ~ mem_loc ~ "," ~ typed_atom ~ ")" }
store = { "store" ~ "(" ~ mem_loc ~ "," ~ typed_atom ~ "<-" ~ typed_atom ~ ")" }
mem_loc = { mem_space ~ ":" ~ integer }
mem_space = @{ temp_space | ident }
temp_space = { "$temp" ~ dec_int }
cast = { cast_op ~ "(" ~ ty ~ "," ~ typed_atom ~ ")" }
extract = { "extract" ~ "(" ~ (named_extract | indexed_extract) ~ ")" }
named_extract = { typed_atom ~ "." ~ ident }
indexed_extract = { typed_atom ~ "," ~ integer }
gep = { "gep" ~ "(" ~ (named_gep | offset_gep) ~ ")" }
named_gep = { typed_atom ~ "." ~ ident }
offset_gep = { typed_atom ~ "," ~ integer }
tuple = { pack_tuple | positional_tuple }
pack_tuple = { "pack" ~ "(" ~ tuple_field ~ ("," ~ tuple_field)* ~ ")" }
tuple_field = { ident ~ "=" ~ typed_atom }
positional_tuple = { "(" ~ typed_atom ~ ("," ~ typed_atom)* ~ ")" }
cast_op = { "zext" | "sext" | "int2float" | "float2float" | "trunc" }
typed_atom = { ty? ~ atom }
atom = { capture | block_param_name | ssa_name | addressof | bool_lit | ident | integer }
bool_lit = @{ ("true" | "false") ~ !(ASCII_ALPHANUMERIC | "_") }
addressof = { "&" ~ ident }
capture = { "{" ~ ident ~ "}" }
block_param_name = @{ "@" ~ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* }
ssa_name = @{ "%" ~ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* }
ident = @{ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* }
callee = _{ minted_callee | ident }
minted_callee = @{ "<minted:" ~ dec_int ~ ">" }
integer = @{ hex_int | dec_int }
hex_int = { "0" ~ ("x" | "X") ~ ASCII_HEX_DIGIT+ }
dec_int = { ASCII_DIGIT+ }
ty = @{ "bool" | ("i" | "f") ~ ASCII_DIGIT+ }
op = { "s>>" | "s<=" | "s>=" | "s<" | "s>" | "s/" | "s%" | "f<=" | "f>=" | "f==" | "f!=" | "f<" | "f>" | "f+" | "f-" | "f*" | "f/" | "<<" | ">>" | "<=" | ">=" | "==" | "!=" | "+" | "-" | "*" | "/" | "%" | "&" | "|" | "^" | "<" | ">" }
unop_op = { "~" | "-" | "f-" }
func_unop_op = { "abs" | "sqrt" | "floor" | "ceil" | "round" }
func_ident = { "nan" | "popcount" | "lzcount" | "carry" | "scarry" | "sborrow" }
eq = { "=" ~ !"=" }