// ============================================================================
// AST Types
// ============================================================================

///|
pub enum Index {
  Num(UInt32)
  Id(String)
} derive(Eq, Debug)

///|
pub impl Show for Index with fn output(self, logger) {
  match self {
    Num(val) => {
      logger.write_string("Num(")
      val.output(logger)
      logger.write_char(')')
    }
    Id(val) => {
      logger.write_string("Id(")
      val.output(logger)
      logger.write_char(')')
    }
  }
}

///|
pub struct Limits {
  min : UInt32
  max : UInt32?
} derive(Eq, Debug)

///|
pub impl Show for Limits with fn output(self, logger) {
  logger.write_string("Limits {min: ")
  self.min.output(logger)
  logger.write_string(", max:")
  logger.write_string(@debug.to_string(self.max))
  logger.write_char('}')
}

///|
pub struct FuncType {
  params : Array[ValueType]
  results : Array[ValueType]
} derive(Eq, Debug)

///|
pub struct TypeUse {
  type_index : Index?
  func_type : FuncType
  param_ids : Array[String?]
} derive(Eq, Debug)

///|
pub struct Annotation {
  name : String
  args : Array[String]
} derive(Eq, Debug)

///|
pub enum InlineExport {
  Export(String)
} derive(Eq, Debug)

///|
pub enum ImportDesc {
  Func(TypeUse)
  Table(TableType)
  Memory(MemoryType)
  Global(GlobalType)
  Tag(TypeUse)
} derive(Eq, Debug)

///|
pub struct Import {
  module_name : String
  field_name : String
  desc : ImportDesc
  id : String?
  annotations : Array[Annotation]
} derive(Eq, Debug)

///|
pub enum ExportDesc {
  Func(Index)
  Table(Index)
  Memory(Index)
  Global(Index)
  Tag(Index)
} derive(Eq, Debug)

///|
pub struct Export {
  name : String
  desc : ExportDesc
} derive(Eq, Debug)

///|
pub struct TableType {
  limits : Limits
  elem_type : ValueType
} derive(Eq, Debug)

///|
pub struct MemoryType {
  min : UInt64
  max : UInt64?
  shared : Bool
  memory64 : Bool
} derive(Eq, Debug)

///|
pub fn MemoryType::new(
  min : UInt64,
  max~ : UInt64? = None,
  shared~ : Bool = false,
  memory64~ : Bool = false,
) -> MemoryType {
  { min, max, shared, memory64, }
}

///|
pub struct GlobalType {
  val_type : ValueType
  mutable_ : Bool
} derive(Eq, Debug)

///|
pub struct Local {
  id : String?
  val_type : ValueType
} derive(Eq, Debug)

///|
pub enum AtomicOrder {
  SeqCst
  AcqRel
  Relaxed
} derive(Eq, Debug)

///|
pub struct Func {
  id : String?
  annotations : Array[Annotation]
  exports : Array[InlineExport]
  type_use : TypeUse
  locals : Array[Local]
  body : Array[Instruction]
} derive(Eq, Debug)

///|
pub struct Global {
  id : String?
  exports : Array[InlineExport]
  global_type : GlobalType
  init : Array[Instruction]
} derive(Eq, Debug)

///|
pub struct Table {
  id : String?
  exports : Array[InlineExport]
  table_type : TableType
  elem_init : Array[Index]?
  elem_exprs : Array[ElemInitExpr]?
  elem_init_type : ValueType?
  elem_offset : Array[Instruction]?
} derive(Eq, Debug)

///|
pub struct Memory {
  id : String?
  exports : Array[InlineExport]
  memory_type : MemoryType
} derive(Eq, Debug)

///|
pub struct Tag {
  id : String?
  exports : Array[InlineExport]
  type_use : TypeUse
} derive(Eq, Debug)

///|
pub enum FieldStorage {
  Value(ValueType)
  I8
  I16
} derive(Eq, Debug)

///|
pub struct StructFieldDef {
  storage : FieldStorage
  mutable_ : Bool
} derive(Eq, Debug)

///|
pub enum TypeDefBody {
  Func(FuncType)
  Struct(Array[StructFieldDef])
  Array(StructFieldDef)
  Shared(TypeDefBody)
} derive(Eq, Debug)

///|
pub struct TypeDefMetadata {
  describes : Index?
  descriptor : Index?
} derive(Eq, Debug)

///|
pub struct TypeDef {
  id : String?
  is_sub : Bool
  final_ : Bool
  supers : Array[Index]
  metadata : TypeDefMetadata
  body : TypeDefBody
} derive(Eq, Debug)

///|
pub struct DataSegment {
  id : String?
  memory_index : Index
  offset : Array[Instruction]
  data : Bytes
} derive(Eq, Debug)

///|
pub struct ElemSegment {
  id : String?
  declarative : Bool
  table_index : Index
  offset : Array[Instruction]
  func_indices : Array[Index]
  elem_exprs : Array[ElemInitExpr]
  elem_type : ValueType?
} derive(Eq, Debug)

///|
pub struct ElemInitExpr {
  instrs : Array[Instruction]
  explicit_item : Bool
} derive(Eq, Debug)

///|
pub struct Start {
  func_index : Index
} derive(Eq, Debug)

///|
pub enum ModuleField {
  TypeField(TypeDef)
  RecField(Array[TypeDef])
  FuncField(Func)
  TableField(Table)
  MemoryField(Memory)
  GlobalField(Global)
  TagField(Tag)
  ImportField(Import)
  ExportField(Export)
  StartField(Start)
  ElemField(ElemSegment)
  DataField(DataSegment)
} derive(Eq, Debug)

///|
pub struct Module {
  id : String?
  fields : Array[ModuleField]
} derive(Eq, Debug)

///|
pub fn Module::new(fields : Array[ModuleField], id~ : String? = None) -> Module {
  { id, fields, }
}

// ============================================================================
// Instruction AST
// ============================================================================

///|
pub struct MemArg {
  align : UInt64
  offset : UInt64
} derive(Eq, Debug)

///|
pub struct BlockType {
  label : String?
  type_use : TypeUse?
} derive(Eq, Debug)

// ============================================================================
// instruction.mbt - Extended Instruction AST for SIMD
// ============================================================================

///|
pub struct V128Const {
  shape : SimdShape
  values : Array[String]
} derive(Eq, Debug)

///|
pub enum SimdShape {
  I8x16
  I16x8
  I32x4
  I64x2
  F32x4
  F64x2
} derive(Eq, Debug)

///|
pub impl Show for SimdShape with fn output(self, logger) {
  match self {
    I8x16 => logger.write_string("I8x16")
    I16x8 => logger.write_string("I16x8")
    I32x4 => logger.write_string("I32x4")
    I64x2 => logger.write_string("I64x2")
    F32x4 => logger.write_string("F32x4")
    F64x2 => logger.write_string("F64x2")
  }
}

///|
pub struct ShuffleLanes {
  lanes : FixedArray[UInt32] // 16 lane indices
} derive(Eq, Debug)

///|
pub impl Show for ShuffleLanes with fn output(self, logger) {
  logger.write_string("ShuffleLanes {lanes: ")
  self.lanes.output(logger)
  logger.write_string("}")
}

///|
pub enum CatchClause {
  CatchClause(Index, Index)
  CatchRefClause(Index, Index)
  CatchAllClause(Index)
  CatchAllRefClause(Index)
} derive(Eq, Debug)

///|
pub impl Show for CatchClause with fn output(self, logger) {
  match self {
    CatchClause(idx1, idx2) => {
      logger.write_string("CatchClause(")
      idx1.output(logger)
      logger.write_string(", ")
      idx2.output(logger)
      logger.write_char(')')
    }
    CatchRefClause(idx1, idx2) => {
      logger.write_string("CatchRefClause(")
      idx1.output(logger)
      logger.write_string(", ")
      idx2.output(logger)
      logger.write_char(')')
    }
    CatchAllClause(idx) => {
      logger.write_string("CatchAllClause(")
      idx.output(logger)
      logger.write_char(')')
    }
    CatchAllRefClause(idx) => {
      logger.write_string("CatchAllRefClause(")
      idx.output(logger)
      logger.write_char(')')
    }
  }
}

///|
pub enum LegacyCatchClause {
  LegacyCatch(Index, Array[Instruction])
  LegacyCatchAll(Array[Instruction])
} derive(Eq, Debug)

///|
pub enum Instruction {
  // Control
  Unreachable
  Nop
  Block(BlockType, Array[Instruction])
  Loop(BlockType, Array[Instruction])
  If(BlockType, Array[Instruction], Array[Instruction])
  Br(Index)
  BrIf(Index)
  BrOnNull(Index)
  BrOnNonNull(Index)
  BrOnCastDescEq(Index, ValueType, ValueType)
  BrOnCastDescEqFail(Index, ValueType, ValueType)
  BrTable(Array[Index], Index)
  Return
  Call(Index)
  CallIndirect(Index, TypeUse)
  ReturnCall(Index)
  ReturnCallIndirect(Index, TypeUse)
  CallRef(TypeUse)
  ReturnCallRef(TypeUse)
  Throw(Index)
  ThrowRef
  Rethrow(Index)
  TryTable(BlockType, Array[CatchClause], Array[Instruction])
  TryLegacy(BlockType, Array[Instruction], Array[LegacyCatchClause], Index?)

  // Parametric
  Drop
  Select(Array[ValueType]?)

  // Variable
  LocalGet(Index)
  LocalSet(Index)
  LocalTee(Index)
  GlobalGet(Index)
  GlobalSet(Index)

  // Table
  TableGet(Index)
  TableSet(Index)
  TableSize(Index)
  TableGrow(Index)
  TableFill(Index)
  TableCopy(Index, Index)
  TableInit(Index, Index)
  ElemDrop(Index)

  // Memory
  MemoryAtomic(MemoryAtomicOp, AtomicOrder, Index?, MemArg)
  AtomicFence(AtomicOrder)
  Load(Opcode, MemArg)
  Store(Opcode, MemArg)
  ArrayLoad(Opcode, Index, MemArg)
  ArrayStore(Opcode, Index, MemArg)
  MemorySize
  MemoryGrow
  MemoryFill
  MemoryCopy
  MemoryInit(Index)
  DataDrop(Index)

  // Numeric
  Const(Opcode, String)
  Unary(Opcode)
  Binary(Opcode)
  Test(Opcode)
  Compare(Opcode)
  Convert(Opcode)

  // Reference
  RefNull(ValueType)
  RefIsNull
  RefFunc(Index)
  StringConst(Bytes)
  StringMeasureWtf16
  StringConcat
  StringEq
  StringAsWtf16
  StringViewWtf16GetCodeunit
  StringViewWtf16Slice
  StringNewUtf8Array
  StringNewWtf16Array
  StringEncodeUtf8Array
  StringEncodeWtf16Array
  StringNewLossyUtf8Array
  StringNewWtf8Array
  StringEncodeLossyUtf8Array
  StringEncodeWtf8Array
  StructNew(Index)
  StructNewDefault(Index)
  ArrayNew(Index)
  ArrayNewDefault(Index)
  ArrayNewFixed(Index, UInt32)
  ArrayNewData(Index, Index)
  ArrayNewElem(Index, Index)
  StructGet(Index, UInt32)
  StructGetS(Index, UInt32)
  StructGetU(Index, UInt32)
  WaitqueueNew
  WaitqueueNotify
  StructWait(Index, UInt32)
  Publish
  StructAtomicSet(AtomicOrder, Index, UInt32)
  ArrayAtomicSet(AtomicOrder, Index)
  StructAtomicGet(AtomicOrder, Index, UInt32)
  StructAtomicGetS(AtomicOrder, Index, UInt32)
  StructAtomicGetU(AtomicOrder, Index, UInt32)
  StructAtomicRmw(AtomicOrder, @lib.AggregateAtomicRmwOp, Index, UInt32)
  StructAtomicCmpxchg(AtomicOrder, Index, UInt32)
  ArrayAtomicGet(AtomicOrder, Index)
  ArrayAtomicGetS(AtomicOrder, Index)
  ArrayAtomicGetU(AtomicOrder, Index)
  ArrayAtomicRmw(AtomicOrder, @lib.AggregateAtomicRmwOp, Index)
  ArrayAtomicCmpxchg(AtomicOrder, Index)
  StructNewDesc(Index)
  StructNewDefaultDesc(Index)
  RefGetDesc(Index)
  RefTestDesc(Bool, Index)
  RefCastDescEq(Bool, Index)

  // ========== SIMD Instructions ==========

  // V128 Const
  V128Const(V128Const)

  // V128 Memory
  V128Load(MemArg)
  V128Load8x8S(MemArg)
  V128Load8x8U(MemArg)
  V128Load16x4S(MemArg)
  V128Load16x4U(MemArg)
  V128Load32x2S(MemArg)
  V128Load32x2U(MemArg)
  V128Load8Splat(MemArg)
  V128Load16Splat(MemArg)
  V128Load32Splat(MemArg)
  V128Load64Splat(MemArg)
  V128Load32Zero(MemArg)
  V128Load64Zero(MemArg)
  V128Store(MemArg)
  V128Load8Lane(MemArg, UInt32) // memarg, lane
  V128Load16Lane(MemArg, UInt32)
  V128Load32Lane(MemArg, UInt32)
  V128Load64Lane(MemArg, UInt32)
  V128Store8Lane(MemArg, UInt32)
  V128Store16Lane(MemArg, UInt32)
  V128Store32Lane(MemArg, UInt32)
  V128Store64Lane(MemArg, UInt32)

  // V128 Shuffle/Swizzle
  I8x16Shuffle(ShuffleLanes)
  I8x16Swizzle

  // V128 Splat
  I8x16Splat
  I16x8Splat
  I32x4Splat
  I64x2Splat
  F32x4Splat
  F64x2Splat

  // V128 Extract/Replace Lane
  I8x16ExtractLaneS(UInt32)
  I8x16ExtractLaneU(UInt32)
  I8x16ReplaceLane(UInt32)
  I16x8ExtractLaneS(UInt32)
  I16x8ExtractLaneU(UInt32)
  I16x8ReplaceLane(UInt32)
  I32x4ExtractLane(UInt32)
  I32x4ReplaceLane(UInt32)
  I64x2ExtractLane(UInt32)
  I64x2ReplaceLane(UInt32)
  F32x4ExtractLane(UInt32)
  F32x4ReplaceLane(UInt32)
  F64x2ExtractLane(UInt32)
  F64x2ReplaceLane(UInt32)

  // V128 Unary operations (no operands beyond the v128)
  SimdUnary(Opcode)

  // V128 Binary operations
  SimdBinary(Opcode)

  // V128 Ternary operations (bitselect, relaxed laneselect, fma)
  SimdTernary(Opcode)

  // V128 Shift operations (take i32 shift amount)
  SimdShift(Opcode)

  // V128 Test operations (return i32)
  SimdTest(Opcode)

  // V128 Comparison operations (return v128 mask)
  SimdCompare(Opcode)

  // V128 Conversion operations
  SimdConvert(Opcode)

  // Relaxed SIMD
  SimdRelaxed(Opcode)
} derive(Eq, Debug)

///|
pub enum WastValue {
  I32(String)
  I64(String)
  F32(String)
  F64(String)
  RefNull(ValueType)
  RefFunc
} derive(Eq, Debug)

///|
pub enum WastResult {
  Value(WastValue)
  F32NanCanonical
  F32NanArithmetic
  F64NanCanonical
  F64NanArithmetic
  RefNull(ValueType?)
  RefFunc
} derive(Eq, Debug)

///|
pub struct WastAction {
  module_name : String?
  name : String
  args : Array[WastValue]
} derive(Eq, Debug)

///|
pub enum WastActionType {
  Invoke(WastAction)
  Get(String?, String) // module_name?, global_name
} derive(Eq, Debug)

///|
pub enum WastCommand {
  Module(Module)
  ModuleBinary(String?, Bytes)
  ModuleQuote(String?, Array[String])
  Register(String, String?) // name, module_id
  Action(WastActionType)
  AssertReturn(WastActionType, Array[WastResult])
  AssertTrap(WastActionType, String)
  AssertException(WastActionType)
  AssertExhaustion(WastActionType, String)
  AssertMalformed(WastModuleDef, String)
  AssertInvalid(WastModuleDef, String)
  AssertUnlinkable(WastModuleDef, String)
} derive(Eq, Debug)

///|
pub enum WastModuleDef {
  Inline(Module)
  Binary(Bytes)
  Quote(Array[String])
} derive(Eq, Debug)

///|
pub struct WastScript {
  commands : Array[WastCommand]
} derive(Eq, Debug)

// ============================================================================
// Parser Error Types
// ============================================================================

///|
pub struct ParseError {
  location : Location
  message : String
} derive(Eq, Debug)

///|
pub suberror ParserError {
  UnexpectedToken(Token, String)
  InvalidInstruction(String)
  Custom(String)
} derive(Eq, Debug)

///|
pub impl Show for ParserError with fn output(self, logger) {
  match self {
    UnexpectedToken(tok, msg) => {
      logger.write_string("UnexpectedToken(")
      tok.output(logger)
      logger.write_string(", ")
      msg.output(logger)
      logger.write_char(')')
    }
    InvalidInstruction(msg) => {
      logger.write_string("InvalidInstruction(")
      msg.output(logger)
      logger.write_char(')')
    }
    Custom(msg) => {
      logger.write_string("Custom(")
      msg.output(logger)
      logger.write_char(')')
    }
  }
}

// ============================================================================
// Lexer State for Rollback
// ============================================================================

///|
pub struct LexerState {
  cursor : Int32
  line : Int32
  line_start : Int32
}

// ============================================================================
// Parser Structure
// ============================================================================

///|
pub struct WastParser {
  lexer : WastLexer
  mut current : Token
  errors : Array[ParseError]
}

///|
pub fn WastParser::new(lexer : WastLexer) -> WastParser {
  let parser = {
    lexer,
    current: {
      location: { filename: "", line: 0, first_column: 0, last_column: 0, },
      token_type: Eof,
      value: None,
    },
    errors: [],
  }
  ignore(parser.advance())
  parser
}

// ============================================================================
// Parser Utilities
// ============================================================================

///|
fn WastParser::advance(self : WastParser) -> Token {
  let prev = self.current
  self.current = self.lexer.get_token()
  prev
}

///|
fn WastParser::check(self : WastParser, token_type : TokenType) -> Bool {
  match (self.current.token_type, token_type) {
    // Match token "families" so callers can check ValueType/Opcode kinds
    // without knowing the exact payload.
    (ValueType(_), ValueType(_)) => true
    (Opcode(_), Opcode(_)) => true
    (SimdShapeToken(_), SimdShapeToken(_)) => true
    _ => self.current.token_type == token_type
  }
}

///|
fn WastParser::match_token(self : WastParser, token_type : TokenType) -> Bool {
  if self.check(token_type) {
    let _ = self.advance()
    true
  } else {
    false
  }
}

///|
fn WastParser::expect(
  self : WastParser,
  token_type : TokenType,
  msg : String,
) -> Token raise ParserError {
  if self.check(token_type) {
    self.advance()
  } else {
    raise UnexpectedToken(self.current, msg)
  }
}

///|
fn WastParser::expect_lpar(self : WastParser) -> Unit raise ParserError {
  let _ = self.expect(Lpar, "expected '('")
}

///|
fn WastParser::expect_rpar(self : WastParser) -> Unit raise ParserError {
  let _ = self.expect(Rpar, "expected ')'")
}

///|
fn WastParser::get_text(_ : WastParser, token : Token) -> String {
  match token.value {
    Text(s) => s
    _ => ""
  }
}

///|
fn WastParser::get_literal_text(_ : WastParser, token : Token) -> String {
  match token.value {
    Literal(lit) => lit.text
    _ => ""
  }
}

///|
fn WastParser::get_annotation_arg_text(
  self : WastParser,
  token : Token,
) -> String raise ParserError {
  let text = self.get_text(token)
  if text.length() > 0 {
    return text
  }
  let literal = self.get_literal_text(token)
  if literal.length() > 0 {
    return literal
  }
  raise UnexpectedToken(token, "expected annotation argument")
}

///|
fn WastParser::parse_annotation(
  self : WastParser,
) -> Annotation raise ParserError {
  let token = self.expect(LparAnn, "expected annotation")
  let name = self.get_text(token)
  let args : Array[String] = []
  while !self.check(Rpar) {
    args.push(self.get_annotation_arg_text(self.advance()))
  }
  self.expect_rpar()
  { name, args, }
}

///|
fn WastParser::attach_annotations(
  _ : WastParser,
  field : ModuleField,
  annotations : Array[Annotation],
) -> ModuleField raise ParserError {
  if annotations.length() == 0 {
    return field
  }
  match field {
    FuncField(func) => ModuleField::FuncField({ ..func, annotations, })
    ImportField(im) =>
      match im.desc {
        Func(_) => ModuleField::ImportField({ ..im, annotations, })
        _ =>
          raise Custom(
            "function annotations are only supported on functions and func imports",
          )
      }
    _ =>
      raise Custom(
        "function annotations are only supported on functions and func imports",
      )
  }
}

///|
fn WastParser::parse_annotated_module_fields(
  self : WastParser,
) -> Array[ModuleField] raise ParserError {
  let annotations : Array[Annotation] = []
  while self.check(LparAnn) {
    annotations.push(self.parse_annotation())
  }
  self.expect_lpar()
  if self.check(Import) {
    let imports = self.parse_imports()
    self.expect_rpar()
    let fields : Array[ModuleField] = []
    for im in imports {
      fields.push(
        self.attach_annotations(ModuleField::ImportField(im), annotations),
      )
    }
    fields
  } else {
    let field = self.parse_module_field()
    self.expect_rpar()
    [self.attach_annotations(field, annotations)]
  }
}

// ============================================================================
// Lexer State Management
// ============================================================================

