// The Wax instruction tree.
//
// Ported from wax/src/lib-wax/ast.mli. Generic over `Info`, the annotation each
// node carries: the parser produces `Info = Location`, and the type checker --
// which will be ported later -- re-annotates the same tree with inferred types.
// See AGENTS.md for why that parameter is kept even though only one instance
// exists today.
//
// A block's body is `Annotated[Array[Instr[Info]], Location]`, not a bare
// array: the LIST carries its own `{ ... }` span, which the trivia layer needs
// in order to attach a comment written inside an otherwise-empty block.

///|
/// A statement or expression. Wax is expression-oriented, so there is no
/// separate statement type.
pub(all) enum InstrDesc[Info] {
  // -- structured control flow ---------------------------------------------
  Block(label~ : Label?, typ~ : FuncType, block~ : Body[Info])
  Loop(label~ : Label?, typ~ : FuncType, block~ : Body[Info])
  /// `step` is a Zig-style continue-expression: a statement run at the end of
  /// every iteration, INCLUDING one reached by `continue` (a branch to the loop
  /// label). Without it, a `continue` would skip the increment.
  While(
    label~ : Label?,
    cond~ : Instr[Info],
    step~ : Instr[Info]?,
    block~ : Body[Info]
  )
  If(
    label~ : Label?,
    typ~ : FuncType,
    cond~ : Instr[Info],
    if_block~ : Body[Info],
    else_block~ : Body[Info]?
  )
  TryTable(
    label~ : Label?,
    typ~ : FuncType,
    catches~ : Array[Catch],
    block~ : Body[Info]
  )
  /// The deprecated legacy exception handler (`try_legacy`), compiling to the
  /// legacy try/catch instructions.
  Try(
    label~ : Label?,
    typ~ : FuncType,
    block~ : Body[Info],
    catches~ : Array[(Ident, Body[Info])],
    catch_all~ : Body[Info]?
  )
  /// The structured `try { ... } catch { tag => { ... } ... }`, lowered to
  /// try_table plus a block ladder. Arms are honest trailing code in clause
  /// order: an arm's completion falls into the next arm, the last arm's into
  /// the join, and the body's completion escapes past all arms.
  TryCatch(
    label~ : Label?,
    typ~ : FuncType,
    block~ : Body[Info],
    arms~ : Array[TryCatchArm[Info]]
  )

  // -- leaves ---------------------------------------------------------------
  Unreachable
  Nop
  /// `_` in expression position: a hole, which the typer fills or reports.
  Hole
  Null
  Get(Ident)
  /// A qualified name `namespace::member`, the callee of a built-in intrinsic
  /// such as `i64::add128`.
  Path(Ident, Ident)

  // -- assignment -----------------------------------------------------------
  /// Assignment to a local or global. The middle field is the compound operator:
  /// `None` for `x = e`, `Some(op)` for `x op= e`. It is preserved through
  /// typing and lowering so the form round-trips in both directions rather than
  /// being normalised to a get/op/set.
  ///
  /// A discarded value (`_ = e`) is NOT a Set but an anonymous Let.
  Set(Ident, Annotated[BinOp, Location]?, Instr[Info])
  /// `x := e` -- assign and yield the value.
  Tee(Ident, Instr[Info])

  // -- calls ----------------------------------------------------------------
  Call(Instr[Info], Array[Instr[Info]])
  TailCall(Instr[Info], Array[Instr[Info]])
  /// A labelled call argument `name: expr`, used for the static
  /// `offset`/`align`/`lane` immediates of a memory access.
  Labelled(Ident, Instr[Info])

  // -- literals -------------------------------------------------------------
  Char(Char)
  /// A string literal, optionally prefixed by a type (`t#"..."`).
  Str(Ident?, Bytes)
  /// Numeric literals stay RAW TEXT all the way through the front end. That is
  /// what lets the printer reproduce `0x1_0` as written, and what the typer's
  /// flexible-literal inference will need.
  Int(String)
  Float(String)

  // -- casts and tests ------------------------------------------------------
  Cast(Instr[Info], CastType)
  CastDesc(Instr[Info], Bool, Instr[Info])
  Test(Instr[Info], RefType)
  /// `e!` -- assert non-null.
  NonNull(Instr[Info])

  // -- aggregates -----------------------------------------------------------
  /// A field's value is `None` when written in the punning shorthand `{x}`,
  /// abbreviating `{x: x}`. The distinction is kept so the printer can put it
  /// back the way it was written.
  Struct(Ident?, Array[(Ident, Instr[Info]?)])
  StructDefault(Ident?)
  StructDesc(Instr[Info], Array[(Ident, Instr[Info]?)])
  StructDefaultDesc(Instr[Info])
  StructGet(Instr[Info], Ident)
  GetDescriptor(Instr[Info])
  StructSet(Instr[Info], Ident, Instr[Info])
  Array(Ident?, Instr[Info], Instr[Info])
  ArrayDefault(Ident?, Instr[Info])
  ArrayFixed(Ident?, Array[Instr[Info]])
  ArraySegment(Ident?, Ident, Instr[Info], Instr[Info])
  ArrayGet(Instr[Info], Instr[Info])
  ArraySet(Instr[Info], Instr[Info], Instr[Info])

