wazabin-qcode-parser 0.1.1

Parser for the QCode text format
Documentation
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              =  { "=" ~ !"=" }