///|
fn WastParser::save_lexer_state(self : WastParser) -> LexerState {
  {
    cursor: self.lexer.cursor,
    line: self.lexer.line,
    line_start: self.lexer.line_start,
  }
}

///|
fn WastParser::restore_lexer_state(
  self : WastParser,
  state : LexerState,
) -> Unit {
  self.lexer.cursor = state.cursor
  self.lexer.line = state.line
  self.lexer.line_start = state.line_start
}

// ============================================================================
// Index Parsing
// ============================================================================

///|
fn WastParser::parse_index(self : WastParser) -> Index raise ParserError {
  match self.current.token_type {
    Nat => {
      let token = self.advance()
      let text = self.get_literal_text(token)
      Index::Num(parse_uint(text))
    }
    Var => {
      let token = self.advance()
      let text = self.get_text(token)
      Index::Id(text)
    }
    _ => raise UnexpectedToken(self.current, "expected index")
  }
}

///|
fn WastParser::try_parse_index(self : WastParser) -> Index? raise ParserError {
  match self.current.token_type {
    Nat | Var => Some(self.parse_index())
    _ => None
  }
}

///|
fn parse_unsigned_literal(
  s : String,
  limit : UInt64,
) -> UInt64 raise ParserError {
  let hex = s.has_prefix("0x") || s.has_prefix("0X")
  let radix = if hex { 16UL } else { 10UL }
  let mut result = 0UL
  let mut has_digit = false
  for i = (if hex { 2 } else { 0 }); i < s.length(); i = i + 1 {
    let c = s[i]
    if c == '_' {
      continue
    }
    let digit = if c >= '0' && c <= '9' {
      (c.to_int() - 48).to_uint64()
    } else if c >= 'a' && c <= 'f' {
      (c.to_int() - 87).to_uint64()
    } else if c >= 'A' && c <= 'F' {
      (c.to_int() - 55).to_uint64()
    } else {
      raise Custom("invalid unsigned literal: \{s}")
    }
    if digit >= radix || result > (limit - digit) / radix {
      raise Custom("unsigned literal exceeds range: \{s}")
    }
    result = result * radix + digit
    has_digit = true
  }
  if !has_digit {
    raise Custom("invalid unsigned literal: \{s}")
  }
  result
}

///|
fn parse_uint(s : String) -> UInt32 raise ParserError {
  parse_unsigned_literal(s, 4294967295UL).to_uint()
}

///|
fn WastParser::parse_nat(
  self : WastParser,
  what : String,
) -> UInt32 raise ParserError {
  let token = self.expect(Nat, what)
  parse_uint(self.get_literal_text(token))
}

///|
fn parse_uint64(s : String) -> UInt64 raise ParserError {
  parse_unsigned_literal(s, 18446744073709551615UL)
}

// ============================================================================
// Value Type Parsing
// ============================================================================

///|
fn WastParser::is_reserved_word(self : WastParser, word : String) -> Bool {
  match self.current.token_type {
    Reserved => self.get_text(self.current) == word
    _ => false
  }
}

///|
fn WastParser::try_parse_atomic_order(self : WastParser) -> AtomicOrder? {
  if self.is_reserved_word("relaxed") {
    let _ = self.advance()
    return Some(Relaxed)
  }
  if self.is_reserved_word("seq_cst") || self.is_reserved_word("seqcst") {
    let _ = self.advance()
    return Some(SeqCst)
  }
  if self.is_reserved_word("acq_rel") || self.is_reserved_word("acqrel") {
    let _ = self.advance()
    return Some(AcqRel)
  }
  None
}

///|
fn WastParser::is_heap_type_token(self : WastParser) -> Bool {
  match self.current.token_type {
    ValueType(vt) =>
      match vt {
        FuncRef | ExternRef | ExnRef => true
        _ => false
      }
    Func | Var | Nat => true
    Reserved => {
      let text = self.get_text(self.current)
      text == "any" ||
      text == "array" ||
      text == "eq" ||
      text == "exn" ||
      text == "extern" ||
      text == "func" ||
      text == "i31" ||
      text == "none" ||
      text == "nofunc" ||
      text == "noextern" ||
      text == "noexn" ||
      text == "struct"
    }
    _ => false
  }
}

///|
fn heap_type_ref_from_index(idx : Index) -> HeapTypeRef {
  match idx {
    Num(n) => HeapTypeRef::TypeNum(n)
    Id(id) => HeapTypeRef::TypeId(id)
  }
}

///|
fn exact_heap_type_ref_from_index(idx : Index) -> HeapTypeRef {
  match idx {
    Num(n) => HeapTypeRef::ExactTypeNum(n)
    Id(id) => HeapTypeRef::ExactTypeId(id)
  }
}

///|
fn heap_type_ref_from_reserved_keyword(text : String) -> HeapTypeRef? {
  match text {
    "any" => Some(HeapTypeRef::Any)
    "string" => Some(HeapTypeRef::String)
    "array" => Some(HeapTypeRef::Array)
    "eq" => Some(HeapTypeRef::Eq)
    "i31" => Some(HeapTypeRef::I31)
    "none" => Some(HeapTypeRef::None_)
    "nofunc" => Some(HeapTypeRef::NoFunc)
    "func" => Some(HeapTypeRef::Func)
    "noextern" => Some(HeapTypeRef::NoExtern)
    "noexn" => Some(HeapTypeRef::NoExn)
    "waitqueue" => Some(HeapTypeRef::Waitqueue)
    "nowaitqueue" => Some(HeapTypeRef::NoWaitqueue)
    "struct" => Some(HeapTypeRef::Struct)
    _ => None
  }
}

///|
fn legacy_alias_value_type_from_reserved_keyword(text : String) -> ValueType? {
  match text {
    "anyref" => Some(TypedFuncRef(HeapTypeRef::Any))
    "stringref" => Some(TypedFuncRef(HeapTypeRef::String))
    "arrayref" => Some(TypedFuncRef(HeapTypeRef::Array))
    "eqref" => Some(TypedFuncRef(HeapTypeRef::Eq))
    "i31ref" => Some(TypedFuncRef(HeapTypeRef::I31))
    "nullref" => Some(TypedFuncRef(HeapTypeRef::None_))
    "structref" => Some(TypedFuncRef(HeapTypeRef::Struct))
    _ => None
  }
}

///|
fn WastParser::can_start_value_type(self : WastParser) -> Bool {
  match self.current.token_type {
    ValueType(_) | Func => true
    Reserved => {
      let text = self.get_text(self.current)
      text == "exn" ||
      text == "extern" ||
      legacy_alias_value_type_from_reserved_keyword(text) is Some(_)
    }
    Lpar => true
    _ => false
  }
}

///|
fn ref_value_type_with_nullability(
  base : ValueType,
  nullable : Bool,
) -> ValueType {
  if nullable {
    match base {
      ExternRef | NonNullExternRef => ExternRef
      ExnRef | NonNullExnRef => ExnRef
      _ => FuncRef
    }
  } else {
    match base {
      ExternRef | NonNullExternRef => NonNullExternRef
      ExnRef | NonNullExnRef => NonNullExnRef
      TypedFuncRef(ht) | NonNullTypedFuncRef(ht) => NonNullTypedFuncRef(ht)
      _ => NonNullFuncRef
    }
  }
}

///|
fn WastParser::parse_heap_type_ref(
  self : WastParser,
) -> HeapTypeRef raise ParserError {
  match self.current.token_type {
    Func => {
      let _ = self.advance()
      HeapTypeRef::Func
    }
    Var | Nat => {
      let idx = self.parse_index()
      heap_type_ref_from_index(idx)
    }
    Reserved => {
      let token = self.advance()
      let text = self.get_text(token)
      if text == "extern" || text == "exn" {
        raise UnexpectedToken(token, "expected heap type")
      }
      match heap_type_ref_from_reserved_keyword(text) {
        Some(heap_ref) => heap_ref
        None => raise UnexpectedToken(token, "expected heap type")
      }
    }
    Lpar => {
      self.expect_lpar()
      if self.is_reserved_word("shared") {
        let _ = self.advance()
        let heap = self.parse_heap_type_ref()
        self.expect_rpar()
        return HeapTypeRef::Shared(heap)
      }
      if !self.is_reserved_word("exact") {
        raise UnexpectedToken(self.current, "expected heap type")
      }
      let _ = self.advance()
      let idx = self.parse_index()
      self.expect_rpar()
      exact_heap_type_ref_from_index(idx)
    }
    _ => raise UnexpectedToken(self.current, "expected heap type")
  }
}

///|
fn WastParser::parse_heap_type_value_type(
  self : WastParser,
  nullable : Bool,
) -> ValueType raise ParserError {
  match self.current.token_type {
    ValueType(vt) => {
      let _ = self.advance()
      ref_value_type_with_nullability(vt, nullable)
    }
    Func => {
      let _ = self.advance()
      ref_value_type_with_nullability(FuncRef, nullable)
    }
    Reserved => {
      let token = self.advance()
      let text = self.get_text(token)
      if text == "exn" {
        ref_value_type_with_nullability(ExnRef, nullable)
      } else if text == "extern" {
        ref_value_type_with_nullability(ExternRef, nullable)
      } else if text == "func" {
        ref_value_type_with_nullability(FuncRef, nullable)
      } else {
        match heap_type_ref_from_reserved_keyword(text) {
          Some(heap_ref) =>
            if nullable {
              TypedFuncRef(heap_ref)
            } else {
              NonNullTypedFuncRef(heap_ref)
            }
          None => raise UnexpectedToken(token, "expected heap type")
        }
      }
    }
    Var | Nat | Lpar => {
      let heap_ref = self.parse_heap_type_ref()
      if nullable {
        TypedFuncRef(heap_ref)
      } else {
        NonNullTypedFuncRef(heap_ref)
      }
    }
    _ => raise UnexpectedToken(self.current, "expected heap type")
  }
}

///|
fn WastParser::try_parse_ref_value_type(
  self : WastParser,
) -> ValueType? raise ParserError {
  if !self.check(Lpar) {
    return None
  }
  let saved_state = self.save_lexer_state()
  let saved_token = self.current
  self.expect_lpar()
  if !self.is_reserved_word("ref") {
    self.restore_lexer_state(saved_state)
    self.current = saved_token
    return None
  }
  let _ = self.advance()
  let nullable = if self.is_reserved_word("null") {
    let _ = self.advance()
    true
  } else {
    false
  }
  if !(self.is_heap_type_token() || self.check(Lpar)) {
    self.restore_lexer_state(saved_state)
    self.current = saved_token
    return None
  }
  let vt = self.parse_heap_type_value_type(nullable)
  self.expect_rpar()
  Some(vt)
}

///|
fn WastParser::parse_value_type(
  self : WastParser,
) -> ValueType raise ParserError {
  match self.current.token_type {
    ValueType(vt) => {
      let _ = self.advance()
      vt
    }
    // 'func' is an abbreviation for funcref in heap type contexts (e.g., ref.null func)
    Func => {
      let _ = self.advance()
      FuncRef
    }
    Reserved => {
      let text = self.get_text(self.current)
      if text == "exn" {
        let _ = self.advance()
        ExnRef
      } else if text == "extern" {
        let _ = self.advance()
        ExternRef
      } else {
        match legacy_alias_value_type_from_reserved_keyword(text) {
          Some(vt) => {
            let _ = self.advance()
            vt
          }
          None => raise UnexpectedToken(self.current, "expected value type")
        }
      }
    }
    Lpar =>
      match self.try_parse_ref_value_type() {
        Some(vt) => vt
        None => raise UnexpectedToken(self.current, "expected value type")
      }
    _ => raise UnexpectedToken(self.current, "expected value type")
  }
}

// ============================================================================
// Limits Parsing
// ============================================================================

///|
fn WastParser::parse_limits(self : WastParser) -> Limits raise ParserError {
  let min_token = self.expect(Nat, "expected minimum limit")
  let min = parse_uint(self.get_literal_text(min_token))
  let max : UInt32? = if self.check(Nat) {
    let max_token = self.advance()
    Some(parse_uint(self.get_literal_text(max_token)))
  } else {
    None
  }
  { min, max, }
}

// ============================================================================
// Type Use Parsing
// ============================================================================

///|
fn WastParser::parse_call_ref_type_use(
  self : WastParser,
) -> TypeUse raise ParserError {
  match self.try_parse_index() {
    Some(index) =>
      {
        type_index: Some(index),
        func_type: { params: [], results: [], },
        param_ids: [],
      }
    None => self.parse_type_use()
  }
}

///|
fn WastParser::parse_type_use(self : WastParser) -> TypeUse raise ParserError {
  let mut type_index : Index? = None
  let params : Array[ValueType] = []
  let results : Array[ValueType] = []
  let param_ids : Array[String?] = []

  // Check for (type $id) or (type 0)
  if self.check(Lpar) {
    let saved_state = self.save_lexer_state()
    let saved_token = self.current
    self.expect_lpar()
    if self.check(Type) {
      let _ = self.advance() // consume 'type'
      type_index = Some(self.parse_index())
      self.expect_rpar()
    } else {
      // Not a type reference, rollback
      self.restore_lexer_state(saved_state)
      self.current = saved_token
    }
  }

  // Parse inline params (only if we didn't find a type reference)
  while self.check(Lpar) {
    let saved_state = self.save_lexer_state()
    let saved_token = self.current
    self.expect_lpar()
    if self.check(Param) {
      let _ = self.advance() // consume 'param'
      // Check for named param: (param $name type)
      if self.current.token_type is Var {
        let id_token = self.advance()
        let param_id : String? = Some(self.get_text(id_token))
        params.push(self.parse_value_type())
        param_ids.push(param_id)
      } else {
        // Abbreviated: (param type*)
        while self.can_start_value_type() {
          params.push(self.parse_value_type())
          param_ids.push(None)
        }
      }
      self.expect_rpar()
    } else {
      // Not a param, rollback and continue
      self.restore_lexer_state(saved_state)
      self.current = saved_token
      break
    }
  }

  // Parse inline results
  while self.check(Lpar) {
    let saved_state = self.save_lexer_state()
    let saved_token = self.current
    self.expect_lpar()
    if self.check(Result) {
      let _ = self.advance() // consume 'result'
      while self.can_start_value_type() {
        results.push(self.parse_value_type())
      }
      self.expect_rpar()
    } else {
      // Not a result, rollback and stop
      self.restore_lexer_state(saved_state)
      self.current = saved_token
      break
    }
  }
  { type_index, func_type: { params, results, }, param_ids, }
}

// ============================================================================
// Instruction Parsing
// ============================================================================

///|
fn WastParser::parse_mem_arg(
  self : WastParser,
  default_align : UInt64,
) -> MemArg raise ParserError {
  let mut offset : UInt64 = 0UL
  let mut align = default_align

  // Parse offset=N and align=N in any order
  while true {
    match self.current.token_type {
      OffsetEqNat => {
        let token = self.advance()
        offset = parse_uint64(self.get_text(token))
      }
      AlignEqNat => {
        let token = self.advance()
        align = parse_uint64(self.get_text(token))
      }
      _ => break
    }
  }
  { align, offset, }
}

///|
fn WastParser::parse_block_type(
  self : WastParser,
) -> BlockType raise ParserError {
  let label : String? = if self.current.token_type is Var {
    let token = self.advance()
    Some(self.get_text(token))
  } else {
    None
  }

  // Try to parse type use for block
  let type_use : TypeUse? = if self.check(Lpar) {
    let saved_state = self.save_lexer_state()
    let saved_token = self.current
    self.expect_lpar()
    if self.check(Type) || self.check(Param) || self.check(Result) {
      // It's a type use, parse it
      self.restore_lexer_state(saved_state)
      self.current = saved_token
      Some(self.parse_type_use())
    } else {
      // Not a type use, restore
      self.restore_lexer_state(saved_state)
      self.current = saved_token
      None
    }
  } else if self.can_start_value_type() {
    // Shorthand: just a result type (no parens)
    let vt = self.parse_value_type()
    Some({
      type_index: None,
      func_type: { params: [], results: [vt], },
      param_ids: [],
    })
  } else {
    None
  }
  { label, type_use, }
}

///|
fn WastParser::parse_instructions(
  self : WastParser,
) -> Array[Instruction] raise ParserError {
  let instrs : Array[Instruction] = []
  while true {
    match self.current.token_type {
      Rpar | Else | End | Eof => break
      Lpar => {
        self.expect_lpar()
        let folded = self.parse_folded_instruction()
        for instr in folded {
          instrs.push(instr)
        }
        self.expect_rpar()
      }
      _ =>
        match self.try_parse_plain_instruction() {
          Some(instr) => instrs.push(instr)
          None => break
        }
    }
  }
  instrs
}

///|
fn WastParser::parse_folded_instruction(
  self : WastParser,
) -> Array[Instruction] raise ParserError {
  match self.current.token_type {
    Block => {
      let _ = self.advance()
      let block_type = self.parse_block_type()
      let body = self.parse_instructions()
      [Block(block_type, body)]
    }
    Loop => {
      let _ = self.advance()
      let block_type = self.parse_block_type()
      let body = self.parse_instructions()
      [Loop(block_type, body)]
    }
    If => {
      let _ = self.advance()
      let block_type = self.parse_block_type()

      // Collect condition operands first
      let result : Array[Instruction] = []

      // Parse condition operands (folded instructions before then/else)
      while self.check(Lpar) {
        let saved = self.save_lexer_state()
        let saved_token = self.current
        self.expect_lpar()
        if self.check(Then) || self.check(Else) {
          self.restore_lexer_state(saved)
          self.current = saved_token
          break
        }
        self.restore_lexer_state(saved)
        self.current = saved_token

        // Parse the operand
        self.expect_lpar()
        let nested = self.parse_folded_instruction()
        for i in nested {
          result.push(i)
        }
        self.expect_rpar()
      }
      let has_folded_then = self.check(Lpar) &&
        ({
          let saved = self.save_lexer_state()
          let saved_token = self.current
          self.expect_lpar()
          let is_then = self.check(Then)
          self.restore_lexer_state(saved)
          self.current = saved_token
          is_then
        })
      let mut then_body : Array[Instruction] = []
      let mut else_body : Array[Instruction] = []
      if has_folded_then {
        // Hybrid syntax
        self.expect_lpar()
        let _ = self.expect(Then, "expected 'then'")
        then_body = self.parse_instructions()
        self.expect_rpar()
        if self.check(Lpar) {
          let saved = self.save_lexer_state()
          let saved_token = self.current
          self.expect_lpar()
          if self.check(Else) {
            let _ = self.advance()
            else_body = self.parse_instructions()
            self.expect_rpar()
          } else {
            self.restore_lexer_state(saved)
            self.current = saved_token
          }
        }
      } else {
        // Flat syntax
        then_body = self.parse_block_body()
        else_body = if self.match_token(Else) {
          self.parse_block_body()
        } else {
          []
        }
        let _ = self.expect(End, "expected 'end'")
      }
      result.push(If(block_type, then_body, else_body))
      result
    }
    Try => {
      let _ = self.advance()
      [self.parse_legacy_try_instruction(false)]
    }
    Rethrow => {
      let _ = self.advance()
      [Rethrow(self.parse_index())]
    }
    Opcode(op) => {
      let _ = self.advance()
      if op is TryTable {
        return [self.parse_try_table_instruction(false)]
      }
      let instr = self.parse_opcode_instruction(op)
      // Parse nested operand instructions
      let result : Array[Instruction] = []
      while self.check(Lpar) {
        self.expect_lpar()
        let nested = self.parse_folded_instruction()
        for i in nested {
          result.push(i)
        }
        self.expect_rpar()
      }
      result.push(instr)
      result
    }
    _ => raise UnexpectedToken(self.current, "expected instruction")
  }
}

///|
fn WastParser::try_parse_plain_instruction(
  self : WastParser,
) -> Instruction? raise ParserError {
  match self.current.token_type {
    Block => {
      let _ = self.advance()
      let block_type = self.parse_block_type()
      let body = self.parse_block_body()
      let _ = self.expect(End, "expected 'end'")
      Some(Block(block_type, body))
    }
    Loop => {
      let _ = self.advance()
      let block_type = self.parse_block_type()
      let body = self.parse_block_body()
      let _ = self.expect(End, "expected 'end'")
      Some(Loop(block_type, body))
    }
    If => {
      let _ = self.advance()
      let block_type = self.parse_block_type()
      let mut used_explicit_syntax = false

      // Check if we're using explicit (then ...) syntax
      let then_body : Array[Instruction] = if self.check(Lpar) {
        let saved_state = self.save_lexer_state()
        let saved_token = self.current
        self.expect_lpar()
        if self.check(Then) {
          // Explicit (then ...) form
          used_explicit_syntax = true
          let _ = self.advance()
          let instrs = self.parse_instructions()
          self.expect_rpar()
          instrs
        } else {
          // Not a then block, rollback and parse as regular instructions
          self.restore_lexer_state(saved_state)
          self.current = saved_token
          self.parse_block_body()
        }
      } else {
        // Flat form without explicit then
        self.parse_block_body()
      }

      // Parse else block
      let else_body : Array[Instruction] = if self.check(Lpar) {
        let saved_state = self.save_lexer_state()
        let saved_token = self.current
        self.expect_lpar()
        if self.check(Else) {
          // Explicit (else ...) form
          used_explicit_syntax = true
          let _ = self.advance()
          let instrs = self.parse_instructions()
          self.expect_rpar()
          instrs
        } else {
          // Not an else block, rollback
          self.restore_lexer_state(saved_state)
          self.current = saved_token
          if self.match_token(Else) {
            self.parse_block_body()
          } else {
            []
          }
        }
      } else if self.match_token(Else) {
        // Bare else keyword in flat form
        self.parse_block_body()
      } else {
        []
      }

      // Only expect 'end' for flat form (no explicit then/else blocks)
      if !used_explicit_syntax {
        let _ = self.expect(End, "expected 'end'")
      }
      Some(If(block_type, then_body, else_body))
    }
    Try => {
      let _ = self.advance()
      Some(self.parse_legacy_try_instruction(true))
    }
    Rethrow => {
      let _ = self.advance()
      Some(Rethrow(self.parse_index()))
    }
    Opcode(TryTable) => {
      let _ = self.advance()
      Some(self.parse_try_table_instruction(true))
    }
    Opcode(op) => {
      let _ = self.advance()
      Some(self.parse_opcode_instruction(op))
    }
    _ => None
  }
}