  // -- operators ------------------------------------------------------------
  BinOpI(Annotated[BinOp, Location], Instr[Info], Instr[Info])
  UnOpI(Annotated[UnOp, Location], Instr[Info])

  // -- bindings -------------------------------------------------------------
  /// `let` binds several names at once (a tuple pattern). A name is `None` for
  /// a discarded binding (`_`), and the type is `None` when inferred.
  Let(Array[(Ident?, ValType?)], Instr[Info]?)

  // -- branches -------------------------------------------------------------
  Br(Label, Instr[Info]?)
  BrIf(Label, Instr[Info])
  BrTable(Array[Label], Instr[Info])
  Dispatch(
    index~ : Instr[Info],
    cases~ : Array[Label],
    default~ : Label,
    arms~ : Array[(Label, Body[Info])]
  )
  Match(
    scrutinee~ : Instr[Info],
    arms~ : Array[(MatchPattern, Body[Info])],
    default~ : Body[Info]
  )
  BrOnNull(Label, Instr[Info])
  BrOnNonNull(Label, Instr[Info])
  BrOnCast(Label, RefType, Instr[Info])
  BrOnCastFail(Label, RefType, Instr[Info])
  BrOnCastDescEq(Label, Bool, Instr[Info], Instr[Info])
  BrOnCastDescEqFail(Label, Bool, Instr[Info], Instr[Info])

  // -- exceptions -----------------------------------------------------------
  Throw(Ident, Array[Instr[Info]])
  ThrowRef(Instr[Info])

  // -- stack switching ------------------------------------------------------
  ContNew(Ident, Instr[Info])
  ContBind(Ident, Ident, Array[Instr[Info]])
  Suspend(Ident, Array[Instr[Info]])
  Resume(Ident, Array[OnClause], Array[Instr[Info]])
  ResumeThrow(Ident, Ident, Array[OnClause], Array[Instr[Info]])
  ResumeThrowRef(Ident, Array[OnClause], Array[Instr[Info]])
  Switch(Ident, Ident, Array[Instr[Info]])
  /// The postfix handler clause `e on [t -> 'l, ...]` AS PARSED. The typer folds
  /// it into the Resume/ResumeThrow/ResumeThrowRef it wraps; the printer prints
  /// this surface form, which is why it survives in the tree.
  On(Instr[Info], Array[OnClause])

  // -- misc -----------------------------------------------------------------
  Return(Instr[Info]?)
  Sequence(Array[Instr[Info]])
  /// `c ? a : b`.
  Select(Instr[Info], Instr[Info], Instr[Info])
  /// A conditional-compilation group, `#[if(c)] { ... } #[else] { ... }`.
  ///
  /// This is not LALR-pairable (it is the dangling-else problem), so the parser
  /// produces markers for each brace group and a post-pass folds adjacent ones
  /// into this node. See parser.mbty.
  IfAnnotation(cond~ : Cond, then_body~ : Body[Info], else_body~ : Body[Info]?)
} derive(Eq, Debug)

///|
/// A braced block of instructions together with the span of its braces.
pub type Body[Info] = Annotated[Array[Instr[Info]], Location]

///|
/// An instruction node.
///
/// `hints` and `expected` are fields rather than wrapper nodes so the pervasive
/// matches on `desc` neither see them nor have to see through them.
pub(all) struct Instr[Info] {
  desc : InstrDesc[Info]
  info : Info
  /// Advisory `metadata.code.*` metadata (`#[likely]`, `#[freq = 16]`).
  hints : Hints
  /// The type this node's value MUST have, when a producer knows it
  /// independently of Wax inference.
  ///
  /// Nothing in the source language sets this and nothing user-visible reads
  /// it: only the wasm-to-Wax decompiler records the type the original opcode
  /// stated, so the typer can catch an expression it would otherwise re-infer
  /// at a different width -- silently changing the opcode on recompile. A
  /// parsed module always leaves it `None`.
  ///
  /// Kept even though this port never populates it, because omitting it would
  /// force an AST change when the typer lands.
  expected : ValType?
} derive(Eq, Debug)

///|
/// One arm of a structured `try ... catch`.
pub(all) struct TryCatchArm[Info] {
  /// `None` for the trailing catch-all.
  arm_tag : Ident?
  /// A `&` arm, where the `&exn` is delivered above the payload.
  arm_ref : Bool
  /// The arm's entry stack: the tag's payload, plus the `&exn` for a `&` arm.
  ///
  /// EMPTY as parsed -- the typer fills it, and the lowering back to wasm reads
  /// it. Present here for the same reason as `Instr::expected`.
  arm_types : Array[ValType]
  arm_body : Body[Info]
} derive(Eq, Debug)

///|
/// Build an instruction with a span and no hints.
pub fn build(desc : InstrDesc[Location], loc : Location) -> Instr[Location] {
  { desc, info: loc, hints: no_hints, expected: None }
}

///|
/// A synthesized instruction: no source span, no hints.
pub fn no_loc_instr(desc : InstrDesc[Location]) -> Instr[Location] {
  build(desc, @basic.dummy_loc)
}