WHITESPACE = _{ " " | "\t" | "\r" | "\n" }
COMMENT = _{ "//" ~ (!"\n" ~ ANY)* }

program = { SOI ~ decl* ~ EOI }
decl = { cfg_attr* ~ decl_core }
decl_core = _{ import_decl | const_decl | alias_decl | port_decl | mmio_decl | embed_decl | global_decl | struct_decl | extern_decl | fn_decl }
cfg_attr = { "@cfg" ~ "(" ~ cfg_predicate ~ ")" }
cfg_predicate = { cfg_all | cfg_any | cfg_not | cfg_call | cfg_flag }
cfg_all = { "all" ~ "(" ~ cfg_predicates? ~ ")" }
cfg_any = { "any" ~ "(" ~ cfg_predicates? ~ ")" }
cfg_not = { "not" ~ "(" ~ cfg_predicate ~ ")" }
cfg_call = { ident ~ "(" ~ (string_lit | int_lit) ~ ")" }
cfg_flag = { "debug" | "release" }
cfg_predicates = { cfg_predicate ~ ("," ~ cfg_predicate)* ~ ","? }

layout_file = { SOI ~ layout_decl ~ EOI }
layout_decl = { "layout" ~ ident ~ "{" ~ layout_item* ~ "}" }
layout_item = _{ layout_load | layout_entry | layout_stack | layout_region | layout_section | layout_symbol }
layout_load = { "load" ~ int_lit ~ semi? }
layout_entry = { "entry" ~ int_lit ~ semi? }
layout_stack = { "stack" ~ int_lit ~ semi? }
layout_region = { "region" ~ ident ~ int_lit ~ ".." ~ int_lit ~ layout_flag* ~ semi? }
layout_flag = { "read" | "write" | "execute" | "volatile" | "reserved" }
layout_section = { "section" ~ section_name ~ "->" ~ ident ~ "align" ~ int_lit ~ semi? }
layout_symbol = { "symbol" ~ ident ~ "=" ~ expr ~ semi? }

visibility = { "pub" }
volatile_kw = { "volatile" }
attr = { "inline" | "naked" | "interrupt" }
fn_modifier = _{ attr | visibility }
import_decl = { "import" ~ path ~ semi? }
const_decl = { visibility? ~ "const" ~ ident ~ ":" ~ ty ~ "=" ~ expr ~ semi? }
alias_decl = { visibility? ~ "alias" ~ ident ~ "=" ~ ty ~ semi? }
port_decl = { visibility? ~ "port" ~ ident ~ ":" ~ ty ~ "=" ~ expr ~ semi? }
mmio_decl = { visibility? ~ volatile_kw? ~ "mmio" ~ ident ~ ":" ~ ty ~ "=" ~ expr ~ semi? }
embed_decl = { visibility? ~ "embed" ~ ident ~ ":" ~ "bytes" ~ "=" ~ embed_source ~ embed_opts? ~ semi? }
embed_source = { file_embed | bytes_embed | text_embed | cstr_embed | repeat_embed }
file_embed = { "file" ~ "(" ~ string_lit ~ ")" }
bytes_embed = { "bytes" ~ "[" ~ args? ~ "]" }
text_embed = { "text" ~ "(" ~ string_lit ~ ")" }
cstr_embed = { "cstr" ~ "(" ~ string_lit ~ ")" }
repeat_embed = { "repeat" ~ "(" ~ expr ~ "," ~ expr ~ ")" }
embed_opts = { ("section" ~ section_name)? ~ ("align" ~ expr)? }
section_name = @{ "."? ~ ident ~ ("." ~ ident)* }
global_decl = { visibility? ~ "global" ~ ident ~ ":" ~ ty ~ "=" ~ expr ~ semi? }
struct_decl = { visibility? ~ "struct" ~ ident ~ "{" ~ field_decl* ~ "}" }
field_decl = { ident ~ ":" ~ ty ~ semi? }
extern_decl = { visibility? ~ "extern" ~ "asm" ~ "fn" ~ ident ~ "(" ~ params? ~ ")" ~ ret_ty? ~ semi? }
fn_decl = { fn_modifier* ~ "fn" ~ ident ~ "(" ~ params? ~ ")" ~ ret_ty? ~ block }
params = { param ~ ("," ~ param)* ~ ","? }
param = { ident ~ ":" ~ ty }
ret_ty = { "->" ~ ty }

block = { "{" ~ stmt* ~ "}" }
stmt = _{
    let_stmt
  | if_stmt
  | while_stmt
  | loop_stmt
  | break_stmt
  | continue_stmt
  | return_stmt
  | asm_stmt
  | out_stmt
  | assign_stmt
  | expr_stmt
}