///|
fn WastParser::parse_block_body(
  self : WastParser,
) -> Array[Instruction] raise ParserError {
  let instrs : Array[Instruction] = []
  while true {
    match self.current.token_type {
      End | Else | Then | Eof => break
      Lpar => {
        let saved_state = self.save_lexer_state()
        let saved_token = self.current
        self.expect_lpar()

        // Check for (then or (else which shouldn't be parsed as instructions
        if self.check(Then) || self.check(Else) {
          self.restore_lexer_state(saved_state)
          self.current = saved_token
          break
        }
        let folded = self.parse_folded_instruction()
        for instr in folded {
          instrs.push(instr)
        }
        self.expect_rpar()
      }
      _ =>
        match self.try_parse_plain_instruction() {
          Some(instr) => instrs.push(instr)
          None => break
        }
    }
  }
  instrs
}

///|
fn WastParser::parse_try_table_block_type(
  self : WastParser,
) -> BlockType raise ParserError {
  let type_use : TypeUse? = if self.check(Lpar) {
    let saved_state = self.save_lexer_state()
    let saved_token = self.current
    self.expect_lpar()
    if self.check(Type) || self.check(Param) || self.check(Result) {
      self.restore_lexer_state(saved_state)
      self.current = saved_token
      Some(self.parse_type_use())
    } else {
      self.restore_lexer_state(saved_state)
      self.current = saved_token
      None
    }
  } else if self.can_start_value_type() {
    let vt = self.parse_value_type()
    Some({
      type_index: None,
      func_type: { params: [], results: [vt], },
      param_ids: [],
    })
  } else {
    None
  }
  { label: None, type_use, }
}

///|
fn WastParser::parse_try_table_catches(
  self : WastParser,
) -> Array[CatchClause] raise ParserError {
  let catches : Array[CatchClause] = []
  while self.check(Lpar) {
    let saved_state = self.save_lexer_state()
    let saved_token = self.current
    self.expect_lpar()
    let parsed : CatchClause? = match self.current.token_type {
      Catch => {
        let _ = self.advance()
        let tag_idx = self.parse_index()
        let label_idx = self.parse_index()
        Some(CatchClause::CatchClause(tag_idx, label_idx))
      }
      CatchRef => {
        let _ = self.advance()
        let tag_idx = self.parse_index()
        let label_idx = self.parse_index()
        Some(CatchClause::CatchRefClause(tag_idx, label_idx))
      }
      CatchAll => {
        let _ = self.advance()
        Some(CatchClause::CatchAllClause(self.parse_index()))
      }
      CatchAllRef => {
        let _ = self.advance()
        Some(CatchClause::CatchAllRefClause(self.parse_index()))
      }
      _ => None
    }
    match parsed {
      Some(catch_clause) => {
        catches.push(catch_clause)
        self.expect_rpar()
      }
      None => {
        self.restore_lexer_state(saved_state)
        self.current = saved_token
        break
      }
    }
  }
  catches
}

///|
fn WastParser::parse_try_table_instruction(
  self : WastParser,
  with_end : Bool,
) -> Instruction raise ParserError {
  let block_type = self.parse_try_table_block_type()
  let catches = self.parse_try_table_catches()
  let body = if with_end {
    self.parse_block_body()
  } else {
    self.parse_instructions()
  }
  if with_end {
    let _ = self.expect(End, "expected 'end'")
  }
  TryTable(block_type, catches, body)
}

///|
fn WastParser::parse_legacy_try_do_clause(
  self : WastParser,
) -> Array[Instruction] raise ParserError {
  self.expect_lpar()
  let _ = self.expect(Do, "expected 'do' clause in legacy try")
  let body = self.parse_instructions()
  self.expect_rpar()
  body
}

///|
fn WastParser::parse_legacy_try_instruction(
  self : WastParser,
  with_end : Bool,
) -> Instruction raise ParserError {
  let block_type = self.parse_block_type()
  let body = self.parse_legacy_try_do_clause()
  let catches : Array[LegacyCatchClause] = []
  let mut delegate_target : Index? = None
  let mut saw_catch_all = false
  while self.check(Lpar) {
    let saved_state = self.save_lexer_state()
    let saved_token = self.current
    self.expect_lpar()
    match self.current.token_type {
      Catch => {
        if delegate_target is Some(_) {
          raise UnexpectedToken(
            self.current,
            "legacy try cannot mix catch and delegate",
          )
        }
        let _ = self.advance()
        let tag_idx = self.parse_index()
        let catch_body = self.parse_instructions()
        self.expect_rpar()
        catches.push(LegacyCatch(tag_idx, catch_body))
      }
      CatchAll => {
        if delegate_target is Some(_) {
          raise UnexpectedToken(
            self.current,
            "legacy try cannot mix catch and delegate",
          )
        }
        if saw_catch_all {
          raise UnexpectedToken(
            self.current,
            "legacy try can only contain one catch_all",
          )
        }
        saw_catch_all = true
        let _ = self.advance()
        let catch_body = self.parse_instructions()
        self.expect_rpar()
        catches.push(LegacyCatchAll(catch_body))
      }
      Delegate => {
        if catches.length() > 0 {
          raise UnexpectedToken(
            self.current,
            "legacy try cannot mix catch and delegate",
          )
        }
        if delegate_target is Some(_) {
          raise UnexpectedToken(
            self.current,
            "legacy try can only contain one delegate",
          )
        }
        let _ = self.advance()
        delegate_target = Some(self.parse_index())
        self.expect_rpar()
      }
      _ => {
        self.restore_lexer_state(saved_state)
        self.current = saved_token
        break
      }
    }
  }
  if with_end {
    let _ = self.expect(End, "expected 'end'")
  }
  TryLegacy(block_type, body, catches, delegate_target)
}

///|
fn WastParser::parse_opcode_instruction(
  self : WastParser,
  op : Opcode,
) -> Instruction raise ParserError {
  match op {
    // Control
    Unreachable => Unreachable
    Nop => Nop
    Br => Br(self.parse_index())
    BrIf => BrIf(self.parse_index())
    BrOnNull => BrOnNull(self.parse_index())
    BrOnNonNull => BrOnNonNull(self.parse_index())
    BrOnCastDescEq =>
      BrOnCastDescEq(
        self.parse_index(),
        self.parse_value_type(),
        self.parse_value_type(),
      )
    BrOnCastDescEqFail =>
      BrOnCastDescEqFail(
        self.parse_index(),
        self.parse_value_type(),
        self.parse_value_type(),
      )
    BrTable => {
      let indices : Array[Index] = []
      while self.current.token_type is (Nat | Var) {
        indices.push(self.parse_index())
      }
      if indices.length() == 0 {
        raise Custom("br_table requires at least one index")
      }
      let default = indices.pop().unwrap()
      BrTable(indices, default)
    }
    Return => Return
    Call => Call(self.parse_index())
    CallIndirect => {
      let table_index = self.try_parse_index().unwrap_or(Index::Num(0))
      let type_use = self.parse_type_use()
      CallIndirect(table_index, type_use)
    }
    ReturnCall => ReturnCall(self.parse_index())
    ReturnCallIndirect => {
      let table_index = self.try_parse_index().unwrap_or(Index::Num(0))
      let type_use = self.parse_type_use()
      ReturnCallIndirect(table_index, type_use)
    }
    CallRef => CallRef(self.parse_call_ref_type_use())
    ReturnCallRef => ReturnCallRef(self.parse_call_ref_type_use())
    Throw => Throw(self.parse_index())
    ThrowRef => ThrowRef
    TryTable => self.parse_try_table_instruction(false)
    StructNew => StructNew(self.parse_index())
    StructNewDefault => StructNewDefault(self.parse_index())
    StructGet => {
      let type_idx = self.parse_index()
      let field_idx = self.parse_nat("expected struct field index")
      StructGet(type_idx, field_idx)
    }
    StructGetS => {
      let type_idx = self.parse_index()
      let field_idx = self.parse_nat("expected struct field index")
      StructGetS(type_idx, field_idx)
    }
    StructGetU => {
      let type_idx = self.parse_index()
      let field_idx = self.parse_nat("expected struct field index")
      StructGetU(type_idx, field_idx)
    }
    WaitqueueNew => WaitqueueNew
    ArrayNew => ArrayNew(self.parse_index())
    ArrayNewDefault => ArrayNewDefault(self.parse_index())
    ArrayNewFixed => {
      let type_idx = self.parse_index()
      let length = self.parse_nat("expected array.new_fixed length")
      ArrayNewFixed(type_idx, length)
    }
    ArrayNewData => {
      let type_idx = self.parse_index()
      let data_idx = self.parse_index()
      ArrayNewData(type_idx, data_idx)
    }
    ArrayNewElem => {
      let type_idx = self.parse_index()
      let elem_idx = self.parse_index()
      ArrayNewElem(type_idx, elem_idx)
    }
    WaitqueueNotify => WaitqueueNotify
    StructWait => {
      let type_idx = self.parse_index()
      let field_idx = self.parse_nat("expected field index")
      StructWait(type_idx, field_idx)
    }
    Publish => Publish
    StructAtomicSet => {
      let order = self.try_parse_atomic_order().unwrap_or(SeqCst)
      let type_idx = self.parse_index()
      let field_idx = self.parse_nat("expected struct field index")
      StructAtomicSet(order, type_idx, field_idx)
    }
    ArrayAtomicSet => {
      let order = self.try_parse_atomic_order().unwrap_or(SeqCst)
      ArrayAtomicSet(order, self.parse_index())
    }
    StructAtomicGet => {
      let order = self.try_parse_atomic_order().unwrap_or(SeqCst)
      let type_idx = self.parse_index()
      let field_idx = self.parse_nat("expected struct field index")
      StructAtomicGet(order, type_idx, field_idx)
    }
    StructAtomicGetS => {
      let order = self.try_parse_atomic_order().unwrap_or(SeqCst)
      let type_idx = self.parse_index()
      let field_idx = self.parse_nat("expected struct field index")
      StructAtomicGetS(order, type_idx, field_idx)
    }
    StructAtomicGetU => {
      let order = self.try_parse_atomic_order().unwrap_or(SeqCst)
      let type_idx = self.parse_index()
      let field_idx = self.parse_nat("expected struct field index")
      StructAtomicGetU(order, type_idx, field_idx)
    }
    StructAtomicRmwAdd => {
      let order = self.parse_atomic_order_pair()
      let type_idx = self.parse_index()
      let field_idx = self.parse_nat("expected struct field index")
      StructAtomicRmw(
        order,
        @lib.AggregateAtomicRmwOp::add(),
        type_idx,
        field_idx,
      )
    }
    StructAtomicRmwSub => {
      let order = self.parse_atomic_order_pair()
      let type_idx = self.parse_index()
      let field_idx = self.parse_nat("expected struct field index")
      StructAtomicRmw(
        order,
        @lib.AggregateAtomicRmwOp::sub(),
        type_idx,
        field_idx,
      )
    }
    StructAtomicRmwAnd => {
      let order = self.parse_atomic_order_pair()
      let type_idx = self.parse_index()
      let field_idx = self.parse_nat("expected struct field index")
      StructAtomicRmw(
        order,
        @lib.AggregateAtomicRmwOp::and_(),
        type_idx,
        field_idx,
      )
    }
    StructAtomicRmwOr => {
      let order = self.parse_atomic_order_pair()
      let type_idx = self.parse_index()
      let field_idx = self.parse_nat("expected struct field index")
      StructAtomicRmw(
        order,
        @lib.AggregateAtomicRmwOp::or_(),
        type_idx,
        field_idx,
      )
    }
    StructAtomicRmwXor => {
      let order = self.parse_atomic_order_pair()
      let type_idx = self.parse_index()
      let field_idx = self.parse_nat("expected struct field index")
      StructAtomicRmw(
        order,
        @lib.AggregateAtomicRmwOp::xor(),
        type_idx,
        field_idx,
      )
    }
    StructAtomicRmwXchg => {
      let order = self.parse_atomic_order_pair()
      let type_idx = self.parse_index()
      let field_idx = self.parse_nat("expected struct field index")
      StructAtomicRmw(
        order,
        @lib.AggregateAtomicRmwOp::xchg(),
        type_idx,
        field_idx,
      )
    }
    StructAtomicRmwCmpxchg => {
      let order = self.parse_atomic_order_pair()
      let type_idx = self.parse_index()
      let field_idx = self.parse_nat("expected struct field index")
      StructAtomicCmpxchg(order, type_idx, field_idx)
    }
    ArrayAtomicGet => {
      let order = self.try_parse_atomic_order().unwrap_or(SeqCst)
      ArrayAtomicGet(order, self.parse_index())
    }
    ArrayAtomicGetS => {
      let order = self.try_parse_atomic_order().unwrap_or(SeqCst)
      ArrayAtomicGetS(order, self.parse_index())
    }
    ArrayAtomicGetU => {
      let order = self.try_parse_atomic_order().unwrap_or(SeqCst)
      ArrayAtomicGetU(order, self.parse_index())
    }
    ArrayAtomicRmwAdd => {
      let order = self.parse_atomic_order_pair()
      ArrayAtomicRmw(
        order,
        @lib.AggregateAtomicRmwOp::add(),
        self.parse_index(),
      )
    }
    ArrayAtomicRmwSub => {
      let order = self.parse_atomic_order_pair()
      ArrayAtomicRmw(
        order,
        @lib.AggregateAtomicRmwOp::sub(),
        self.parse_index(),
      )
    }
    ArrayAtomicRmwAnd => {
      let order = self.parse_atomic_order_pair()
      ArrayAtomicRmw(
        order,
        @lib.AggregateAtomicRmwOp::and_(),
        self.parse_index(),
      )
    }
    ArrayAtomicRmwOr => {
      let order = self.parse_atomic_order_pair()
      ArrayAtomicRmw(
        order,
        @lib.AggregateAtomicRmwOp::or_(),
        self.parse_index(),
      )
    }
    ArrayAtomicRmwXor => {
      let order = self.parse_atomic_order_pair()
      ArrayAtomicRmw(
        order,
        @lib.AggregateAtomicRmwOp::xor(),
        self.parse_index(),
      )
    }
    ArrayAtomicRmwXchg => {
      let order = self.parse_atomic_order_pair()
      ArrayAtomicRmw(
        order,
        @lib.AggregateAtomicRmwOp::xchg(),
        self.parse_index(),
      )
    }
    ArrayAtomicRmwCmpxchg => {
      let order = self.parse_atomic_order_pair()
      ArrayAtomicCmpxchg(order, self.parse_index())
    }
    StructNewDesc => StructNewDesc(self.parse_index())
    StructNewDefaultDesc => StructNewDefaultDesc(self.parse_index())
    RefGetDesc => RefGetDesc(self.parse_index())
    RefTestDesc => RefTestDesc(false, self.parse_index())
    RefTestDescNull => RefTestDesc(true, self.parse_index())
    RefCastDescEq => RefCastDescEq(false, self.parse_index())
    RefCastDescEqNull => RefCastDescEq(true, self.parse_index())

    // Parametric
    Drop => Drop
    Select => {
      // Check for typed select
      let types : Array[ValueType]? = if self.check(Lpar) {
        let saved_state = self.save_lexer_state()
        let saved_token = self.current
        self.expect_lpar()
        if self.check(Result) {
          let _ = self.advance()
          let ts : Array[ValueType] = []
          while self.can_start_value_type() {
            ts.push(self.parse_value_type())
          }
          self.expect_rpar()
          Some(ts)
        } else {
          self.restore_lexer_state(saved_state)
          self.current = saved_token
          None
        }
      } else {
        None
      }
      Select(types)
    }

    // Variable
    LocalGet => LocalGet(self.parse_index())
    LocalSet => LocalSet(self.parse_index())
    LocalTee => LocalTee(self.parse_index())
    GlobalGet => GlobalGet(self.parse_index())
    GlobalSet => GlobalSet(self.parse_index())

    // Table
    TableGet => TableGet(self.try_parse_index().unwrap_or(Index::Num(0)))
    TableSet => TableSet(self.try_parse_index().unwrap_or(Index::Num(0)))
    TableSize => TableSize(self.try_parse_index().unwrap_or(Index::Num(0)))
    TableGrow => TableGrow(self.try_parse_index().unwrap_or(Index::Num(0)))
    TableFill => TableFill(self.try_parse_index().unwrap_or(Index::Num(0)))
    TableCopy => {
      let dst = self.try_parse_index().unwrap_or(Index::Num(0))
      let src = self.try_parse_index().unwrap_or(Index::Num(0))
      TableCopy(dst, src)
    }
    TableInit => {
      // table.init can have either:
      //   table.init elemidx           -> table defaults to 0
      //   table.init tableidx elemidx  -> explicit table index
      let first = self.try_parse_index()
      match first {
        Some(idx1) => {
          let second = self.try_parse_index()
          match second {
            Some(idx2) => TableInit(idx1, idx2) // tableidx, elemidx
            None => TableInit(Index::Num(0), idx1) // default table 0, elemidx
          }
        }
        None => raise Custom("table.init requires at least one index")
      }
    }
    ElemDrop => ElemDrop(self.parse_index())

    // Memory
    MemoryAtomic(op) => self.parse_memory_atomic(op)
    AtomicFence => AtomicFence(self.try_parse_atomic_order().unwrap_or(SeqCst))
    I32Load => self.parse_memory_access(op, 4, false)
    I64Load => self.parse_memory_access(op, 8, false)
    F32Load => self.parse_memory_access(op, 4, false)
    F64Load => self.parse_memory_access(op, 8, false)
    I32Load8S | I32Load8U => self.parse_memory_access(op, 1, false)
    I32Load16S | I32Load16U => self.parse_memory_access(op, 2, false)
    I64Load8S | I64Load8U => self.parse_memory_access(op, 1, false)
    I64Load16S | I64Load16U => self.parse_memory_access(op, 2, false)
    I64Load32S | I64Load32U => self.parse_memory_access(op, 4, false)
    I32Store => self.parse_memory_access(op, 4, true)
    I64Store => self.parse_memory_access(op, 8, true)
    F32Store => self.parse_memory_access(op, 4, true)
    F64Store => self.parse_memory_access(op, 8, true)
    I32Store8 | I64Store8 => self.parse_memory_access(op, 1, true)
    I32Store16 | I64Store16 => self.parse_memory_access(op, 2, true)
    I64Store32 => self.parse_memory_access(op, 4, true)
    MemorySize => MemorySize
    MemoryGrow => MemoryGrow
    MemoryFill => MemoryFill
    MemoryCopy => MemoryCopy
    MemoryInit => MemoryInit(self.parse_index())
    DataDrop => DataDrop(self.parse_index())

    // Const
    I32Const | I64Const => {
      let token = self.advance()
      let text = match token.token_type {
        Nat | Int => self.get_literal_text(token)
        _ => raise UnexpectedToken(token, "expected integer literal")
      }
      Const(op, text)
    }
    F32Const | F64Const => {
      let token = self.advance()
      let text = match token.token_type {
        Nat | Int | Float => self.get_literal_text(token)
        _ => raise UnexpectedToken(token, "expected numeric literal")
      }
      Const(op, text)
    }

    // Unary
    I32Clz
    | I32Ctz
    | I32Popcnt
    | I64Clz
    | I64Ctz
    | I64Popcnt
    | F32Abs
    | F32Neg
    | F32Ceil
    | F32Floor
    | F32Trunc
    | F32Nearest
    | F32Sqrt
    | F64Abs
    | F64Neg
    | F64Ceil
    | F64Floor
    | F64Trunc
    | F64Nearest
    | F64Sqrt
    | I32Extend8S
    | I32Extend16S
    | I64Extend8S
    | I64Extend16S
    | I64Extend32S
    | RefAsNonNull
    | RefI31
    | RefI31Shared
    | I31GetS
    | I31GetU => Unary(op)

    // Binary
    I32Add
    | I32Sub
    | I32Mul
    | I32DivS
    | I32DivU
    | I32RemS
    | I32RemU
    | I32And
    | I32Or
    | I32Xor
    | I32Shl
    | I32ShrS
    | I32ShrU
    | I32Rotl
    | I32Rotr
    | I64Add
    | I64Sub
    | I64Mul
    | I64DivS
    | I64DivU
    | I64RemS
    | I64RemU
    | I64And
    | I64Or
    | I64Xor
    | I64Shl
    | I64ShrS
    | I64ShrU
    | I64Rotl
    | I64Rotr
    | F32Add
    | F32Sub
    | F32Mul
    | F32Div
    | F32Min
    | F32Max
    | F32Copysign
    | F64Add
    | F64Sub
    | F64Mul
    | F64Div
    | F64Min
    | F64Max
    | F64Copysign => Binary(op)

    // Test
    I32Eqz | I64Eqz => Test(op)

    // Compare
    I32Eq
    | I32Ne
    | I32LtS
    | I32LtU
    | I32GtS
    | I32GtU
    | I32LeS
    | I32LeU
    | I32GeS
    | I32GeU
    | I64Eq
    | I64Ne
    | I64LtS
    | I64LtU
    | I64GtS
    | I64GtU
    | I64LeS
    | I64LeU
    | I64GeS
    | I64GeU
    | F32Eq
    | F32Ne
    | F32Lt
    | F32Gt
    | F32Le
    | F32Ge
    | F64Eq
    | F64Ne
    | F64Lt
    | F64Gt
    | F64Le
    | F64Ge
    | RefEq => Compare(op)

    // Conversion
    I32WrapI64
    | I32TruncF32S
    | I32TruncF32U
    | I32TruncF64S
    | I32TruncF64U
    | I64ExtendI32S
    | I64ExtendI32U
    | I64TruncF32S
    | I64TruncF32U
    | I64TruncF64S
    | I64TruncF64U
    | F32ConvertI32S
    | F32ConvertI32U
    | F32ConvertI64S
    | F32ConvertI64U
    | F32DemoteF64
    | F64ConvertI32S
    | F64ConvertI32U
    | F64ConvertI64S
    | F64ConvertI64U
    | F64PromoteF32
    | I32ReinterpretF32
    | I64ReinterpretF64
    | F32ReinterpretI32
    | F64ReinterpretI64
    | I32TruncSatF32S
    | I32TruncSatF32U
    | I32TruncSatF64S
    | I32TruncSatF64U
    | I64TruncSatF32S
    | I64TruncSatF32U
    | I64TruncSatF64S
    | I64TruncSatF64U
    | AnyConvertExtern
    | ExternConvertAny => Convert(op)

    // Reference
    RefNull => {
      let vt = self.parse_heap_type_value_type(true)
      RefNull(vt)
    }
    RefIsNull => RefIsNull
    RefFunc => RefFunc(self.parse_index())
    StringConst => {
      let text = self.expect(Text, "expected string.const literal")
      StringConst(self.decode_string(self.get_text(text)))
    }
    StringMeasureWtf16 => StringMeasureWtf16
    StringConcat => StringConcat
    StringEq => StringEq
    StringAsWtf16 => StringAsWtf16
    StringViewWtf16GetCodeunit => StringViewWtf16GetCodeunit
    StringViewWtf16Slice => StringViewWtf16Slice
    StringNewUtf8Array => StringNewUtf8Array
    StringNewWtf16Array => StringNewWtf16Array
    StringEncodeUtf8Array => StringEncodeUtf8Array
    StringEncodeWtf16Array => StringEncodeWtf16Array
    StringNewLossyUtf8Array => StringNewLossyUtf8Array
    StringNewWtf8Array => StringNewWtf8Array
    StringEncodeLossyUtf8Array => StringEncodeLossyUtf8Array
    StringEncodeWtf8Array => StringEncodeWtf8Array

    // SIMD - delegate to SIMD parser
    _ => self.parse_simd_instruction(op)
  }
}

