// PEG grammar for the .inkt textual format.
// Matches the output of `write_inkt` exactly.
WHITESPACE = _{ " " | "\t" | NEWLINE }
story = { SOI ~ "(" ~ "story" ~ story_checksum? ~ name_table? ~ globals? ~ lists? ~ list_items? ~ externals? ~ addresses? ~ address_paths? ~ list_literals? ~ container* ~ ")" ~ EOI }
story_checksum = { "checksum=" ~ hex_literal }
// ── Name table ──────────────────────────────────────────────────────────────
name_table = { "(" ~ "name_table" ~ name_entry* ~ ")" }
name_entry = { integer ~ string }
// ── Globals ─────────────────────────────────────────────────────────────────
globals = { "(" ~ "globals" ~ global_entry* ~ ")" }
global_entry = { "(" ~ "global" ~ def_id ~ ":" ~ type_name ~ value ~ mutable_flag? ~ "(" ~ "name" ~ integer ~ ")" ~ ")" }
mutable_flag = { "mutable" }
type_name = { "int" | "float" | "bool" | "string" | "list" | "divert_target" | "var_pointer" | "null" }
// ── Lists ───────────────────────────────────────────────────────────────────
lists = { "(" ~ "lists" ~ list_entry* ~ ")" }
list_entry = { "(" ~ "list" ~ def_id ~ "(" ~ "name" ~ integer ~ ")" ~ list_item_inline* ~ ")" }
list_item_inline = { "(" ~ "item" ~ "name=" ~ integer ~ "ordinal=" ~ integer ~ ")" }
// ── List items ──────────────────────────────────────────────────────────────
list_items = { "(" ~ "list_items" ~ list_item_entry* ~ ")" }
list_item_entry = { "(" ~ "list_item" ~ def_id ~ "(" ~ "origin" ~ def_id ~ ")" ~ "(" ~ "ordinal" ~ integer ~ ")" ~ ("(" ~ "name" ~ integer ~ ")")? ~ ")" }
// ── Externals ───────────────────────────────────────────────────────────────
externals = { "(" ~ "externals" ~ extern_entry* ~ ")" }
extern_entry = { "(" ~ "extern" ~ def_id ~ "argc=" ~ integer ~ "(" ~ "name" ~ integer ~ ")" ~ fallback? ~ ")" }
fallback = { "(" ~ "fallback" ~ def_id ~ ")" }
// ── Addresses ───────────────────────────────────────────────────────────────
addresses = { "(" ~ "addresses" ~ address_entry* ~ ")" }
address_entry = { "(" ~ "address" ~ def_id ~ "->" ~ def_id ~ "+" ~ integer ~ ")" }
// ── Address paths ─────────────────────────────────────────────────────────────
address_paths = { "(" ~ "address_paths" ~ address_path_entry* ~ ")" }
address_path_entry = { "(" ~ "path" ~ integer ~ "->" ~ def_id ~ ")" }
// ── List literals ────────────────────────────────────────────────────────────
list_literals = { "(" ~ "list_literals" ~ list_literal_entry* ~ ")" }
list_literal_entry = { "(" ~ "list" ~ list_value_items ~ list_value_origins ~ ")" }
// ── Containers ──────────────────────────────────────────────────────────────
container = { "(" ~ "container" ~ def_id ~ hash_field? ~ scope_field? ~ container_name_field? ~ flags_field? ~ path_hash_field? ~ params_field? ~ lines_field? ~ code_field? ~ ")" }
scope_field = { "(" ~ "scope" ~ def_id ~ ")" }
container_name_field = { "(" ~ "name" ~ integer ~ ")" }
path_hash_field = { "(" ~ "path_hash" ~ integer ~ ")" }
params_field = { "(" ~ "params" ~ integer ~ ")" }
hash_field = { "(" ~ "hash" ~ hex_literal ~ ")" }
flags_field = { "(" ~ "flags" ~ flag_name+ ~ ")" }
flag_name = { "visits" | "turns" | "start_only" }
lines_field = { "(" ~ "lines" ~ line_entry* ~ ")" }
line_entry = { integer ~ line_content ~ source_hash ~ audio_field? ~ slots_field? ~ source_field? }
audio_field = { "(" ~ "audio" ~ string ~ ")" }
slots_field = { "(" ~ "slots" ~ slot_entry+ ~ ")" }
slot_entry = { integer ~ ":" ~ string }
source_field = { "(" ~ "source" ~ string ~ integer ~ ".." ~ integer ~ ")" }
source_hash = @{ "@" ~ ASCII_HEX_DIGIT{16} }
line_content = { string | template }
template = { "(" ~ "template" ~ template_part+ ~ ")" }
template_part = { literal_part | slot_part | select_part }
literal_part = { "(" ~ "lit" ~ string ~ ")" }
slot_part = { "(" ~ "slot" ~ integer ~ ")" }
select_part = { "(" ~ "select" ~ "slot=" ~ integer ~ select_variant* ~ select_default ~ ")" }
select_variant = { "(" ~ select_key ~ string ~ ")" }
select_default = { "(" ~ "default" ~ string ~ ")" }
select_key = { cardinal_key | ordinal_key | exact_key | keyword_key }
cardinal_key = { "cardinal:" ~ plural_cat }
ordinal_key = { "ordinal:" ~ plural_cat }
exact_key = { "=" ~ integer }
keyword_key = { "keyword:" ~ ident }
plural_cat = { "Zero" | "One" | "Two" | "Few" | "Many" | "Other" }
// ── Code ────────────────────────────────────────────────────────────────────
code_field = { "(" ~ "code" ~ instruction* ~ ")" }
instruction = { opcode_mnemonic ~ operand* }
opcode_mnemonic = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* }
operand = { choice_flags | kv_operand | def_id | float | integer | source_loc | seq_kind | bool_lit }
kv_operand = @{ ASCII_ALPHA+ ~ "=" ~ (ASCII_ALPHANUMERIC | "_" | "-")+ }
seq_kind = { "cycle" | "stopping" | "once_only" | "shuffle" }
bool_lit = { "true" | "false" }
choice_flags = @{ ("cond" | "start" | "choice_only" | "once" | "invis_default" | "none") ~ ("+" ~ ("cond" | "start" | "choice_only" | "once" | "invis_default"))* }
source_loc = @{ ASCII_DIGIT+ ~ ":" ~ ASCII_DIGIT+ }
// ── Values ──────────────────────────────────────────────────────────────────
value = { list_value | var_pointer_value | fragment_ref_value | string | float | integer | def_id | null_value | bool_value }
var_pointer_value = { "(" ~ "var_pointer" ~ def_id ~ ")" }
fragment_ref_value = { "(" ~ "fragment_ref" ~ integer ~ ")" }
list_value = { "(" ~ "list" ~ list_value_items ~ list_value_origins ~ ")" }
list_value_items = { "(" ~ "items" ~ def_id* ~ ")" }
list_value_origins = { "(" ~ "origins" ~ def_id* ~ ")" }
null_value = { "null" }
bool_value = { "true" | "false" }
// ── Primitives ──────────────────────────────────────────────────────────────
def_id = @{ "$" ~ ASCII_HEX_DIGIT{2} ~ "_" ~ ASCII_HEX_DIGIT+ }
hex_literal = @{ "0x" ~ ASCII_HEX_DIGIT+ }
string = @{ "\"" ~ (escape | (!("\"" | "\\") ~ ANY))* ~ "\"" }
escape = { "\\\\" | "\\\"" | "\\n" | "\\t" | "\\r" }
integer = @{ "-"? ~ ASCII_DIGIT+ }
float = @{ "-"? ~ ASCII_DIGIT+ ~ "." ~ ASCII_DIGIT* }
ident = @{ (ASCII_ALPHANUMERIC | "_")+ }