let_stmt = { "let" ~ ident ~ ":" ~ ty ~ "=" ~ expr ~ semi? }
assign_stmt = { place ~ assign_op ~ expr ~ semi? }
place = { deref_place | access_place | index_place | field_place | ident }
deref_place = { "*" ~ deref_operand }
deref_operand = _{ deref_call_operand | deref_access_operand | ident | "(" ~ expr ~ ")" }
deref_call_operand = { path ~ "(" ~ args? ~ ")" }
deref_access_operand = { ident ~ access_suffix+ }
access_place = { ident ~ &(access_suffix ~ access_suffix) ~ access_suffix+ }
index_place = { path ~ "[" ~ expr ~ "]" }
field_place = { ident ~ "." ~ ident ~ !("." | "(" | "[") }
if_stmt = { "if" ~ expr ~ block ~ ("else" ~ (if_stmt | block))? }
while_stmt = { "while" ~ expr ~ block }
loop_stmt = { "loop" ~ block }
break_stmt = { "break" ~ semi? }
continue_stmt = { "continue" ~ semi? }
return_stmt = { "return" ~ expr? ~ semi? }
asm_stmt = { "asm" ~ volatile_kw? ~ asm_operands? ~ "{" ~ asm_line* ~ "}" }
asm_operands = { "(" ~ asm_operand_list? ~ ")" }
asm_operand_list = { asm_operand ~ ("," ~ asm_operand)* ~ ","? }
asm_operand = { asm_input | asm_output | asm_clobber }
asm_input = { "in" ~ ident ~ ":" ~ ty ~ ("as" ~ asm_operand_class)? }
asm_output = { "out" ~ ident ~ ":" ~ ty ~ ("as" ~ asm_operand_class)? }
asm_clobber = { "clobber" ~ ident }
asm_operand_class = { "reg8" | "reg16" | "reg24" | "mem" | "imm" }
asm_line = { string_lit }
out_stmt = { "out" ~ path ~ "," ~ expr ~ semi? }
expr_stmt = { expr ~ !(WHITESPACE* ~ assign_op) ~ semi? }
assign_op = { "<<=" | ">>=" | "+=" | "-=" | "*=" | "/=" | "%=" | "&=" | "|=" | "^=" | "=" }

expr = { logical_or }
logical_or = { logical_and ~ (or_op ~ logical_and)* }
logical_and = { bit_or ~ (and_op ~ bit_or)* }
bit_or = { bit_xor ~ (bit_or_op ~ bit_xor)* }
bit_xor = { bit_and ~ (bit_xor_op ~ bit_and)* }
bit_and = { equality ~ (bit_and_op ~ equality)* }
equality = { comparison ~ (eq_op ~ comparison)* }
comparison = { shift ~ (cmp_op ~ shift)* }
shift = { additive ~ (shift_op ~ additive)* }
additive = { multiplicative ~ (add_op ~ multiplicative)* }
multiplicative = { unary ~ (mul_op ~ unary)* }
unary = { unary_op* ~ primary }
primary = { cast_expr | in_expr | addr_access_expr | addr_index_expr | addr_field_expr | addr_expr | deref_expr | struct_lit | array_lit | call_expr | access_expr | field_expr | index_expr | literal | path_expr | "(" ~ expr ~ ")" }
cast_expr = { "cast" ~ "<" ~ ty ~ ">" ~ "(" ~ expr ~ ")" }
in_expr = ${ "in" ~ (" " | "\t" | "\r" | "\n")+ ~ path }
addr_access_expr = { "&" ~ ident ~ &(access_suffix ~ access_suffix) ~ access_suffix+ }
addr_index_expr = { "&" ~ path ~ "[" ~ expr ~ "]" }
addr_field_expr = { "&" ~ ident ~ "." ~ ident ~ !("." | "(" | "[") }
addr_expr = { "&" ~ ident }
deref_expr = { "*" ~ deref_operand }
struct_lit = { path ~ "{" ~ field_inits? ~ "}" }
field_inits = { field_init ~ ("," ~ field_init)* ~ ","? }
field_init = { ident ~ ":" ~ expr }
access_expr = { ident ~ &(access_suffix ~ access_suffix) ~ access_suffix+ }
access_suffix = { field_suffix | index_suffix }
field_suffix = { "." ~ ident }
index_suffix = { "[" ~ expr ~ "]" }
index_expr = { path ~ "[" ~ expr ~ "]" }
field_expr = { ident ~ "." ~ ident ~ !("." | "(" | "[") }
array_lit = { "[" ~ args? ~ "]" }
call_expr = { path ~ "(" ~ args? ~ ")" }
args = { expr ~ ("," ~ expr)* ~ ","? }
path_expr = { path }
literal = { bool_lit | char_lit | string_lit | int_lit }

or_op = { "||" }
and_op = { "&&" }
bit_or_op = { "|" ~ !"|" }
bit_xor_op = { "^" }
bit_and_op = { "&" ~ !"&" }
eq_op = { "==" | "!=" }
cmp_op = { "<=" | ">=" | "<" | ">" }
shift_op = { "<<" | ">>" }
add_op = { "+" | "-" }
mul_op = { "*" | "/" | "%" }
unary_op = { "-" | "~" | "!" }

ty = { ptr_ty | array_ty | named_ty }
ptr_ty = { "ptr" ~ "<" ~ ty ~ ">" }
array_ty = { "[" ~ ty ~ ";" ~ expr ~ "]" }
named_ty = { path }

path = @{ ident ~ ("." ~ ident)* }
ident = @{ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* }

bool_lit = { "true" | "false" }
int_lit = @{ (hex_lit | bin_lit | dec_lit) ~ int_suffix? }
hex_lit = { "0x" ~ ASCII_HEX_DIGIT+ }
bin_lit = { "0b" ~ ("0" | "1")+ }
dec_lit = { ASCII_DIGIT+ }
int_suffix = { "u8" | "i8" | "u16" | "i16" | "u24" | "i24" }
char_lit = @{ "'" ~ (escape | !"'" ~ ANY) ~ "'" }
string_lit = @{ "\"" ~ (escape | !"\"" ~ ANY)* ~ "\"" }
escape = { "\\" ~ ("n" | "0" | "t" | "\\" | "'" | "\"") }
semi = _{ ";" }