// ============================================================================
// Module Field Parsing
// ============================================================================

///|
fn WastParser::parse_type_def(self : WastParser) -> TypeDef raise ParserError {
  let _ = self.expect(Type, "expected 'type'")
  let id : String? = if self.current.token_type is Var {
    let token = self.advance()
    Some(self.get_text(token))
  } else {
    None
  }
  match self.try_parse_type_sub_wrapper() {
    Some((final_, supers, metadata)) => {
      let body = self.parse_type_def_body()
      self.expect_rpar()
      return { id, is_sub: true, final_, supers, metadata, body, }
    }
    None => ()
  }
  let metadata = self.parse_type_metadata_clauses()
  let body = self.parse_type_def_body()
  { id, is_sub: false, final_: false, supers: [], metadata, body, }
}

///|
fn WastParser::try_parse_type_sub_wrapper(
  self : WastParser,
) -> (Bool, Array[Index], TypeDefMetadata)? raise ParserError {
  if !self.check(Lpar) {
    return None
  }
  let saved_state = self.save_lexer_state()
  let saved_token = self.current
  self.expect_lpar()
  if !self.is_reserved_word("sub") {
    self.restore_lexer_state(saved_state)
    self.current = saved_token
    return None
  }
  let _ = self.advance()
  let mut final_ = false
  if self.is_reserved_word("final") {
    let _ = self.advance()
    final_ = true
  }
  let supers : Array[Index] = []
  while self.current.token_type is Var || self.current.token_type is Nat {
    supers.push(self.parse_index())
  }
  let metadata = self.parse_type_metadata_clauses()
  Some((final_, supers, metadata))
}

///|
fn WastParser::try_parse_type_metadata_clause(
  self : WastParser,
) -> (String, Index)? raise ParserError {
  if !self.check(Lpar) {
    return None
  }
  let saved_state = self.save_lexer_state()
  let saved_token = self.current
  self.expect_lpar()
  let kind = if self.is_reserved_word("describes") {
    let _ = self.advance()
    "describes"
  } else if self.is_reserved_word("descriptor") {
    let _ = self.advance()
    "descriptor"
  } else {
    self.restore_lexer_state(saved_state)
    self.current = saved_token
    return None
  }
  let idx = self.parse_index()
  self.expect_rpar()
  Some((kind, idx))
}

///|
fn WastParser::parse_type_metadata_clauses(
  self : WastParser,
) -> TypeDefMetadata raise ParserError {
  let mut describes : Index? = None
  let mut descriptor : Index? = None
  while true {
    match self.try_parse_type_metadata_clause() {
      Some((kind, idx)) =>
        if kind == "describes" {
          if descriptor is Some(_) {
            raise InvalidInstruction(
              "describes clause must precede descriptor clause in type definition",
            )
          }
          if describes is Some(_) {
            raise InvalidInstruction(
              "duplicate describes clause in type definition",
            )
          }
          describes = Some(idx)
        } else {
          if descriptor is Some(_) {
            raise InvalidInstruction(
              "duplicate descriptor clause in type definition",
            )
          }
          descriptor = Some(idx)
        }
      None => break
    }
  }
  { describes, descriptor, }
}

///|
fn WastParser::parse_type_def_body(
  self : WastParser,
) -> TypeDefBody raise ParserError {
  self.expect_lpar()
  if self.is_reserved_word("shared") {
    let _ = self.advance()
    let body = self.parse_type_def_body()
    self.expect_rpar()
    if body is TypeDefBody::Shared(_) {
      raise InvalidInstruction("duplicate shared type wrapper")
    }
    return TypeDefBody::Shared(body)
  }
  if self.check(Func) {
    let _ = self.advance()
    let func_type = self.parse_func_type_signature()
    self.expect_rpar()
    return TypeDefBody::Func(func_type)
  }
  if self.is_reserved_word("struct") {
    let _ = self.advance()
    let fields : Array[StructFieldDef] = []
    while self.check(Lpar) {
      fields.append(self.parse_struct_field_group())
    }
    self.expect_rpar()
    return TypeDefBody::Struct(fields)
  }
  if self.is_reserved_word("array") {
    let _ = self.advance()
    let field = self.parse_struct_field_item()
    self.expect_rpar()
    return TypeDefBody::Array(field)
  }
  raise UnexpectedToken(self.current, "expected type body")
}

///|
fn WastParser::parse_func_type_signature(
  self : WastParser,
) -> FuncType raise ParserError {
  let params : Array[ValueType] = []
  let results : Array[ValueType] = []

  while self.check(Lpar) {
    let saved_state = self.save_lexer_state()
    let saved_token = self.current
    self.expect_lpar()
    if self.check(Param) {
      let _ = self.advance()
      if self.current.token_type is Var {
        let _ = self.advance()
        params.push(self.parse_value_type())
      } else {
        while self.can_start_value_type() {
          params.push(self.parse_value_type())
        }
      }
      self.expect_rpar()
    } else {
      self.restore_lexer_state(saved_state)
      self.current = saved_token
      break
    }
  }

  while self.check(Lpar) {
    let saved_state = self.save_lexer_state()
    let saved_token = self.current
    self.expect_lpar()
    if self.check(Result) {
      let _ = self.advance()
      while self.can_start_value_type() {
        results.push(self.parse_value_type())
      }
      self.expect_rpar()
    } else {
      self.restore_lexer_state(saved_state)
      self.current = saved_token
      break
    }
  }
  { params, results, }
}

///|
fn WastParser::parse_field_storage(
  self : WastParser,
) -> FieldStorage raise ParserError {
  match self.current.token_type {
    Reserved => {
      let text = self.get_text(self.current)
      if text == "i8" {
        let _ = self.advance()
        FieldStorage::I8
      } else if text == "i16" {
        let _ = self.advance()
        FieldStorage::I16
      } else {
        FieldStorage::Value(self.parse_value_type())
      }
    }
    _ => FieldStorage::Value(self.parse_value_type())
  }
}

///|
fn WastParser::parse_struct_field_item(
  self : WastParser,
) -> StructFieldDef raise ParserError {
  if self.check(Lpar) {
    let saved_state = self.save_lexer_state()
    let saved_token = self.current
    self.expect_lpar()
    if self.check(Mut) {
      let _ = self.advance()
      let storage = self.parse_field_storage()
      self.expect_rpar()
      return { storage, mutable_: true, }
    }
    self.restore_lexer_state(saved_state)
    self.current = saved_token
  }
  let storage = self.parse_field_storage()
  { storage, mutable_: false, }
}

///|
fn WastParser::parse_struct_field_group(
  self : WastParser,
) -> Array[StructFieldDef] raise ParserError {
  self.expect_lpar()
  if !self.is_reserved_word("field") {
    raise UnexpectedToken(self.current, "expected field")
  }
  let _ = self.advance()
  let fields : Array[StructFieldDef] = []
  while !self.check(Rpar) {
    fields.push(self.parse_struct_field_item())
  }
  self.expect_rpar()
  fields
}

///|
fn WastParser::parse_inline_exports(
  self : WastParser,
) -> Array[InlineExport] raise ParserError {
  let exports : Array[InlineExport] = []
  while self.check(Lpar) {
    let saved_state = self.save_lexer_state()
    let saved_token = self.current
    self.expect_lpar()
    if self.check(Export) {
      let _ = self.advance()
      let name_token = self.expect(Text, "expected export name")
      let name = self.decode_name(self.get_text(name_token))
      exports.push(InlineExport::Export(name))
      self.expect_rpar()
    } else {
      self.restore_lexer_state(saved_state)
      self.current = saved_token
      break
    }
  }
  exports
}

///|
fn WastParser::parse_func(self : WastParser) -> ModuleField raise ParserError {
  let _ = self.expect(Func, "expected 'func'")
  let id : String? = if self.current.token_type is Var {
    let token = self.advance()
    Some(self.get_text(token))
  } else {
    None
  }
  let exports = self.parse_inline_exports()

  // Inline func import shorthand: (func $id (import "m" "f") (type ...))
  if self.check(Lpar) {
    let saved_state = self.save_lexer_state()
    let saved_token = self.current
    self.expect_lpar()
    if self.check(Import) {
      let _ = self.advance()
      let module_token = self.expect(Text, "expected import module name")
      let field_token = self.expect(Text, "expected import field name")
      let module_name = self.decode_name(self.get_text(module_token))
      let field_name = self.decode_name(self.get_text(field_token))
      self.expect_rpar()
      if exports.length() > 0 {
        raise Custom(
          "inline exports are not supported on inline func import shorthand; use an explicit (export ...) field",
        )
      }
      let type_use = self.parse_type_use()
      return ModuleField::ImportField({
        module_name,
        field_name,
        desc: ImportDesc::Func(type_use),
        id,
        annotations: [],
      })
    }
    self.restore_lexer_state(saved_state)
    self.current = saved_token
  }

  let type_use = self.parse_type_use()

  // Parse locals
  let locals : Array[Local] = []
  while self.check(Lpar) {
    let saved_state = self.save_lexer_state()
    let saved_token = self.current
    self.expect_lpar()
    if self.check(Local) {
      let _ = self.advance()
      if self.current.token_type is Var {
        let id_token = self.advance()
        let local_id : String? = Some(self.get_text(id_token))
        let vt = self.parse_value_type()
        locals.push({ id: local_id, val_type: vt, })
      } else {
        while self.can_start_value_type() {
          let vt = self.parse_value_type()
          locals.push({ id: None, val_type: vt, })
        }
      }
      self.expect_rpar()
    } else {
      self.restore_lexer_state(saved_state)
      self.current = saved_token
      break
    }
  }
  let body = self.parse_instructions()
  ModuleField::FuncField({
    id,
    annotations: [],
    exports,
    type_use,
    locals,
    body,
  })
}

///|
fn WastParser::parse_elem_item_instrs(
  self : WastParser,
) -> ElemInitExpr raise ParserError {
  self.expect_lpar()
  let explicit_item = if self.check(Item) {
    let _ = self.advance()
    true
  } else {
    false
  }
  let instrs = self.parse_instructions()
  self.expect_rpar()
  { instrs, explicit_item, }
}

///|
fn WastParser::parse_table(self : WastParser) -> Table raise ParserError {
  let _ = self.expect(Table, "expected 'table'")
  let id : String? = if self.current.token_type is Var {
    let token = self.advance()
    Some(self.get_text(token))
  } else {
    None
  }
  let exports = self.parse_inline_exports()
  fn parse_table_elem_init() -> (
    Array[Index],
    Array[ElemInitExpr],
    ValueType?,
    Array[Instruction]?,
  ) raise ParserError {
    let mut offset : Array[Instruction]? = None
    if self.check(Lpar) {
      let saved_state = self.save_lexer_state()
      let saved_token = self.current
      self.expect_lpar()
      if self.check(Offset) {
        let _ = self.advance()
        offset = Some(self.parse_instructions())
        self.expect_rpar()
      } else if self.check(Item) {
        self.restore_lexer_state(saved_state)
        self.current = saved_token
      } else if (match self.current.token_type {
          Opcode(RefFunc) | Opcode(RefNull) => true
          _ => false
        }) {
        self.restore_lexer_state(saved_state)
        self.current = saved_token
      } else {
        offset = Some(self.parse_instructions())
        self.expect_rpar()
      }
    }

    let mut elem_init_type : ValueType? = None
    if self.check(Func) {
      let _ = self.advance()
    } else if self.can_start_value_type() {
      let saved_state = self.save_lexer_state()
      let saved_token = self.current
      let parsed_type : ValueType? = Some(self.parse_value_type()) catch {
        _ => {
          self.restore_lexer_state(saved_state)
          self.current = saved_token
          None
        }
      }
      match parsed_type {
        Some(v) => elem_init_type = Some(v)
        None => ()
      }
    }

    let func_indices : Array[Index] = []
    while self.current.token_type is (Nat | Var) {
      func_indices.push(self.parse_index())
    }
    let elem_exprs : Array[ElemInitExpr] = []
    while self.check(Lpar) {
      elem_exprs.push(self.parse_elem_item_instrs())
    }
    (func_indices, elem_exprs, elem_init_type, offset)
  }

  if self.current.token_type is ValueType(_) {
    // Abbreviation: (table funcref (elem ...))
    let elem_type = self.parse_value_type()
    self.expect_lpar()
    let _ = self.expect(Elem, "expected 'elem' table initializer")
    let (func_indices, elem_exprs, elem_init_type, elem_offset) = parse_table_elem_init()
    self.expect_rpar()
    let limits : Limits = {
      min: (func_indices.length() + elem_exprs.length()).reinterpret_as_uint(),
      max: None,
    }
    return {
      id,
      exports,
      table_type: { limits, elem_type, },
      elem_init: Some(func_indices),
      elem_exprs: Some(elem_exprs),
      elem_init_type,
      elem_offset: Some(elem_offset.unwrap_or([Const(I32Const, "0")])),
    }
  }

  let limits = self.parse_limits()
  let elem_type = self.parse_value_type()
  let (elem_init, elem_exprs, elem_init_type, elem_offset) : (
    Array[Index]?,
    Array[ElemInitExpr]?,
    ValueType?,
    Array[Instruction]?,
  ) = if self.check(Lpar) {
    let saved_state = self.save_lexer_state()
    let saved_token = self.current
    self.expect_lpar()
    if self.check(Elem) {
      let _ = self.advance()
      let (func_indices, parsed_exprs, parsed_elem_type, parsed_offset) = parse_table_elem_init()
      self.expect_rpar()
      (
        Some(func_indices),
        Some(parsed_exprs),
        parsed_elem_type,
        Some(parsed_offset.unwrap_or([Const(I32Const, "0")])),
      )
    } else {
      self.restore_lexer_state(saved_state)
      self.current = saved_token
      (None, None, None, None)
    }
  } else {
    (None, None, None, None)
  }
  {
    id,
    exports,
    table_type: { limits, elem_type, },
    elem_init,
    elem_exprs,
    elem_init_type,
    elem_offset,
  }
}

///|
fn WastParser::parse_memory(self : WastParser) -> Memory raise ParserError {
  let _ = self.expect(Memory, "expected 'memory'")
  let id : String? = if self.current.token_type is Var {
    let token = self.advance()
    Some(self.get_text(token))
  } else {
    None
  }
  let exports = self.parse_inline_exports()
  let memory_type = self.parse_memory_type()
  { id, exports, memory_type, }
}

///|
fn WastParser::parse_tag_type_use_or_default(
  self : WastParser,
) -> TypeUse raise ParserError {
  if self.check(Lpar) {
    let saved_state = self.save_lexer_state()
    let saved_token = self.current
    self.expect_lpar()
    if self.check(Type) || self.check(Param) || self.check(Result) {
      self.restore_lexer_state(saved_state)
      self.current = saved_token
      return self.parse_type_use()
    }
    self.restore_lexer_state(saved_state)
    self.current = saved_token
  }
  { type_index: None, func_type: { params: [], results: [], }, param_ids: [], }
}

///|
fn WastParser::parse_tag_field(
  self : WastParser,
) -> ModuleField raise ParserError {
  let _ = self.expect(Tag, "expected 'tag'")
  let id : String? = if self.current.token_type is Var {
    let token = self.advance()
    Some(self.get_text(token))
  } else {
    None
  }
  let exports = self.parse_inline_exports()

  let inline_import : (String, String)? = if self.check(Lpar) {
    let saved_state = self.save_lexer_state()
    let saved_token = self.current
    self.expect_lpar()
    if self.check(Import) {
      let _ = self.advance()
      let module_token = self.expect(Text, "expected module name")
      let module_name = self.decode_name(self.get_text(module_token))
      let field_token = self.expect(Text, "expected field name")
      let field_name = self.decode_name(self.get_text(field_token))
      self.expect_rpar()
      Some((module_name, field_name))
    } else {
      self.restore_lexer_state(saved_state)
      self.current = saved_token
      None
    }
  } else {
    None
  }

  let type_use = self.parse_tag_type_use_or_default()
  match inline_import {
    Some((module_name, field_name)) => {
      if exports.length() > 0 {
        raise Custom(
          "inline exports are not supported on inline tag import shorthand; use an explicit (export ...) field",
        )
      }
      ModuleField::ImportField({
        module_name,
        field_name,
        desc: ImportDesc::Tag(type_use),
        id,
        annotations: [],
      })
    }
    None => ModuleField::TagField({ id, exports, type_use, })
  }
}

///|
fn WastParser::parse_global(self : WastParser) -> Global raise ParserError {
  let _ = self.expect(Global, "expected 'global'")
  let id : String? = if self.current.token_type is Var {
    let token = self.advance()
    Some(self.get_text(token))
  } else {
    None
  }
  let exports = self.parse_inline_exports()
  let global_type = self.parse_global_type()
  let init = self.parse_instructions()
  { id, exports, global_type, init, }
}

///|
fn WastParser::parse_global_type(
  self : WastParser,
) -> GlobalType raise ParserError {
  let (val_type, mutable_) = if self.check(Lpar) {
    let saved_state = self.save_lexer_state()
    let saved_token = self.current
    self.expect_lpar()
    if self.check(Mut) {
      let _ = self.advance()
      let vt = self.parse_value_type()
      self.expect_rpar()
      (vt, true)
    } else {
      self.restore_lexer_state(saved_state)
      self.current = saved_token
      (self.parse_value_type(), false)
    }
  } else {
    (self.parse_value_type(), false)
  }
  { val_type, mutable_, }
}

///|
fn WastParser::parse_import_desc(
  self : WastParser,
) -> (ImportDesc, String?) raise ParserError {
  self.expect_lpar()
  let (desc, id) = match self.current.token_type {
    Func => {
      let _ = self.advance()
      let func_id : String? = if self.current.token_type is Var {
        let token = self.advance()
        Some(self.get_text(token))
      } else {
        None
      }
      let type_use = self.parse_type_use()
      (ImportDesc::Func(type_use), func_id)
    }
    Table => {
      let _ = self.advance()
      let table_id : String? = if self.current.token_type is Var {
        let token = self.advance()
        Some(self.get_text(token))
      } else {
        None
      }
      let limits = self.parse_limits()
      let elem_type = self.parse_value_type()
      (ImportDesc::Table({ limits, elem_type, }), table_id)
    }
    Memory => {
      let _ = self.advance()
      let mem_id : String? = if self.current.token_type is Var {
        let token = self.advance()
        Some(self.get_text(token))
      } else {
        None
      }
      let memory_type = self.parse_memory_type()
      (ImportDesc::Memory(memory_type), mem_id)
    }
    Global => {
      let _ = self.advance()
      let global_id : String? = if self.current.token_type is Var {
        let token = self.advance()
        Some(self.get_text(token))
      } else {
        None
      }
      (ImportDesc::Global(self.parse_global_type()), global_id)
    }
    Tag => {
      let _ = self.advance()
      let tag_id : String? = if self.current.token_type is Var {
        let token = self.advance()
        Some(self.get_text(token))
      } else {
        None
      }
      let type_use = self.parse_tag_type_use_or_default()
      (ImportDesc::Tag(type_use), tag_id)
    }
    _ => raise UnexpectedToken(self.current, "expected import descriptor")
  }
  self.expect_rpar()
  (desc, id)
}

///|
fn WastParser::parse_imports(
  self : WastParser,
) -> Array[Import] raise ParserError {
  let _ = self.expect(Import, "expected 'import'")
  let module_token = self.expect(Text, "expected module name")
  let module_name = self.decode_name(self.get_text(module_token))
  if self.check(Text) {
    let field_token = self.advance()
    let field_name = self.decode_name(self.get_text(field_token))
    let (desc, id) = self.parse_import_desc()
    return [{ module_name, field_name, desc, id, annotations: [], }]
  }
  let items : Array[(String, String?, ImportDesc?)] = []
  let mut has_individual_desc = false
  let mut has_shared_desc = false
  while self.check(Lpar) {
    let saved_state = self.save_lexer_state()
    let saved_token = self.current
    self.expect_lpar()
    if !self.check(Item) {
      self.restore_lexer_state(saved_state)
      self.current = saved_token
      break
    }
    let _ = self.advance()
    let id : String? = if self.check(Var) {
      Some(self.get_text(self.advance()))
    } else {
      None
    }
    let token = self.expect(Text, "expected compact import item name")
    let field_name = self.decode_name(self.get_text(token))
    let desc : ImportDesc? = if self.check(Lpar) {
      let (desc, desc_id) = self.parse_import_desc()
      if desc_id is Some(_) {
        raise Custom("compact import IDs belong on items")
      }
      has_individual_desc = true
      Some(desc)
    } else {
      has_shared_desc = true
      None
    }
    self.expect_rpar()
    items.push((field_name, id, desc))
  }
  if has_individual_desc && has_shared_desc {
    raise Custom("compact imports cannot mix individual and shared descriptors")
  }
  let common_desc : ImportDesc? = if has_shared_desc ||
    (items.is_empty() && self.check(Lpar)) {
    let (desc, id) = self.parse_import_desc()
    if id is Some(_) {
      raise Custom("compact import IDs belong on items")
    }
    Some(desc)
  } else {
    None
  }
  let out : Array[Import] = []
  for item in items {
    let (field_name, id, own_desc) = item
    let desc = match own_desc {
      Some(desc) => desc
      None =>
        match common_desc {
          Some(desc) => desc
          None => raise Custom("expected shared compact import descriptor")
        }
    }
    out.push({ module_name, field_name, desc, id, annotations: [], })
  }
  out
}

///|
fn WastParser::parse_export(self : WastParser) -> Export raise ParserError {
  let _ = self.expect(Export, "expected 'export'")
  let name_token = self.expect(Text, "expected export name")
  let name = self.decode_name(self.get_text(name_token))
  self.expect_lpar()
  let desc = match self.current.token_type {
    Func => {
      let _ = self.advance()
      ExportDesc::Func(self.parse_index())
    }
    Table => {
      let _ = self.advance()
      ExportDesc::Table(self.parse_index())
    }
    Memory => {
      let _ = self.advance()
      ExportDesc::Memory(self.parse_index())
    }
    Global => {
      let _ = self.advance()
      ExportDesc::Global(self.parse_index())
    }
    Tag => {
      let _ = self.advance()
      ExportDesc::Tag(self.parse_index())
    }
    _ => raise UnexpectedToken(self.current, "expected export descriptor")
  }
  self.expect_rpar()
  { name, desc, }
}

///|
pub fn WastParser::parse_script(
  self : WastParser,
) -> WastScript raise ParserError {
  let commands : Array[WastCommand] = []
  while !self.check(Eof) {
    self.expect_lpar()
    let cmd = self.parse_command()
    commands.push(cmd)
    self.expect_rpar()
  }
  self.check_lexer_errors()
  { commands, }
}

///|
fn WastParser::parse_command(
  self : WastParser,
) -> WastCommand raise ParserError {
  match self.current.token_type {
    Module => self.parse_module_command()
    Register => self.parse_register_command()
    Invoke => WastCommand::Action(self.parse_action())
    Get => WastCommand::Action(self.parse_action())
    AssertReturn => self.parse_assert_return()
    AssertTrap => self.parse_assert_trap()
    AssertException => self.parse_assert_exception()
    AssertExhaustion => self.parse_assert_exhaustion()
    AssertMalformed => self.parse_assert_malformed()
    AssertInvalid => self.parse_assert_invalid()
    AssertUnlinkable => self.parse_assert_unlinkable()
    _ => raise UnexpectedToken(self.current, "expected WAST command")
  }
}

///|
fn WastParser::parse_module_command(
  self : WastParser,
) -> WastCommand raise ParserError {
  let _ = self.expect(Module, "expected 'module'")

  // Check for optional module id
  let id : String? = if self.current.token_type is Var {
    let token = self.advance()
    Some(self.get_text(token))
  } else {
    None
  }

  // Check for binary or quote form
  match self.current.token_type {
    Binary => {
      let _ = self.advance()
      let data = self.parse_data_strings()
      WastCommand::ModuleBinary(id, data)
    }
    Quote => {
      let _ = self.advance()
      let strings = self.parse_quote_strings()
      WastCommand::ModuleQuote(id, strings)
    }
    _ => {
      // Regular module - parse fields
      let fields : Array[ModuleField] = []
      while self.check(Lpar) || self.check(LparAnn) {
        for field in self.parse_annotated_module_fields() {
          fields.push(field)
        }
      }
      WastCommand::Module({ id, fields, })
    }
  }
}

///|
fn WastParser::parse_data_strings(self : WastParser) -> Bytes raise ParserError {
  let buf = @buffer.new()
  while self.check(Text) {
    let token = self.advance()
    let text = self.get_text(token)
    let decoded = self.decode_string(text)
    buf.write_bytes(decoded)
  }
  buf.to_bytes()
}

///|
fn WastParser::parse_quote_strings(
  self : WastParser,
) -> Array[String] raise ParserError {
  let strings : Array[String] = []
  while self.check(Text) {
    let token = self.advance()
    strings.push(self.decode_name(self.get_text(token)))
  }
  strings
}

///|
fn WastParser::parse_register_command(
  self : WastParser,
) -> WastCommand raise ParserError {
  let _ = self.expect(Register, "expected 'register'")
  let name_token = self.expect(Text, "expected register name")
  let name = self.decode_name(self.get_text(name_token))
  let module_id : String? = if self.current.token_type is Var {
    let token = self.advance()
    Some(self.get_text(token))
  } else {
    None
  }
  WastCommand::Register(name, module_id)
}

///|
fn WastParser::parse_action(
  self : WastParser,
) -> WastActionType raise ParserError {
  match self.current.token_type {
    Invoke => {
      let _ = self.advance()
      let module_name : String? = if self.current.token_type is Var {
        let token = self.advance()
        Some(self.get_text(token))
      } else {
        None
      }
      let name_token = self.expect(Text, "expected function name")
      let name = self.decode_name(self.get_text(name_token))
      let args = self.parse_wast_values()
      WastActionType::Invoke({ module_name, name, args, })
    }
    Get => {
      let _ = self.advance()
      let module_name : String? = if self.current.token_type is Var {
        let token = self.advance()
        Some(self.get_text(token))
      } else {
        None
      }
      let name_token = self.expect(Text, "expected global name")
      let name = self.decode_name(self.get_text(name_token))
      WastActionType::Get(module_name, name)
    }
    _ => raise UnexpectedToken(self.current, "expected 'invoke' or 'get'")
  }
}

///|
fn WastParser::parse_wast_values(
  self : WastParser,
) -> Array[WastValue] raise ParserError {
  let values : Array[WastValue] = []
  while self.check(Lpar) {
    self.expect_lpar()
    let value = self.parse_wast_value()
    values.push(value)
    self.expect_rpar()
  }
  values
}

///|
fn WastParser::parse_wast_value(
  self : WastParser,
) -> WastValue raise ParserError {
  match self.current.token_type {
    Opcode(I32Const) => {
      let _ = self.advance()
      let token = self.advance()
      WastValue::I32(self.get_literal_text(token))
    }
    Opcode(I64Const) => {
      let _ = self.advance()
      let token = self.advance()
      WastValue::I64(self.get_literal_text(token))
    }
    Opcode(F32Const) => {
      let _ = self.advance()
      let token = self.advance()
      WastValue::F32(self.get_literal_text(token))
    }
    Opcode(F64Const) => {
      let _ = self.advance()
      let token = self.advance()
      WastValue::F64(self.get_literal_text(token))
    }
    Opcode(RefNull) => {
      let _ = self.advance()
      let vt = self.parse_heap_type_value_type(true)
      WastValue::RefNull(vt)
    }
    Opcode(RefFunc) => {
      let _ = self.advance()
      WastValue::RefFunc
    }
    _ => raise UnexpectedToken(self.current, "expected const instruction")
  }
}

///|
fn WastParser::parse_wast_results(
  self : WastParser,
) -> Array[WastResult] raise ParserError {
  let results : Array[WastResult] = []
  while self.check(Lpar) {
    self.expect_lpar()
    let result = self.parse_wast_result()
    results.push(result)
    self.expect_rpar()
  }
  results
}

///|
fn WastParser::parse_wast_result(
  self : WastParser,
) -> WastResult raise ParserError {
  match self.current.token_type {
    Opcode(I32Const) => {
      let _ = self.advance()
      let token = self.advance()
      WastResult::Value(WastValue::I32(self.get_literal_text(token)))
    }
    Opcode(I64Const) => {
      let _ = self.advance()
      let token = self.advance()
      WastResult::Value(WastValue::I64(self.get_literal_text(token)))
    }
    Opcode(F32Const) => {
      let _ = self.advance()
      let token = self.advance()
      let text = self.get_literal_text(token)
      if text == "nan:canonical" {
        WastResult::F32NanCanonical
      } else if text == "nan:arithmetic" {
        WastResult::F32NanArithmetic
      } else {
        WastResult::Value(WastValue::F32(text))
      }
    }
    Opcode(F64Const) => {
      let _ = self.advance()
      let token = self.advance()
      let text = self.get_literal_text(token)
      if text == "nan:canonical" {
        WastResult::F64NanCanonical
      } else if text == "nan:arithmetic" {
        WastResult::F64NanArithmetic
      } else {
        WastResult::Value(WastValue::F64(text))
      }
    }
    Opcode(RefNull) => {
      let _ = self.advance()
      let vt : ValueType? = if self.is_heap_type_token() {
        Some(self.parse_heap_type_value_type(true))
      } else {
        None
      }
      WastResult::RefNull(vt)
    }
    Opcode(RefFunc) => {
      let _ = self.advance()
      WastResult::RefFunc
    }
    // ref.extern result
    _ => raise UnexpectedToken(self.current, "expected result value")
  }
}

///|
fn WastParser::parse_assert_return(
  self : WastParser,
) -> WastCommand raise ParserError {
  let _ = self.expect(AssertReturn, "expected 'assert_return'")
  self.expect_lpar()
  let action = self.parse_action()
  self.expect_rpar()
  let results = self.parse_wast_results()
  WastCommand::AssertReturn(action, results)
}

///|
fn WastParser::parse_assert_trap(
  self : WastParser,
) -> WastCommand raise ParserError {
  let _ = self.expect(AssertTrap, "expected 'assert_trap'")
  self.expect_lpar()

  // Can be either an action or a module
  if self.check(Invoke) || self.check(Get) {
    let action = self.parse_action()
    self.expect_rpar()
    let msg_token = self.expect(Text, "expected trap message")
    let msg = self.decode_name(self.get_text(msg_token))
    WastCommand::AssertTrap(action, msg)
  } else {
    // Module trap - treat as module definition that traps on instantiation
    let module_def = self.parse_module_def()
    self.expect_rpar()
    let msg_token = self.expect(Text, "expected trap message")
    let msg = self.decode_name(self.get_text(msg_token))
    // For module traps, we use AssertTrap with a dummy action
    // This could be refined with a separate AST variant
    WastCommand::AssertUnlinkable(module_def, msg)
  }
}

///|
fn WastParser::parse_assert_exception(
  self : WastParser,
) -> WastCommand raise ParserError {
  let _ = self.expect(AssertException, "expected 'assert_exception'")
  self.expect_lpar()
  let action = self.parse_action()
  self.expect_rpar()
  WastCommand::AssertException(action)
}

///|
fn WastParser::parse_assert_exhaustion(
  self : WastParser,
) -> WastCommand raise ParserError {
  let _ = self.expect(AssertExhaustion, "expected 'assert_exhaustion'")
  self.expect_lpar()
  let action = self.parse_action()
  self.expect_rpar()
  let msg_token = self.expect(Text, "expected exhaustion message")
  let msg = self.decode_name(self.get_text(msg_token))
  WastCommand::AssertExhaustion(action, msg)
}

///|
fn WastParser::parse_module_def(
  self : WastParser,
) -> WastModuleDef raise ParserError {
  let _ = self.expect(Module, "expected 'module'")

  // Skip optional id
  if self.current.token_type is Var {
    let _ = self.advance()
  }
  match self.current.token_type {
    Binary => {
      let _ = self.advance()
      let data = self.parse_data_strings()
      WastModuleDef::Binary(data)
    }
    Quote => {
      let _ = self.advance()
      let strings = self.parse_quote_strings()
      WastModuleDef::Quote(strings)
    }
    _ => {
      let fields : Array[ModuleField] = []
      while self.check(Lpar) || self.check(LparAnn) {
        for field in self.parse_annotated_module_fields() {
          fields.push(field)
        }
      }
      WastModuleDef::Inline({ id: None, fields, })
    }
  }
}

///|
fn WastParser::parse_assert_malformed(
  self : WastParser,
) -> WastCommand raise ParserError {
  let _ = self.expect(AssertMalformed, "expected 'assert_malformed'")
  self.expect_lpar()
  let module_def = self.parse_module_def()
  self.expect_rpar()
  let msg_token = self.expect(Text, "expected error message")
  let msg = self.decode_name(self.get_text(msg_token))
  WastCommand::AssertMalformed(module_def, msg)
}

///|
fn WastParser::parse_assert_invalid(
  self : WastParser,
) -> WastCommand raise ParserError {
  let _ = self.expect(AssertInvalid, "expected 'assert_invalid'")
  self.expect_lpar()
  let module_def = self.parse_module_def()
  self.expect_rpar()
  let msg_token = self.expect(Text, "expected error message")
  let msg = self.decode_name(self.get_text(msg_token))
  WastCommand::AssertInvalid(module_def, msg)
}

///|
fn WastParser::parse_assert_unlinkable(
  self : WastParser,
) -> WastCommand raise ParserError {
  let _ = self.expect(AssertUnlinkable, "expected 'assert_unlinkable'")
  self.expect_lpar()
  let module_def = self.parse_module_def()
  self.expect_rpar()
  let msg_token = self.expect(Text, "expected error message")
  let msg = self.decode_name(self.get_text(msg_token))
  WastCommand::AssertUnlinkable(module_def, msg)
}

///|
fn WastParser::parse_start(self : WastParser) -> Start raise ParserError {
  let _ = self.expect(Start, "expected 'start'")
  let func_index = self.parse_index()
  Start::{ func_index, }
}

///|
fn WastParser::parse_data(self : WastParser) -> DataSegment raise ParserError {
  let _ = self.expect(Data, "expected 'data'")
  let id : String? = if self.current.token_type is Var {
    let token = self.advance()
    Some(self.get_text(token))
  } else {
    None
  }

  // Memory index (optional, defaults to 0)
  let memory_index = self.try_parse_index().unwrap_or(Index::Num(0))

  // Offset expression
  let offset = if self.check(Lpar) {
    self.expect_lpar()
    if self.check(Offset) {
      let _ = self.advance()
    }
    let instrs = self.parse_instructions()
    self.expect_rpar()
    instrs
  } else {
    []
  }

  // Data string(s)
  let data_bytes = @buffer.new()
  while self.check(Text) {
    let token = self.advance()
    let text = self.get_text(token)
    let decoded = self.decode_string(text)
    data_bytes.write_bytes(decoded)
  }
  { id, memory_index, offset, data: data_bytes.to_bytes(), }
}

///|
fn WastParser::parse_elem(self : WastParser) -> ElemSegment raise ParserError {
  let _ = self.expect(Elem, "expected 'elem'")
  let id : String? = if self.current.token_type is Var {
    let token = self.advance()
    Some(self.get_text(token))
  } else {
    None
  }

  // Declarative segments share both the index abbreviation and the typed
  // expression grammar with passive segments, but have no table or offset.
  let declarative = if self.check(Declare) {
    let _ = self.advance()
    true
  } else {
    false
  }
  if declarative && !self.can_start_value_type() {
    raise UnexpectedToken(
      self.current,
      "expected func or reference type in declarative elem segment",
    )
  }
  let table_index = if declarative {
    Index::Num(0)
  } else {
    self.try_parse_index().unwrap_or(Index::Num(0))
  }
  let mut elem_type : ValueType? = None

  // Offset expression
  let offset = if declarative {
    []
  } else if self.check(Lpar) {
    let saved_state = self.save_lexer_state()
    let saved_token = self.current
    let parsed_elem_type : ValueType? = Some(self.parse_value_type()) catch {
      _ => {
        self.restore_lexer_state(saved_state)
        self.current = saved_token
        None
      }
    }
    match parsed_elem_type {
      Some(vt) => {
        elem_type = Some(vt)
        []
      }
      None => {
        self.expect_lpar()
        if self.check(Offset) {
          let _ = self.advance()
        }
        let instrs = self.parse_instructions()
        self.expect_rpar()
        instrs
      }
    }
  } else {
    []
  }

  if elem_type is None {
    if self.check(Func) {
      let _ = self.advance()
    } else if self.can_start_value_type() {
      let saved_state = self.save_lexer_state()
      let saved_token = self.current
      let parsed_type : ValueType? = Some(self.parse_value_type()) catch {
        _ => {
          self.restore_lexer_state(saved_state)
          self.current = saved_token
          None
        }
      }
      match parsed_type {
        Some(v) => elem_type = Some(v)
        None => ()
      }
    }
  }

  // Function indices and typed item expressions
  let func_indices : Array[Index] = []
  while self.current.token_type is (Nat | Var) {
    func_indices.push(self.parse_index())
  }
  let elem_exprs : Array[ElemInitExpr] = []
  while self.check(Lpar) {
    elem_exprs.push(self.parse_elem_item_instrs())
  }
  { id, declarative, table_index, offset, func_indices, elem_exprs, elem_type, }
}

// ============================================================================
// Module Parsing
// ============================================================================

///|
fn WastParser::parse_module_field(
  self : WastParser,
) -> ModuleField raise ParserError {
  if self.is_reserved_word("rec") {
    return ModuleField::RecField(self.parse_rec_group())
  }
  match self.current.token_type {
    Type => ModuleField::TypeField(self.parse_type_def())
    Func => self.parse_func()
    Tag => self.parse_tag_field()
    Table => ModuleField::TableField(self.parse_table())
    Memory => ModuleField::MemoryField(self.parse_memory())
    Global => ModuleField::GlobalField(self.parse_global())
    Export => ModuleField::ExportField(self.parse_export())
    Start => ModuleField::StartField(self.parse_start())
    Elem => ModuleField::ElemField(self.parse_elem())
    Data => ModuleField::DataField(self.parse_data())
    _ => raise UnexpectedToken(self.current, "expected module field")
  }
}

///|
fn WastParser::parse_rec_group(
  self : WastParser,
) -> Array[TypeDef] raise ParserError {
  if !self.is_reserved_word("rec") {
    raise UnexpectedToken(self.current, "expected rec")
  }
  let _ = self.advance()
  let types : Array[TypeDef] = []
  while self.check(Lpar) {
    self.expect_lpar()
    if !self.check(Type) {
      raise UnexpectedToken(self.current, "expected type")
    }
    types.push(self.parse_type_def())
    self.expect_rpar()
  }
  types
}

///|
fn WastParser::parse_v128_const(
  self : WastParser,
) -> V128Const raise ParserError {
  // v128.const can have multiple forms:
  // v128.const i8x16 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
  // v128.const i16x8 0 1 2 3 4 5 6 7
  // v128.const i32x4 0 1 2 3
  // v128.const i64x2 0 1
  // v128.const f32x4 0.0 1.0 2.0 3.0
  // v128.const f64x2 0.0 1.0

  let shape = self.parse_simd_shape()
  let count = match shape {
    I8x16 => 16
    I16x8 => 8
    I32x4 => 4
    I64x2 => 2
    F32x4 => 4
    F64x2 => 2
  }
  let values : Array[String] = []
  for i = 0; i < count; i = i + 1 {
    let token = self.advance()
    let text = match token.token_type {
      Nat | Int | Float => self.get_literal_text(token)
      _ => raise UnexpectedToken(token, "expected numeric literal")
    }
    values.push(text)
  }
  { shape, values, }
}

///|
fn WastParser::parse_simd_shape(
  self : WastParser,
) -> SimdShape raise ParserError {
  match self.current.token_type {
    SimdShapeToken(shape) => {
      let _ = self.advance()
      shape
    }
    _ =>
      raise UnexpectedToken(
        self.current,
        "expected SIMD shape (i8x16, i16x8, etc.)",
      )
  }
}

///|
fn WastParser::parse_lane_index(self : WastParser) -> UInt32 raise ParserError {
  let token = self.expect(Nat, "expected lane index")
  parse_uint(self.get_literal_text(token))
}

///|
fn WastParser::parse_shuffle_lanes(
  self : WastParser,
) -> ShuffleLanes raise ParserError {
  let lanes : FixedArray[UInt32] = FixedArray::make(16, 0U)
  for i = 0; i < 16; i = i + 1 {
    let token = self.expect(Nat, "expected shuffle lane index")
    lanes[i] = parse_uint(self.get_literal_text(token))
  }
  { lanes, }
}

///|
fn WastParser::parse_simd_mem_arg(
  self : WastParser,
  default_align : UInt64,
) -> MemArg raise ParserError {
  self.parse_mem_arg(default_align)
}

///|
fn WastParser::parse_simd_instruction(
  self : WastParser,
  op : Opcode,
) -> Instruction raise ParserError {
  match op {
    // V128 Const
    V128Const => {
      let v128_const = self.parse_v128_const()
      V128Const(v128_const)
    }

    // V128 Memory - Load
    V128Load => self.parse_memory_access(op, 16, false)
    V128Load8x8S => V128Load8x8S(self.parse_simd_mem_arg(8))
    V128Load8x8U => V128Load8x8U(self.parse_simd_mem_arg(8))
    V128Load16x4S => V128Load16x4S(self.parse_simd_mem_arg(8))
    V128Load16x4U => V128Load16x4U(self.parse_simd_mem_arg(8))
    V128Load32x2S => V128Load32x2S(self.parse_simd_mem_arg(8))
    V128Load32x2U => V128Load32x2U(self.parse_simd_mem_arg(8))
    V128Load8Splat => V128Load8Splat(self.parse_simd_mem_arg(1))
    V128Load16Splat => V128Load16Splat(self.parse_simd_mem_arg(2))
    V128Load32Splat => V128Load32Splat(self.parse_simd_mem_arg(4))
    V128Load64Splat => V128Load64Splat(self.parse_simd_mem_arg(8))
    V128Load32Zero => V128Load32Zero(self.parse_simd_mem_arg(4))
    V128Load64Zero => V128Load64Zero(self.parse_simd_mem_arg(8))
    V128Store => self.parse_memory_access(op, 16, true)

    // V128 Memory - Lane operations
    V128Load8Lane => {
      let memarg = self.parse_simd_mem_arg(1)
      let lane = self.parse_lane_index()
      V128Load8Lane(memarg, lane)
    }
    V128Load16Lane => {
      let memarg = self.parse_simd_mem_arg(2)
      let lane = self.parse_lane_index()
      V128Load16Lane(memarg, lane)
    }
    V128Load32Lane => {
      let memarg = self.parse_simd_mem_arg(4)
      let lane = self.parse_lane_index()
      V128Load32Lane(memarg, lane)
    }
    V128Load64Lane => {
      let memarg = self.parse_simd_mem_arg(8)
      let lane = self.parse_lane_index()
      V128Load64Lane(memarg, lane)
    }
    V128Store8Lane => {
      let memarg = self.parse_simd_mem_arg(1)
      let lane = self.parse_lane_index()
      V128Store8Lane(memarg, lane)
    }
    V128Store16Lane => {
      let memarg = self.parse_simd_mem_arg(2)
      let lane = self.parse_lane_index()
      V128Store16Lane(memarg, lane)
    }
    V128Store32Lane => {
      let memarg = self.parse_simd_mem_arg(4)
      let lane = self.parse_lane_index()
      V128Store32Lane(memarg, lane)
    }
    V128Store64Lane => {
      let memarg = self.parse_simd_mem_arg(8)
      let lane = self.parse_lane_index()
      V128Store64Lane(memarg, lane)
    }

    // Shuffle
    I8x16Shuffle => {
      let lanes = self.parse_shuffle_lanes()
      I8x16Shuffle(lanes)
    }
    I8x16Swizzle => I8x16Swizzle

    // Splat
    I8x16Splat => I8x16Splat
    I16x8Splat => I16x8Splat
    I32x4Splat => I32x4Splat
    I64x2Splat => I64x2Splat
    F32x4Splat => F32x4Splat
    F64x2Splat => F64x2Splat

    // Extract lane
    I8x16ExtractLaneS => I8x16ExtractLaneS(self.parse_lane_index())
    I8x16ExtractLaneU => I8x16ExtractLaneU(self.parse_lane_index())
    I16x8ExtractLaneS => I16x8ExtractLaneS(self.parse_lane_index())
    I16x8ExtractLaneU => I16x8ExtractLaneU(self.parse_lane_index())
    I32x4ExtractLane => I32x4ExtractLane(self.parse_lane_index())
    I64x2ExtractLane => I64x2ExtractLane(self.parse_lane_index())
    F32x4ExtractLane => F32x4ExtractLane(self.parse_lane_index())
    F64x2ExtractLane => F64x2ExtractLane(self.parse_lane_index())

    // Replace lane
    I8x16ReplaceLane => I8x16ReplaceLane(self.parse_lane_index())
    I16x8ReplaceLane => I16x8ReplaceLane(self.parse_lane_index())
    I32x4ReplaceLane => I32x4ReplaceLane(self.parse_lane_index())
    I64x2ReplaceLane => I64x2ReplaceLane(self.parse_lane_index())
    F32x4ReplaceLane => F32x4ReplaceLane(self.parse_lane_index())
    F64x2ReplaceLane => F64x2ReplaceLane(self.parse_lane_index())

    // Unary operations
    I8x16Abs
    | I8x16Neg
    | I8x16Popcnt
    | I16x8Abs
    | I16x8Neg
    | I32x4Abs
    | I32x4Neg
    | I64x2Abs
    | I64x2Neg
    | F32x4Abs
    | F32x4Neg
    | F32x4Sqrt
    | F32x4Ceil
    | F32x4Floor
    | F32x4Trunc
    | F32x4Nearest
    | F64x2Abs
    | F64x2Neg
    | F64x2Sqrt
    | F64x2Ceil
    | F64x2Floor
    | F64x2Trunc
    | F64x2Nearest
    | V128Not => SimdUnary(op)

    // Binary operations
    I8x16Add
    | I8x16AddSatS
    | I8x16AddSatU
    | I8x16Sub
    | I8x16SubSatS
    | I8x16SubSatU
    | I8x16MinS
    | I8x16MinU
    | I8x16MaxS
    | I8x16MaxU
    | I8x16AvgrU
    | I16x8Add
    | I16x8AddSatS
    | I16x8AddSatU
    | I16x8Sub
    | I16x8SubSatS
    | I16x8SubSatU
    | I16x8Mul
    | I16x8MinS
    | I16x8MinU
    | I16x8MaxS
    | I16x8MaxU
    | I16x8AvgrU
    | I16x8Q15mulrSatS
    | I32x4Add
    | I32x4Sub
    | I32x4Mul
    | I32x4MinS
    | I32x4MinU
    | I32x4MaxS
    | I32x4MaxU
    | I32x4DotI16x8S
    | I64x2Add
    | I64x2Sub
    | I64x2Mul
    | F32x4Add
    | F32x4Sub
    | F32x4Mul
    | F32x4Div
    | F32x4Min
    | F32x4Max
    | F32x4Pmin
    | F32x4Pmax
    | F64x2Add
    | F64x2Sub
    | F64x2Mul
    | F64x2Div
    | F64x2Min
    | F64x2Max
    | F64x2Pmin
    | F64x2Pmax
    | V128And
    | V128AndNot
    | V128Or
    | V128Xor
    | I8x16NarrowI16x8S
    | I8x16NarrowI16x8U
    | I16x8NarrowI32x4S
    | I16x8NarrowI32x4U
    | I16x8ExtmulLowI8x16S
    | I16x8ExtmulHighI8x16S
    | I16x8ExtmulLowI8x16U
    | I16x8ExtmulHighI8x16U
    | I32x4ExtmulLowI16x8S
    | I32x4ExtmulHighI16x8S
    | I32x4ExtmulLowI16x8U
    | I32x4ExtmulHighI16x8U
    | I64x2ExtmulLowI32x4S
    | I64x2ExtmulHighI32x4S
    | I64x2ExtmulLowI32x4U
    | I64x2ExtmulHighI32x4U => SimdBinary(op)

    // Ternary operations
    V128Bitselect
    | F32x4RelaxedMadd
    | F32x4RelaxedNmadd
    | F64x2RelaxedMadd
    | F64x2RelaxedNmadd
    | I8x16RelaxedLaneselect
    | I16x8RelaxedLaneselect
    | I32x4RelaxedLaneselect
    | I64x2RelaxedLaneselect => SimdTernary(op)

    // Shift operations
    I8x16Shl
    | I8x16ShrS
    | I8x16ShrU
    | I16x8Shl
    | I16x8ShrS
    | I16x8ShrU
    | I32x4Shl
    | I32x4ShrS
    | I32x4ShrU
    | I64x2Shl
    | I64x2ShrS
    | I64x2ShrU => SimdShift(op)

    // Test operations (return i32)
    V128AnyTrue
    | I8x16AllTrue
    | I8x16Bitmask
    | I16x8AllTrue
    | I16x8Bitmask
    | I32x4AllTrue
    | I32x4Bitmask
    | I64x2AllTrue
    | I64x2Bitmask => SimdTest(op)

    // Comparison operations
    I8x16Eq
    | I8x16Ne
    | I8x16LtS
    | I8x16LtU
    | I8x16GtS
    | I8x16GtU
    | I8x16LeS
    | I8x16LeU
    | I8x16GeS
    | I8x16GeU
    | I16x8Eq
    | I16x8Ne
    | I16x8LtS
    | I16x8LtU
    | I16x8GtS
    | I16x8GtU
    | I16x8LeS
    | I16x8LeU
    | I16x8GeS
    | I16x8GeU
    | I32x4Eq
    | I32x4Ne
    | I32x4LtS
    | I32x4LtU
    | I32x4GtS
    | I32x4GtU
    | I32x4LeS
    | I32x4LeU
    | I32x4GeS
    | I32x4GeU
    | I64x2Eq
    | I64x2Ne
    | I64x2LtS
    | I64x2GtS
    | I64x2LeS
    | I64x2GeS
    | F32x4Eq
    | F32x4Ne
    | F32x4Lt
    | F32x4Gt
    | F32x4Le
    | F32x4Ge
    | F64x2Eq
    | F64x2Ne
    | F64x2Lt
    | F64x2Gt
    | F64x2Le
    | F64x2Ge => SimdCompare(op)

    // Conversion operations
    I16x8ExtendLowI8x16S
    | I16x8ExtendHighI8x16S
    | I16x8ExtendLowI8x16U
    | I16x8ExtendHighI8x16U
    | I32x4ExtendLowI16x8S
    | I32x4ExtendHighI16x8S
    | I32x4ExtendLowI16x8U
    | I32x4ExtendHighI16x8U
    | I64x2ExtendLowI32x4S
    | I64x2ExtendHighI32x4S
    | I64x2ExtendLowI32x4U
    | I64x2ExtendHighI32x4U
    | I32x4TruncSatF32x4S
    | I32x4TruncSatF32x4U
    | I32x4TruncSatF64x2SZero
    | I32x4TruncSatF64x2UZero
    | F32x4ConvertI32x4S
    | F32x4ConvertI32x4U
    | F32x4DemoteF64x2Zero
    | F64x2ConvertLowI32x4S
    | F64x2ConvertLowI32x4U
    | F64x2PromoteLowF32x4 => SimdConvert(op)

    // Relaxed SIMD
    I8x16RelaxedSwizzle
    | I32x4RelaxedTruncF32x4S
    | I32x4RelaxedTruncF32x4U
    | I32x4RelaxedTruncF64x2SZero
    | I32x4RelaxedTruncF64x2UZero
    | F32x4RelaxedMin
    | F32x4RelaxedMax
    | F64x2RelaxedMin
    | F64x2RelaxedMax
    | I16x8RelaxedQ15mulrS
    | I16x8RelaxedDotI8x16I7x16S
    | I32x4RelaxedDotI8x16I7x16AddS => SimdRelaxed(op)
    _ => raise InvalidInstruction("unhandled SIMD opcode")
  }
}

///|
pub fn WastParser::parse_module(self : WastParser) -> Module raise ParserError {
  self.expect_lpar()
  let _ = self.expect(Module, "expected 'module'")
  let id : String? = if self.current.token_type is Var {
    let token = self.advance()
    Some(self.get_text(token))
  } else {
    None
  }
  let fields : Array[ModuleField] = []
  while self.check(Lpar) || self.check(LparAnn) {
    for field in self.parse_annotated_module_fields() {
      fields.push(field)
    }
  }
  self.expect_rpar()
  self.check_lexer_errors()
  { id, fields, }
}

// ============================================================================
// Utility Functions
// ============================================================================

///|
fn WastParser::strip_quotes(_ : WastParser, s : String) -> String {
  if s.length() >= 2 && s[0] == '"' && s[s.length() - 1] == '"' {
    s.sub(start=1, end=s.length() - 1).to_owned()
  } else {
    s
  }
}

///|
fn WastParser::decode_string(
  _ : WastParser,
  s : String,
) -> Bytes raise ParserError {
  let quoted = s.length() >= 2 && s[0] == '"' && s[s.length() - 1] == '"'
  let start = if quoted { 1 } else { 0 }
  let end = if quoted { s.length() - 1 } else { s.length() }
  let buf = @buffer.new(size_hint=end - start)
  let mut i = start
  while i < end {
    let run_start = i
    while i < end && s[i] != '\\' {
      i += 1
    }
    if i > run_start {
      buf.write_string_utf8(s.sub(start=run_start, end=i))
    }
    if i == end {
      break
    }
    if end - i < 2 {
      raise Custom("incomplete string escape")
    }
    match s[i + 1] {
      'n' => {
        buf.write_byte(b'\n')
        i += 2
      }
      't' => {
        buf.write_byte(b'\t')
        i += 2
      }
      'r' => {
        buf.write_byte(b'\r')
        i += 2
      }
      '"' => {
        buf.write_byte(b'"')
        i += 2
      }
      '\'' => {
        buf.write_byte(b'\'')
        i += 2
      }
      '\\' => {
        buf.write_byte(b'\\')
        i += 2
      }
      'u' => {
        if end - i < 3 || s[i + 2] != '{' {
          raise Custom("invalid Unicode escape")
        }
        i += 3
        let mut scalar = 0
        let mut has_digit = false
        while i < end && s[i] != '}' {
          if s[i] == '_' {
            if !has_digit ||
              end - i < 2 ||
              hex_val(s[i + 1].unsafe_to_char()) < 0 {
              raise Custom("invalid Unicode escape")
            }
            i += 1
            continue
          }
          let digit = hex_val(s[i].unsafe_to_char())
          if digit < 0 || scalar > (0x10ffff - digit) / 16 {
            raise Custom("invalid Unicode escape")
          }
          scalar = scalar * 16 + digit
          has_digit = true
          i += 1
        }
        if i == end || !has_digit || (scalar >= 0xd800 && scalar <= 0xdfff) {
          raise Custom("invalid Unicode escape")
        }
        buf.write_char_utf8(scalar.unsafe_to_char())
        i += 1
      }
      _ => {
        if end - i < 3 {
          raise Custom("incomplete string escape")
        }
        let h1 = hex_val(s[i + 1].unsafe_to_char())
        let h2 = hex_val(s[i + 2].unsafe_to_char())
        if h1 < 0 || h2 < 0 {
          raise Custom("invalid string escape")
        }
        buf.write_byte(((h1 << 4) | h2).to_byte())
        i += 3
      }
    }
  }
  buf.to_bytes()
}

///|
fn WastParser::decode_name(
  self : WastParser,
  s : String,
) -> String raise ParserError {
  if !s.contains("\\") {
    return self.strip_quotes(s)
  }
  @utf8.decode(self.decode_string(s)) catch {
    _ => raise Custom("invalid UTF-8 name")
  }
}

///|
fn WastParser::check_lexer_errors(self : WastParser) -> Unit raise ParserError {
  match self.lexer.get_errors().get(0) {
    Some(error) =>
      raise Custom(
        "\{error.message} at \{error.location.filename}:\{error.location.line}:\{error.location.first_column}",
      )
    None => ()
  }
}

///|
fn hex_val(c : Char) -> Int32 {
  if c >= '0' && c <= '9' {
    c.to_int() - 0x30
  } else if c >= 'a' && c <= 'f' {
    c.to_int() - 0x61 + 10
  } else if c >= 'A' && c <= 'F' {
    c.to_int() - 0x41 + 10
  } else {
    -1
  }
}

///|
pub fn WastParser::has_errors(self : WastParser) -> Bool {
  self.errors.length() > 0 || self.lexer.has_errors()
}

///|
pub fn WastParser::get_errors(self : WastParser) -> Array[ParseError] {
  self.errors
}

// ============================================================================
// Helper for lexer - make fields accessible
// ============================================================================

// Note: You'll need to make these fields pub on WastLexer if not already:
// pub mut cursor : Int
// pub mut line : Int
// pub mut line_start : Int

// ============================================================================
// Tests
// ============================================================================

///|
test "parse simple module" {
  let source =
    #|(module
    #|  (func $add (param i32 i32) (result i32)
    #|    local.get 0
    #|    local.get 1
    #|    i32.add))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let mod = parser.parse_module()
  assert_false(parser.has_errors())
  @debug.assert_eq(mod.fields.length(), 1)
}

///|
test "parse error struct can be constructed" {
  let err : ParseError = {
    location: { filename: "test", line: 1, first_column: 1, last_column: 1, },
    message: "example",
  }
  @debug.assert_eq(err.message, "example")
}

///|
test "parse function with locals" {
  let source =
    #|(module
    #|  (func $fib (param i32) (result i32)
    #|    (local i32 i32)
    #|    local.get 0
    #|    i32.const 2
    #|    i32.lt_s
    #|    if (result i32)
    #|      (then (local.get 0))
    #|      (else
    #|        (i32.add
    #|          (call $fib (i32.sub (local.get 0) (i32.const 1)))
    #|          (call $fib (i32.sub (local.get 0) (i32.const 2)))))))
    #|)
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse function with type reference" {
  let source =
    #|(module
    #|  (type $t0 (func (param i32) (result i32)))
    #|  (func $id (type $t0)
    #|    local.get 0))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let mod = parser.parse_module()
  assert_false(parser.has_errors())
  @debug.assert_eq(mod.fields.length(), 2)
}

///|
test "parse memory and data" {
  let source =
    #|(module
    #|  (memory 1)
    #|  (data (i32.const 0) "hello"))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse import and export" {
  let source =
    #|(module
    #|  (import "env" "log" (func $log (param i32)))
    #|  (export "log" (func $log)))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

// ============================================================================
// Comprehensive Test Suite for WAST Parser
// ============================================================================

// ----------------------------------------------------------------------------
// Module Structure Tests
// ----------------------------------------------------------------------------

///|
test "parse empty module" {
  let source = "(module)"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let mod = parser.parse_module()
  assert_false(parser.has_errors())
  @debug.assert_eq(mod.id, None)
  @debug.assert_eq(mod.fields.length(), 0)
}

///|
test "parse named module" {
  let source = "(module $my_module)"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let mod = parser.parse_module()
  assert_false(parser.has_errors())
  @debug.assert_eq(mod.id, Some("$my_module"))
}

// ----------------------------------------------------------------------------
// Type Definition Tests
// ----------------------------------------------------------------------------

///|
test "parse type with no params no results" {
  let source = "(module (type $t (func)))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let mod = parser.parse_module()
  assert_false(parser.has_errors())
  @debug.assert_eq(mod.fields.length(), 1)
}

///|
test "parse type with params only" {
  let source = "(module (type $t (func (param i32) (param i64))))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse type with results only" {
  let source = "(module (type $t (func (result i32))))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse type with multiple params abbreviated" {
  let source = "(module (type $t (func (param i32 i64 f32 f64))))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse type with multiple results" {
  let source = "(module (type $t (func (result i32 i64))))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse type with named params" {
  let source = "(module (type $t (func (param $a i32) (param $b i64) (result i32))))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse struct type defs with subtype metadata and fields" {
  let source =
    #|(module
    #|  (type $base (struct))
    #|  (type $node
    #|    (sub final $base
    #|      (descriptor $node_desc)
    #|      (struct
    #|        (field i32)
    #|        (field (mut (ref null $base))))))
    #|  (type $node_desc
    #|    (describes $node)
    #|    (struct (field (ref $node)))))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let mod = parser.parse_module()
  assert_false(parser.has_errors())
  match mod.fields {
    [TypeField(base), TypeField(node), TypeField(node_desc)] => {
      @debug.assert_eq(base.id, Some("$base"))
      @debug.assert_eq(base.is_sub, false)
      @debug.assert_eq(base.final_, false)
      @debug.assert_eq(base.supers, [])
      @debug.assert_eq(base.metadata, { describes: None, descriptor: None, })
      @debug.assert_eq(base.body, TypeDefBody::Struct([]))
      @debug.assert_eq(node.id, Some("$node"))
      @debug.assert_eq(node.is_sub, true)
      @debug.assert_eq(node.final_, true)
      @debug.assert_eq(node.supers, [Id("$base")])
      @debug.assert_eq(node.metadata, {
        describes: None,
        descriptor: Some(Id("$node_desc")),
      })
      @debug.assert_eq(
        node.body,
        TypeDefBody::Struct([
          { storage: Value(I32), mutable_: false, },
          {
            storage: Value(TypedFuncRef(HeapTypeRef::TypeId("$base"))),
            mutable_: true,
          },
        ]),
      )
      @debug.assert_eq(node_desc.id, Some("$node_desc"))
      @debug.assert_eq(node_desc.is_sub, false)
      @debug.assert_eq(node_desc.final_, false)
      @debug.assert_eq(node_desc.supers, [])
      @debug.assert_eq(node_desc.metadata, {
        describes: Some(Id("$node")),
        descriptor: None,
      })
      @debug.assert_eq(
        node_desc.body,
        TypeDefBody::Struct([
          {
            storage: Value(NonNullTypedFuncRef(HeapTypeRef::TypeId("$node"))),
            mutable_: false,
          },
        ]),
      )
    }
    _ => fail("expected three type fields in struct subtype parse fixture")
  }
}

///|
test "parse array type defs with subtype metadata" {
  let source =
    #|(module
    #|  (type $bytes (array i8))
    #|  (type $arr
    #|    (sub $bytes
    #|      (descriptor $arr_desc)
    #|      (array (mut (ref null $bytes)))))
    #|  (type $arr_desc
    #|    (describes $arr)
    #|    (array i16)))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let mod = parser.parse_module()
  assert_false(parser.has_errors())
  match mod.fields {
    [TypeField(bytes), TypeField(arr), TypeField(arr_desc)] => {
      @debug.assert_eq(bytes.id, Some("$bytes"))
      @debug.assert_eq(bytes.is_sub, false)
      @debug.assert_eq(
        bytes.body,
        TypeDefBody::Array({ storage: FieldStorage::I8, mutable_: false, }),
      )
      @debug.assert_eq(arr.id, Some("$arr"))
      @debug.assert_eq(arr.is_sub, true)
      @debug.assert_eq(arr.supers, [Index::Id("$bytes")])
      @debug.assert_eq(arr.metadata, {
        describes: None,
        descriptor: Some(Index::Id("$arr_desc")),
      })
      @debug.assert_eq(
        arr.body,
        TypeDefBody::Array({
          storage: FieldStorage::Value(
            TypedFuncRef(HeapTypeRef::TypeId("$bytes")),
          ),
          mutable_: true,
        }),
      )
      @debug.assert_eq(arr_desc.id, Some("$arr_desc"))
      @debug.assert_eq(arr_desc.metadata, {
        describes: Some(Index::Id("$arr")),
        descriptor: None,
      })
      @debug.assert_eq(
        arr_desc.body,
        TypeDefBody::Array({ storage: FieldStorage::I16, mutable_: false, }),
      )
    }
    _ => fail("expected three type fields in array subtype parse fixture")
  }
}

///|
test "parse rec groups with gc type defs" {
  let source =
    #|(module
    #|  (rec
    #|    (type $node
    #|      (descriptor $node_desc)
    #|      (struct (field (ref null $node))))
    #|    (type $node_desc
    #|      (describes $node)
    #|      (struct)))
    #|  (func))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let mod = parser.parse_module()
  assert_false(parser.has_errors())
  match mod.fields {
    [RecField([node, node_desc]), FuncField(_)] => {
      @debug.assert_eq(node.id, Some("$node"))
      @debug.assert_eq(node.metadata, {
        describes: None,
        descriptor: Some(Id("$node_desc")),
      })
      @debug.assert_eq(
        node.body,
        TypeDefBody::Struct([
          {
            storage: Value(TypedFuncRef(HeapTypeRef::TypeId("$node"))),
            mutable_: false,
          },
        ]),
      )
      @debug.assert_eq(node_desc.id, Some("$node_desc"))
      @debug.assert_eq(node_desc.metadata, {
        describes: Some(Id("$node")),
        descriptor: None,
      })
      @debug.assert_eq(node_desc.body, TypeDefBody::Struct([]))
    }
    _ => fail("expected rec group followed by function field")
  }
}

///|
test "parse empty rec groups" {
  let source = "(module (rec) (type (func)))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let mod = parser.parse_module()
  assert_false(parser.has_errors())
  match mod.fields {
    [RecField([]), TypeField(td)] =>
      @debug.assert_eq(td.body, TypeDefBody::Func({ params: [], results: [], }))
    _ => fail("expected empty rec group followed by plain type field")
  }
}

// ----------------------------------------------------------------------------
// Function Tests
// ----------------------------------------------------------------------------

///|
test "parse minimal function" {
  let source = "(module (func))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse named function" {
  let source = "(module (func $foo))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse function with inline export" {
  let source =
    #|(module (func $foo (export "foo")))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse function with multiple inline exports" {
  let source =
    #|(module (func $foo (export "foo") (export "bar")))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse a function with type reference" {
  let source =
    #|(module
    #|  (type $sig (func (param i32) (result i32)))
    #|  (func $f (type $sig) local.get 0))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse function with inline signature" {
  let source = "(module (func $add (param i32 i32) (result i32) local.get 0 local.get 1 i32.add))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse function with named params" {
  let source = "(module (func $add (param $a i32) (param $b i32) (result i32) local.get $a local.get $b i32.add))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse another function with locals" {
  let source = "(module (func $f (local i32) (local i64 f32)))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse function with named locals" {
  let source = "(module (func $f (local $x i32) (local $y i64)))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

// ----------------------------------------------------------------------------
// Control Flow Tests - Block
// ----------------------------------------------------------------------------

///|
test "parse empty block flat" {
  let source = "(module (func block end))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse empty block folded" {
  let source = "(module (func (block)))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse named block" {
  let source = "(module (func (block $label nop)))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse block with result type" {
  let source = "(module (func (result i32) (block (result i32) i32.const 42)))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse block flat with instructions" {
  let source = "(module (func block nop nop end))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse nested blocks" {
  let source = "(module (func (block (block (block nop)))))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

// ----------------------------------------------------------------------------
// Control Flow Tests - Loop
// ----------------------------------------------------------------------------

///|
test "parse empty loop flat" {
  let source = "(module (func loop end))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse empty loop folded" {
  let source = "(module (func (loop)))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse named loop with br" {
  let source = "(module (func (loop $continue (br $continue))))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

// ----------------------------------------------------------------------------
// Control Flow Tests - If
// ----------------------------------------------------------------------------

///|
test "parse if flat no else" {
  let source = "(module (func (param i32) local.get 0 if nop end))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse if flat with else" {
  let source = "(module (func (param i32) local.get 0 if nop else nop end))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse if folded condition only" {
  let source = "(module (func (param i32) (if (local.get 0) (then nop))))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse if folded with else" {
  let source = "(module (func (param i32) (if (local.get 0) (then nop) (else nop))))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse if with result type" {
  let source = "(module (func (param i32) (result i32) (if (result i32) (local.get 0) (then (i32.const 1)) (else (i32.const 0)))))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse nested if" {
  let source =
    #|(module (func (param i32 i32)
    #|  (if (local.get 0)
    #|    (then
    #|      (if (local.get 1)
    #|        (then nop)
    #|        (else nop)))
    #|    (else nop))))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

// ----------------------------------------------------------------------------
// Control Flow Tests - Branch
// ----------------------------------------------------------------------------

///|
test "parse br" {
  let source = "(module (func (block $exit (br $exit))))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse br with numeric index" {
  let source = "(module (func (block (br 0))))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse br_if" {
  let source = "(module (func (param i32) (block $exit (local.get 0) (br_if $exit))))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse br_table" {
  let source = "(module (func (param i32) (block $a (block $b (local.get 0) (br_table $a $b $a)))))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse return" {
  let source = "(module (func return))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

// ----------------------------------------------------------------------------
// Call Tests
// ----------------------------------------------------------------------------

///|
test "parse call" {
  let source = "(module (func $f) (func (call $f)))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse call with numeric index" {
  let source = "(module (func) (func (call 0)))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse call_indirect" {
  let source =
    #|(module
    #|  (type $sig (func (param i32) (result i32)))
    #|  (table 1 funcref)
    #|  (func (param i32) (result i32)
    #|    local.get 0
    #|    i32.const 0
    #|    call_indirect (type $sig)))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

// ----------------------------------------------------------------------------
// Numeric Instruction Tests
// ----------------------------------------------------------------------------

///|
test "parse i32 const" {
  let source = "(module (func (result i32) i32.const 42))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse i32 const negative" {
  let source = "(module (func (result i32) i32.const -42))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse i32 const hex" {
  let source = "(module (func (result i32) i32.const 0x2a))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse i64 const" {
  let source = "(module (func (result i64) i64.const 9223372036854775807))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse f32 const" {
  let source = "(module (func (result f32) f32.const 3.14))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse f64 const" {
  let source = "(module (func (result f64) f64.const 3.141592653589793))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse i32 arithmetic" {
  let source = "(module (func (param i32 i32) (result i32) local.get 0 local.get 1 i32.add))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse i32 arithmetic folded" {
  let source = "(module (func (param i32 i32) (result i32) (i32.add (local.get 0) (local.get 1))))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse i32 all binary ops" {
  let source =
    #|(module (func (param i32 i32) (result i32)
    #|  (i32.add (local.get 0) (local.get 1))
    #|  (i32.sub (local.get 0) (local.get 1))
    #|  (i32.mul (local.get 0) (local.get 1))
    #|  (i32.div_s (local.get 0) (local.get 1))
    #|  (i32.div_u (local.get 0) (local.get 1))
    #|  (i32.rem_s (local.get 0) (local.get 1))
    #|  (i32.rem_u (local.get 0) (local.get 1))
    #|  (i32.and (local.get 0) (local.get 1))
    #|  (i32.or (local.get 0) (local.get 1))
    #|  (i32.xor (local.get 0) (local.get 1))
    #|  (i32.shl (local.get 0) (local.get 1))
    #|  (i32.shr_s (local.get 0) (local.get 1))
    #|  (i32.shr_u (local.get 0) (local.get 1))
    #|  (i32.rotl (local.get 0) (local.get 1))
    #|  (i32.rotr (local.get 0) (local.get 1))
    #|  drop drop drop drop drop drop drop drop drop drop drop drop drop drop
    #|  i32.const 0))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse i32 unary ops" {
  let source =
    #|(module (func (param i32) (result i32)
    #|  (i32.clz (local.get 0))
    #|  (i32.ctz (local.get 0))
    #|  (i32.popcnt (local.get 0))
    #|  drop drop
    #|  i32.const 0))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse i32 comparison ops" {
  let source =
    #|(module (func (param i32 i32) (result i32)
    #|  (i32.eq (local.get 0) (local.get 1))
    #|  (i32.ne (local.get 0) (local.get 1))
    #|  (i32.lt_s (local.get 0) (local.get 1))
    #|  (i32.lt_u (local.get 0) (local.get 1))
    #|  (i32.gt_s (local.get 0) (local.get 1))
    #|  (i32.gt_u (local.get 0) (local.get 1))
    #|  (i32.le_s (local.get 0) (local.get 1))
    #|  (i32.le_u (local.get 0) (local.get 1))
    #|  (i32.ge_s (local.get 0) (local.get 1))
    #|  (i32.ge_u (local.get 0) (local.get 1))
    #|  drop drop drop drop drop drop drop drop drop
    #|  i32.const 0))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse i32 eqz" {
  let source = "(module (func (param i32) (result i32) (i32.eqz (local.get 0))))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse f32 ops" {
  let source =
    #|(module (func (param f32 f32) (result f32)
    #|  (f32.add (local.get 0) (local.get 1))
    #|  (f32.sub (local.get 0) (local.get 1))
    #|  (f32.mul (local.get 0) (local.get 1))
    #|  (f32.div (local.get 0) (local.get 1))
    #|  (f32.min (local.get 0) (local.get 1))
    #|  (f32.max (local.get 0) (local.get 1))
    #|  (f32.copysign (local.get 0) (local.get 1))
    #|  drop drop drop drop drop drop
    #|  f32.const 0))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse f32 unary ops" {
  let source =
    #|(module (func (param f32) (result f32)
    #|  (f32.abs (local.get 0))
    #|  (f32.neg (local.get 0))
    #|  (f32.ceil (local.get 0))
    #|  (f32.floor (local.get 0))
    #|  (f32.trunc (local.get 0))
    #|  (f32.nearest (local.get 0))
    #|  (f32.sqrt (local.get 0))
    #|  drop drop drop drop drop drop
    #|  f32.const 0))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

// ----------------------------------------------------------------------------
// Conversion Instruction Tests
// ----------------------------------------------------------------------------

///|
test "parse i32 wrap i64" {
  let source = "(module (func (param i64) (result i32) (i32.wrap_i64 (local.get 0))))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse i64 extend i32" {
  let source =
    #|(module (func (param i32) (result i64)
    #|  (i64.extend_i32_s (local.get 0))
    #|  (i64.extend_i32_u (local.get 0))
    #|  drop
    #|  i64.const 0))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse float conversions" {
  let source =
    #|(module
    #|  (func (param i32) (result f32) (f32.convert_i32_s (local.get 0)))
    #|  (func (param i32) (result f32) (f32.convert_i32_u (local.get 0)))
    #|  (func (param f64) (result f32) (f32.demote_f64 (local.get 0)))
    #|  (func (param f32) (result f64) (f64.promote_f32 (local.get 0))))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse reinterpret" {
  let source =
    #|(module
    #|  (func (param f32) (result i32) (i32.reinterpret_f32 (local.get 0)))
    #|  (func (param i32) (result f32) (f32.reinterpret_i32 (local.get 0)))
    #|  (func (param f64) (result i64) (i64.reinterpret_f64 (local.get 0)))
    #|  (func (param i64) (result f64) (f64.reinterpret_i64 (local.get 0))))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse trunc sat" {
  let source =
    #|(module
    #|  (func (param f32) (result i32) (i32.trunc_sat_f32_s (local.get 0)))
    #|  (func (param f32) (result i32) (i32.trunc_sat_f32_u (local.get 0)))
    #|  (func (param f64) (result i32) (i32.trunc_sat_f64_s (local.get 0)))
    #|  (func (param f64) (result i32) (i32.trunc_sat_f64_u (local.get 0))))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse sign extension" {
  let source =
    #|(module (func (param i32 i64)
    #|  (i32.extend8_s (local.get 0))
    #|  (i32.extend16_s (local.get 0))
    #|  (i64.extend8_s (local.get 1))
    #|  (i64.extend16_s (local.get 1))
    #|  (i64.extend32_s (local.get 1))
    #|  drop drop drop drop drop))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

// ----------------------------------------------------------------------------
// Variable Instruction Tests
// ----------------------------------------------------------------------------

///|
test "parse local get set tee" {
  let source =
    #|(module (func (param i32) (local i32)
    #|  (local.get 0)
    #|  (local.set 1)
    #|  (local.tee 1 (i32.const 42))
    #|  drop))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse global get set" {
  let source =
    #|(module
    #|  (global $g (mut i32) (i32.const 0))
    #|  (func
    #|    (global.get $g)
    #|    (global.set $g)
    #|    (global.get 0)
    #|    (global.set 0)))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

// ----------------------------------------------------------------------------
// Memory Instruction Tests
// ----------------------------------------------------------------------------

///|
test "parse memory load store" {
  let source =
    #|(module
    #|  (memory 1)
    #|  (func (param i32)
    #|    (i32.load (local.get 0))
    #|    (i32.store (local.get 0))
    #|    (i64.load (local.get 0))
    #|    (i64.store (local.get 0))
    #|    drop drop))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse memory load with offset and align" {
  let source =
    #|(module
    #|  (memory 1)
    #|  (func (param i32) (result i32)
    #|    (i32.load offset=4 align=4 (local.get 0))))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse memory load partial" {
  let source =
    #|(module
    #|  (memory 1)
    #|  (func (param i32)
    #|    (i32.load8_s (local.get 0))
    #|    (i32.load8_u (local.get 0))
    #|    (i32.load16_s (local.get 0))
    #|    (i32.load16_u (local.get 0))
    #|    (i64.load8_s (local.get 0))
    #|    (i64.load8_u (local.get 0))
    #|    (i64.load16_s (local.get 0))
    #|    (i64.load16_u (local.get 0))
    #|    (i64.load32_s (local.get 0))
    #|    (i64.load32_u (local.get 0))
    #|    drop drop drop drop drop drop drop drop drop drop))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse memory store partial" {
  let source =
    #|(module
    #|  (memory 1)
    #|  (func (param i32 i64)
    #|    (i32.store8 (local.get 0) (local.get 0))
    #|    (i32.store16 (local.get 0) (local.get 0))
    #|    (i64.store8 (local.get 0) (local.get 1))
    #|    (i64.store16 (local.get 0) (local.get 1))
    #|    (i64.store32 (local.get 0) (local.get 1))))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse memory size and grow" {
  let source =
    #|(module
    #|  (memory 1)
    #|  (func (result i32)
    #|    memory.size
    #|    (memory.grow (i32.const 1))
    #|    drop))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse memory fill copy" {
  let source =
    #|(module
    #|  (memory 1)
    #|  (func
    #|    (memory.fill (i32.const 0) (i32.const 0) (i32.const 100))
    #|    (memory.copy (i32.const 0) (i32.const 100) (i32.const 50))))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse memory init and data drop" {
  let source =
    #|(module
    #|  (memory 1)
    #|  (data $d (i32.const 0) "hello")
    #|  (func
    #|    (memory.init $d (i32.const 0) (i32.const 0) (i32.const 5))
    #|    (data.drop $d)))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

// ----------------------------------------------------------------------------
// Table Instruction Tests
// ----------------------------------------------------------------------------

///|
test "parse table get set" {
  let source =
    #|(module
    #|  (table 1 funcref)
    #|  (func (param i32)
    #|    (table.get (local.get 0))
    #|    (table.set (local.get 0))
    #|    drop))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse table size grow fill" {
  let source =
    #|(module
    #|  (table 1 funcref)
    #|  (func
    #|    (table.size)
    #|    (table.grow (ref.null func) (i32.const 1))
    #|    drop drop))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse table copy init" {
  let source =
    #|(module
    #|  (table $t 10 funcref)
    #|  (elem $e (i32.const 0) $f)
    #|  (func $f)
    #|  (func
    #|    (table.copy (i32.const 0) (i32.const 5) (i32.const 5))
    #|    (table.init $e (i32.const 0) (i32.const 0) (i32.const 1))
    #|    (elem.drop $e)))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

// ----------------------------------------------------------------------------
// Reference Instruction Tests
// ----------------------------------------------------------------------------

///|
test "parse ref null" {
  let source = "(module (func (result funcref) (ref.null func)))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse ref is null" {
  let source = "(module (func (param funcref) (result i32) (ref.is_null (local.get 0))))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse ref func" {
  let source =
    #|(module
    #|  (func $f)
    #|  (func (result funcref) (ref.func $f)))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse ref get desc" {
  let source = "(module (func (param funcref) (result i32) (ref.get_desc 0 (local.get 0))))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse ref test desc and cast desc eq" {
  let source =
    #|(module
    #|  (func (param funcref) (result i32)
    #|    (drop (ref.test_desc 0 (local.get 0)))
    #|    (drop (ref.test_desc_null 0 (local.get 0)))
    #|    (drop (ref.cast_desc_eq 0 (local.get 0)))
    #|    (drop (ref.cast_desc_eq_null 0 (local.get 0)))
    #|    (i32.const 0)))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse struct allocation instruction families" {
  let source =
    #|(module
    #|  (func
    #|    (drop (struct.new 0))
    #|    (drop (struct.new_default 0))
    #|    (drop (struct.new_desc 0 (ref.null none)))
    #|    (drop (struct.new_default_desc 0 (ref.null none)))))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let mod = parser.parse_module()
  assert_false(parser.has_errors())
  match mod.fields {
    [FuncField(func)] =>
      @debug.assert_eq(func.body, [
        StructNew(Num(0U)),
        Drop,
        StructNewDefault(Num(0U)),
        Drop,
        RefNull(TypedFuncRef(HeapTypeRef::None_)),
        StructNewDesc(Num(0U)),
        Drop,
        RefNull(TypedFuncRef(HeapTypeRef::None_)),
        StructNewDefaultDesc(Num(0U)),
        Drop,
      ])
    _ =>
      fail("expected single function field in struct allocation parse fixture")
  }
}

///|
test "parse tags with throw and throw_ref" {
  let source =
    #|(module
    #|  (type $void (func))
    #|  (tag $e0 (type $void))
    #|  (func
    #|    (throw $e0)
    #|    throw_ref))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse try_table catch forms" {
  let source =
    #|(module
    #|  (type $void (func))
    #|  (tag $e0 (type $void))
    #|  (func
    #|    (block $h
    #|      (try_table (catch $e0 $h)
    #|        (throw $e0))
    #|      (try_table (catch_ref $e0 $h)
    #|        throw_ref)
    #|      (try_table (catch_all $h)
    #|        unreachable)
    #|      (try_table (catch_all_ref $h)
    #|        unreachable))))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse legacy try do catch forms" {
  let source =
    #|(module
    #|  (tag $e0)
    #|  (func
    #|    (try
    #|      (do (throw $e0))
    #|      (catch $e0)))
    #|  (func
    #|    (try (result i32)
    #|      (do (throw $e0) (i32.const 0))
    #|      (catch_all (i32.const 1)))))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse legacy delegate and rethrow forms" {
  let source =
    #|(module
    #|  (tag $e0)
    #|  (func
    #|    (try $outer
    #|      (do
    #|        (try
    #|          (do (throw $e0))
    #|          (delegate $outer)))
    #|      (catch $e0)))
    #|  (func
    #|    (try
    #|      (do (throw $e0))
    #|      (catch $e0 (rethrow 0)))))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse function annotations on defined functions" {
  let source =
    #|(module
    #|  (@binaryen.idempotent)
    #|  (@metadata.code.inline "\00")
    #|  (func $f))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let mod = parser.parse_module()
  assert_false(parser.has_errors())
  match mod.fields {
    [FuncField(func)] =>
      @debug.assert_eq(func.annotations, [
        { name: "binaryen.idempotent", args: [], },
        { name: "metadata.code.inline", args: ["\"\\00\""], },
      ])
    _ => fail("expected single annotated function field")
  }
}

///|
test "parse function annotations on func imports" {
  let source =
    #|(module
    #|  (@binaryen.js.called)
    #|  (import "env" "f" (func $f)))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let mod = parser.parse_module()
  assert_false(parser.has_errors())
  match mod.fields {
    [ImportField(im)] => {
      @debug.assert_eq(im.annotations, [
        { name: "binaryen.js.called", args: [], },
      ])
      match im.desc {
        Func(_) => ()
        _ => fail("expected function import descriptor")
      }
    }
    _ => fail("expected single annotated import field")
  }
}

///|
test "parse typed ref forms in signatures" {
  let source =
    #|(module
    #|  (type $t (func))
    #|  (func $dummy)
    #|  (elem declare func $dummy)
    #|
    #|  (tag $e (param (ref $t)))
    #|  (func $throw (throw $e (ref.func $dummy)))
    #|
    #|  (func (export "catch") (result (ref null $t))
    #|    (block $l (result (ref null $t))
    #|      (try_table (catch $e $l) (call $throw))
    #|      unreachable))
    #|  (func (export "catch_ref") (result (ref null $t))
    #|    (block $l (result (ref null $t) (ref exn))
    #|      (try_table (catch_ref $e $l) (call $throw))
    #|      unreachable)
    #|    drop))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse typed-heap value types preserve concrete type references" {
  let source =
    #|(module
    #|  (type $t (func))
    #|  (func (param (ref $t)) (result (ref null $t))
    #|    (ref.null $t)))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let mod = parser.parse_module()
  assert_false(parser.has_errors())
  match mod.fields {
    [TypeField(_), FuncField(func)] => {
      @debug.assert_eq(func.type_use.func_type.params, [
        NonNullTypedFuncRef(HeapTypeRef::TypeId("$t")),
      ])
      @debug.assert_eq(func.type_use.func_type.results, [
        TypedFuncRef(HeapTypeRef::TypeId("$t")),
      ])
      match func.body {
        [RefNull(vt)] =>
          @debug.assert_eq(vt, TypedFuncRef(HeapTypeRef::TypeId("$t")))
        _ => fail("expected single ref.null instruction in typed-heap fixture")
      }
    }
    _ => fail("expected type + function fields in typed-heap parse fixture")
  }
}

///|
test "parse exact typed-heap value types preserve exact concrete references" {
  let source =
    #|(module
    #|  (type $t (func))
    #|  (func (param (ref (exact $t))) (result (ref (exact $t)))
    #|    (local.get 0)))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let mod = parser.parse_module()
  assert_false(parser.has_errors())
  match mod.fields {
    [TypeField(_), FuncField(func)] => {
      @debug.assert_eq(func.type_use.func_type.params, [
        NonNullTypedFuncRef(HeapTypeRef::ExactTypeId("$t")),
      ])
      @debug.assert_eq(func.type_use.func_type.results, [
        NonNullTypedFuncRef(HeapTypeRef::ExactTypeId("$t")),
      ])
      match func.body {
        [LocalGet(Num(0U))] => ()
        _ =>
          fail(
            "expected single local.get instruction in exact typed-heap fixture",
          )
      }
    }
    _ =>
      fail("expected type + function fields in exact typed-heap parse fixture")
  }
}

///|
test "parse abstract heap ref forms preserve non-func heap kinds" {
  let source =
    #|(module
    #|  (func
    #|    (param (ref any) (ref eq) (ref i31) (ref none) (ref nofunc) (ref noextern) (ref noexn))
    #|    (result (ref null any) (ref null eq) (ref null i31) (ref null none) (ref null nofunc) (ref null noextern) (ref null noexn))
    #|    (ref.null any)
    #|    (ref.null eq)
    #|    (ref.null i31)
    #|    (ref.null none)
    #|    (ref.null nofunc)
    #|    (ref.null noextern)
    #|    (ref.null noexn)))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let mod = parser.parse_module()
  assert_false(parser.has_errors())
  match mod.fields {
    [FuncField(func)] => {
      @debug.assert_eq(func.type_use.func_type.params, [
        NonNullTypedFuncRef(HeapTypeRef::Any),
        NonNullTypedFuncRef(HeapTypeRef::Eq),
        NonNullTypedFuncRef(HeapTypeRef::I31),
        NonNullTypedFuncRef(HeapTypeRef::None_),
        NonNullTypedFuncRef(HeapTypeRef::NoFunc),
        NonNullTypedFuncRef(HeapTypeRef::NoExtern),
        NonNullTypedFuncRef(HeapTypeRef::NoExn),
      ])
      @debug.assert_eq(func.type_use.func_type.results, [
        TypedFuncRef(HeapTypeRef::Any),
        TypedFuncRef(HeapTypeRef::Eq),
        TypedFuncRef(HeapTypeRef::I31),
        TypedFuncRef(HeapTypeRef::None_),
        TypedFuncRef(HeapTypeRef::NoFunc),
        TypedFuncRef(HeapTypeRef::NoExtern),
        TypedFuncRef(HeapTypeRef::NoExn),
      ])
      @debug.assert_eq(func.body, [
        RefNull(TypedFuncRef(HeapTypeRef::Any)),
        RefNull(TypedFuncRef(HeapTypeRef::Eq)),
        RefNull(TypedFuncRef(HeapTypeRef::I31)),
        RefNull(TypedFuncRef(HeapTypeRef::None_)),
        RefNull(TypedFuncRef(HeapTypeRef::NoFunc)),
        RefNull(TypedFuncRef(HeapTypeRef::NoExtern)),
        RefNull(TypedFuncRef(HeapTypeRef::NoExn)),
      ])
    }
    _ =>
      fail("expected single function field in abstract heap-ref parse fixture")
  }
}

///|
test "parse legacy gc reference aliases and abstract struct array refs" {
  let source =
    #|(module
    #|  (table 1 anyref)
    #|  (func
    #|    (param anyref eqref i31ref structref arrayref nullref)
    #|    (result anyref structref arrayref nullref)
    #|    (ref.null struct)
    #|    (ref.null array)))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let mod = parser.parse_module()
  assert_false(parser.has_errors())
  match mod.fields {
    [TableField(table), FuncField(func)] => {
      @debug.assert_eq(
        table.table_type.elem_type,
        TypedFuncRef(HeapTypeRef::Any),
      )
      @debug.assert_eq(func.type_use.func_type.params, [
        TypedFuncRef(HeapTypeRef::Any),
        TypedFuncRef(HeapTypeRef::Eq),
        TypedFuncRef(HeapTypeRef::I31),
        TypedFuncRef(HeapTypeRef::Struct),
        TypedFuncRef(HeapTypeRef::Array),
        TypedFuncRef(HeapTypeRef::None_),
      ])
      @debug.assert_eq(func.type_use.func_type.results, [
        TypedFuncRef(HeapTypeRef::Any),
        TypedFuncRef(HeapTypeRef::Struct),
        TypedFuncRef(HeapTypeRef::Array),
        TypedFuncRef(HeapTypeRef::None_),
      ])
      @debug.assert_eq(func.body, [
        RefNull(TypedFuncRef(HeapTypeRef::Struct)),
        RefNull(TypedFuncRef(HeapTypeRef::Array)),
      ])
    }
    _ => fail("expected table + function fields in legacy alias parse fixture")
  }
}

// ----------------------------------------------------------------------------
// Parametric Instruction Tests
// ----------------------------------------------------------------------------

///|
test "parse drop" {
  let source = "(module (func (i32.const 42) drop))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse select" {
  let source = "(module (func (param i32) (result i32) (select (i32.const 1) (i32.const 2) (local.get 0))))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse typed select" {
  let source = "(module (func (param i32) (result i32) (select (result i32) (i32.const 1) (i32.const 2) (local.get 0))))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

// ----------------------------------------------------------------------------
// Memory Definition Tests
// ----------------------------------------------------------------------------

///|
test "parse memory min only" {
  let source = "(module (memory 1))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse memory min max" {
  let source = "(module (memory 1 10))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse named memory" {
  let source = "(module (memory $mem 1))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse memory with inline export" {
  let source =
    #|(module (memory $mem (export "memory") 1))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

// ----------------------------------------------------------------------------
// Table Definition Tests
// ----------------------------------------------------------------------------

///|
test "parse table min only" {
  let source = "(module (table 1 funcref))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse table min max" {
  let source = "(module (table 1 10 funcref))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse named table" {
  let source = "(module (table $tbl 1 funcref))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse table with inline export" {
  let source =
    #|(module (table $tbl (export "table") 1 funcref))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse table elem-abbreviation with function ids" {
  let source =
    #|(module
    #|  (func $f)
    #|  (table funcref (elem $f)))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse table elem-abbreviation with numeric function indices" {
  let source =
    #|(module
    #|  (func)
    #|  (func)
    #|  (table funcref (elem 0 1)))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse table elem-abbreviation with explicit limits" {
  let source =
    #|(module
    #|  (func $a)
    #|  (func $b)
    #|  (table 3 funcref (elem $a $b)))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse table elem-abbreviation with func keyword and non-zero offset" {
  let source =
    #|(module
    #|  (func $f)
    #|  (table 4 funcref (elem (i32.const 2) func $f)))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse table elem-abbreviation with typed ref.func items" {
  let source =
    #|(module
    #|  (func $f)
    #|  (table 2 funcref (elem funcref (ref.func $f) (item (ref.func $f)))))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse table elem-abbreviation with typed ref.null items" {
  let source =
    #|(module
    #|  (func $f)
    #|  (table 2 funcref (elem funcref (item (ref.null func)) (item (ref.func $f)))))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

// ----------------------------------------------------------------------------
// Global Definition Tests
// ----------------------------------------------------------------------------

///|
test "parse immutable global" {
  let source = "(module (global i32 (i32.const 42)))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse mutable global" {
  let source = "(module (global (mut i32) (i32.const 42)))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse named global" {
  let source = "(module (global $g (mut i32) (i32.const 0)))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse global with inline export" {
  let source =
    #|(module (global $g (export "global") (mut i32) (i32.const 0)))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse global all types" {
  let source =
    #|(module
    #|  (global i32 (i32.const 0))
    #|  (global i64 (i64.const 0))
    #|  (global f32 (f32.const 0))
    #|  (global f64 (f64.const 0)))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

// ----------------------------------------------------------------------------
// Import Tests
// ----------------------------------------------------------------------------

///|
test "parse import func" {
  let source =
    #|(module (import "env" "log" (func $log (param i32))))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse import func with type" {
  let source =
    #|(module
    #|  (type $sig (func (param i32)))
    #|  (import "env" "log" (func $log (type $sig))))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse import memory" {
  let source =
    #|(module (import "env" "memory" (memory 1)))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse import table" {
  let source =
    #|(module (import "env" "table" (table 1 funcref)))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse import global immutable" {
  let source =
    #|(module (import "env" "g" (global i32)))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse import global mutable" {
  let source =
    #|(module (import "env" "g" (global (mut i32))))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

// ----------------------------------------------------------------------------
// Export Tests
// ----------------------------------------------------------------------------

///|
test "parse export func" {
  let source =
    #|(module (func $f) (export "f" (func $f)))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse export func by index" {
  let source =
    #|(module (func) (export "f" (func 0)))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse export memory" {
  let source =
    #|(module (memory 1) (export "mem" (memory 0)))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse export table" {
  let source =
    #|(module (table 1 funcref) (export "tbl" (table 0)))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse export global" {
  let source =
    #|(module (global i32 (i32.const 0)) (export "g" (global 0)))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

// ----------------------------------------------------------------------------
// Start Function Tests
// ----------------------------------------------------------------------------

///|
test "parse start by name" {
  let source = "(module (func $main) (start $main))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse start by index" {
  let source = "(module (func) (start 0))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

// ----------------------------------------------------------------------------
// Data Segment Tests
// ----------------------------------------------------------------------------

///|
test "parse data segment" {
  let source =
    #|(module (memory 1) (data (i32.const 0) "hello"))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse data segment with offset keyword" {
  let source =
    #|(module (memory 1) (data (offset (i32.const 0)) "hello"))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse named data segment" {
  let source =
    #|(module (memory 1) (data $d (i32.const 0) "hello"))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse data segment multiple strings" {
  let source =
    #|(module (memory 1) (data (i32.const 0) "hello" " " "world"))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse data segment with escapes" {
  let source =
    #|(module (memory 1) (data (i32.const 0) "hello\nworld\00"))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

// ----------------------------------------------------------------------------
// Element Segment Tests
// ----------------------------------------------------------------------------

///|
test "parse elem segment" {
  let source = "(module (table 1 funcref) (func $f) (elem (i32.const 0) $f))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse elem segment with offset keyword" {
  let source = "(module (table 1 funcref) (func $f) (elem (offset (i32.const 0)) $f))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse named elem segment" {
  let source = "(module (table 1 funcref) (func $f) (elem $e (i32.const 0) $f))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse elem segment multiple funcs" {
  let source = "(module (table 3 funcref) (func $a) (func $b) (func $c) (elem (i32.const 0) $a $b $c))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse elem segment by index" {
  let source = "(module (table 1 funcref) (func) (elem (i32.const 0) 0))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse elem segment with typed ref.null item expressions" {
  let source =
    #|(module
    #|  (table 1 funcref)
    #|  (func $f)
    #|  (elem (i32.const 0) funcref (item (ref.null func)) (item (ref.func $f))))|
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

// ----------------------------------------------------------------------------
// Complex Expression Tests
// ----------------------------------------------------------------------------

///|
test "parse deeply nested folded expressions" {
  let source =
    #|(module (func (result i32)
    #|  (i32.add
    #|    (i32.mul
    #|      (i32.const 2)
    #|      (i32.const 3))
    #|    (i32.sub
    #|      (i32.const 10)
    #|      (i32.const 4)))))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse mixed flat and folded" {
  let source =
    #|(module (func (param i32) (result i32)
    #|  local.get 0
    #|  (i32.add (i32.const 1))
    #|  i32.const 2
    #|  i32.mul))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse fibonacci recursive" {
  let source =
    #|(module
    #|  (func $fib (param $n i32) (result i32)
    #|    (if (result i32) (i32.lt_s (local.get $n) (i32.const 2))
    #|      (then (local.get $n))
    #|      (else
    #|        (i32.add
    #|          (call $fib (i32.sub (local.get $n) (i32.const 1)))
    #|          (call $fib (i32.sub (local.get $n) (i32.const 2))))))))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse factorial iterative" {
  let source =
    #|(module
    #|  (func $fact (param $n i32) (result i32)
    #|    (local $result i32)
    #|    (local.set $result (i32.const 1))
    #|    (block $exit
    #|      (loop $loop
    #|        (br_if $exit (i32.le_s (local.get $n) (i32.const 1)))
    #|        (local.set $result (i32.mul (local.get $result) (local.get $n)))
    #|        (local.set $n (i32.sub (local.get $n) (i32.const 1)))
    #|        (br $loop)))
    #|    (local.get $result)))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

// ----------------------------------------------------------------------------
// Complete Module Tests
// ----------------------------------------------------------------------------

///|
test "parse complete module with all sections" {
  let source =
    #|(module $complete
    #|  ;; Types
    #|  (type $unary (func (param i32) (result i32)))
    #|  (type $binary (func (param i32 i32) (result i32)))
    #|
    #|  ;; Imports
    #|  (import "env" "log" (func $log (param i32)))
    #|  (import "env" "memory" (memory $mem 1))
    #|
    #|  ;; Table
    #|  (table $tbl 2 funcref)
    #|
    #|  ;; Globals
    #|  (global $counter (mut i32) (i32.const 0))
    #|  (global $max i32 (i32.const 100))
    #|
    #|  ;; Functions
    #|  (func $inc (type $unary)
    #|    (i32.add (local.get 0) (i32.const 1)))
    #|
    #|  (func $add (type $binary)
    #|    (i32.add (local.get 0) (local.get 1)))
    #|
    #|  (func $main
    #|    (call $log (call $inc (i32.const 41))))
    #|
    #|  ;; Element segment
    #|  (elem (i32.const 0) $inc $add)
    #|
    #|  ;; Data segment
    #|  (data (i32.const 0) "Hello, World!")
    #|
    #|  ;; Exports
    #|  (export "inc" (func $inc))
    #|  (export "add" (func $add))
    #|  (export "table" (table $tbl))
    #|  (export "counter" (global $counter))
    #|
    #|  ;; Start
    #|  (start $main))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let mod = parser.parse_module()
  assert_false(parser.has_errors())
  @debug.assert_eq(mod.id, Some("$complete"))
}

// ----------------------------------------------------------------------------
// Edge Case Tests
// ----------------------------------------------------------------------------

///|
test "parse hex integers" {
  let source = "(module (func (result i32) i32.const 0xDEADBEEF))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse underscore in numbers" {
  let source = "(module (func (result i32) i32.const 1_000_000))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse empty block in if" {
  let source = "(module (func (param i32) (if (local.get 0) (then) (else))))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse unreachable and nop" {
  let source = "(module (func unreachable) (func nop))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse many fields in module" {
  let source =
    #|(module
    #|  (func) (func) (func) (func) (func)
    #|  (func) (func) (func) (func) (func))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let mod = parser.parse_module()
  assert_false(parser.has_errors())
  @debug.assert_eq(mod.fields.length(), 10)
}

///|
test "parse function with many params" {
  let source = "(module (func (param i32 i32 i32 i32 i32 i32 i32 i32)))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse f32 special values" {
  let source =
    #|(module
    #|  (func (result f32) f32.const inf)
    #|  (func (result f32) f32.const -inf)
    #|  (func (result f32) f32.const nan))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse f64 special values" {
  let source =
    #|(module
    #|  (func (result f64) f64.const inf)
    #|  (func (result f64) f64.const -inf)
    #|  (func (result f64) f64.const nan))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

// ----------------------------------------------------------------------------
// If Statement Variations
// ----------------------------------------------------------------------------

///|
test "parse if flat form simple" {
  let source =
    #|(module (func (param i32)
    #|  local.get 0
    #|  if
    #|    nop
    #|  end))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse if flat form with else" {
  let source =
    #|(module (func (param i32)
    #|  local.get 0
    #|  if
    #|    nop
    #|  else
    #|    nop
    #|  end))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse if flat form with result" {
  let source =
    #|(module (func (param i32) (result i32)
    #|  local.get 0
    #|  if (result i32)
    #|    i32.const 1
    #|  else
    #|    i32.const 0
    #|  end))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse if folded with condition" {
  let source = "(module (func (param i32) (if (local.get 0) (then nop))))"
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse if folded complex condition" {
  let source =
    #|(module (func (param i32 i32)
    #|  (if (i32.and (local.get 0) (local.get 1))
    #|    (then nop)
    #|    (else nop))))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse if inside block" {
  let source =
    #|(module (func (param i32)
    #|  (block
    #|    (if (local.get 0)
    #|      (then (br 1))))))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
test "parse if inside loop" {
  let source =
    #|(module (func (param i32)
    #|  (loop $continue
    #|    (if (local.get 0)
    #|      (then (br $continue))))))
  let lexer = make_lexer(source)
  let parser = WastParser::new(lexer)
  let _ = parser.parse_module()
  assert_false(parser.has_errors())
}

///|
pub impl Show for FuncType with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for TypeUse with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for Annotation with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for InlineExport with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for ImportDesc with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for Import with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for ExportDesc with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for Export with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for TableType with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for MemoryType with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for GlobalType with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for Local with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for Func with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for Global with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for Table with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for Memory with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for Tag with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for FieldStorage with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for StructFieldDef with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for TypeDefBody with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for TypeDefMetadata with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for TypeDef with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for DataSegment with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for ElemSegment with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for ElemInitExpr with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for Start with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for ModuleField with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for MemArg with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for BlockType with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for V128Const with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for LegacyCatchClause with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for Instruction with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for WastValue with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for WastResult with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for WastAction with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for WastActionType with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for WastCommand with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for WastModuleDef with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
pub impl Show for ParseError with fn output(self, logger) {
  logger.write_string(@debug.to_string(self))
}

///|
fn WastParser::parse_memory_access(
  self : WastParser,
  op : Opcode,
  align : UInt64,
  store : Bool,
) -> Instruction raise ParserError {
  let mut array_type : Index? = None
  if self.check(Lpar) {
    let saved_state = self.save_lexer_state()
    let saved_token = self.current
    self.expect_lpar()
    if self.check(Type) {
      ignore(self.advance())
      array_type = Some(self.parse_index())
      self.expect_rpar()
    } else {
      self.restore_lexer_state(saved_state)
      self.current = saved_token
    }
  }
  let ma = self.parse_mem_arg(align)
  match array_type {
    Some(ti) =>
      if store {
        ArrayStore(op, ti, ma)
      } else {
        ArrayLoad(op, ti, ma)
      }
    None =>
      match op {
        V128Load => V128Load(ma)
        V128Store => V128Store(ma)
        _ => if store { Store(op, ma) } else { Load(op, ma) }
      }
  }
}

///|
fn WastParser::parse_atomic_order_pair(
  self : WastParser,
) -> AtomicOrder raise ParserError {
  let first = self.try_parse_atomic_order().unwrap_or(SeqCst)
  let second = self.try_parse_atomic_order().unwrap_or(SeqCst)
  if first != second {
    raise UnexpectedToken(
      self.current,
      "aggregate atomic memory orders must be identical",
    )
  }
  first
}

///|
fn WastParser::parse_memory_type(
  self : WastParser,
) -> MemoryType raise ParserError {
  let memory64 = self.current.token_type == ValueType(I64)
  if memory64 || self.current.token_type == ValueType(I32) {
    let _ = self.advance()
  }
  if !self.check(Nat) {
    raise UnexpectedToken(self.current, "expected minimum limit")
  }
  let min = self.parse_memory_limit(memory64)
  let max : UInt64? = if self.check(Nat) {
    Some(self.parse_memory_limit(memory64))
  } else {
    None
  }
  let shared = self.is_reserved_word("shared")
  if shared {
    let _ = self.advance()
  }
  MemoryType::new(min, max~, shared~, memory64~)
}

///|
fn WastParser::parse_memory_limit(
  self : WastParser,
  memory64 : Bool,
) -> UInt64 raise ParserError {
  let token = self.expect(Nat, "expected memory limit")
  let text = self.get_literal_text(token)
  let hex = text.has_prefix("0x") || text.has_prefix("0X")
  let radix = if hex { 16UL } else { 10UL }
  let limit = if memory64 { 18446744073709551615UL } else { 4294967295UL }
  let mut value = 0UL
  for i = (if hex { 2 } else { 0 }); i < text.length(); i = i + 1 {
    let c = text[i]
    if c == '_' {
      continue
    }
    let digit = if c >= '0' && c <= '9' {
      (c.to_int() - 48).to_uint64()
    } else if c >= 'a' && c <= 'f' {
      (c.to_int() - 87).to_uint64()
    } else if c >= 'A' && c <= 'F' {
      (c.to_int() - 55).to_uint64()
    } else {
      raise UnexpectedToken(token, "invalid memory limit")
    }
    if digit >= radix || value > (limit - digit) / radix {
      raise UnexpectedToken(token, "memory limit exceeds address width")
    }
    value = value * radix + digit
  }
  value
}