// The WebAssembly type family, shared between the Wax and binary forms.
//
// Ported from the `Make_types` functor in wax/src/lib-wasm/ast.mli. OCaml
// parameterises it over three things -- the index representation and two array
// wrappers -- and instantiates it once per language form. MoonBit has no
// functors, so the parameterisation becomes a type parameter.
//
// THE SEAM. Only `Idx` is generic here. The reference itself draws exactly this
// line: it has two mappers, `Map_types_spine` covering the idx-carrying types
// (heaptype, reftype, valtype, storagetype, fieldtype) and `Map_types`
// extending to functype/comptype/subtype/rectype, whose array wrappers differ
// per instance and must be supplied by the caller. So the spine lives here,
// generic over Idx, and the four wrapper-carrying types are declared in each
// instance's own package.
//
// Wax instantiates at `Idx = Ident` -- a NAME with a span, because Wax refers to
// types by name. The binary form the code generator will need instantiates at
// `Idx = Int`. Keeping the parameter is what makes that later instance an
// addition rather than a rewrite; see AGENTS.md.

///|
/// A heap type: what a reference can point at.
///
/// The `No*` cases are the bottom types of each hierarchy (`nofunc` is a
/// subtype of every function type and has no values), and `None_` is the bottom
/// of the internal hierarchy. `Type` names a concrete defined type; `Exact`
/// names one and forbids subtypes, which is the `custom-descriptors` proposal's
/// `&!t`.
pub(all) enum HeapType[Idx] {
  Func
  NoFunc
  Exn
  NoExn
  Cont
  NoCont
  Extern
  NoExtern
  Any
  Eq
  I31
  Struct
  Array
  /// Spelled `none`. Named with a trailing underscore in the reference because
  /// `None` collides with the option constructor; kept here for the same reason.
  None_
  Type(Idx)
  Exact(Idx)
} derive(Eq, Hash, Debug)

///|
/// The keyword naming a heap type, or None for the two that carry an index.
///
/// The index-carrying cases have no keyword because each printer spells the
/// index its own way -- by name in Wax, by number in the binary form.
pub fn[Idx] HeapType::keyword(self : HeapType[Idx]) -> String? {
  match self {
    Func => Some("func")
    NoFunc => Some("nofunc")
    Exn => Some("exn")
    NoExn => Some("noexn")
    Cont => Some("cont")
    NoCont => Some("nocont")
    Extern => Some("extern")
    NoExtern => Some("noextern")
    Any => Some("any")
    Eq => Some("eq")
    I31 => Some("i31")
    Struct => Some("struct")
    Array => Some("array")
    None_ => Some("none")
    Type(_) => None
    Exact(_) => None
  }
}

///|
/// A reference type: a heap type plus whether null is allowed.
pub(all) struct RefType[Idx] {
  nullable : Bool
  typ : HeapType[Idx]
} derive(Eq, Hash, Debug)

///|
/// A value type -- what a local, parameter or stack slot holds.
pub(all) enum ValType[Idx] {
  I32
  I64
  F32
  F64
  V128
  Ref(RefType[Idx])
} derive(Eq, Hash, Debug)

///|
/// A packed storage type. These exist only in struct fields and arrays: there
/// is no i8 or i16 on the operand stack, so they are not value types.
pub(all) enum PackedType {
  I8
  I16
} derive(Eq, Hash, Debug)

///|
/// What a struct field or array element stores.
pub(all) enum StorageType[Idx] {
  Value(ValType[Idx])
  Packed(PackedType)
} derive(Eq, Hash, Debug)

///|
/// A type paired with its mutability. Used for both globals and struct fields,
/// which is why it is generic in the thing being made mutable.
pub(all) struct MutType[T] {
  mut_ : Bool
  typ : T
} derive(Eq, Hash, Debug)

///|
/// A struct field or array element type.
pub type FieldType[Idx] = MutType[StorageType[Idx]]

///|
/// A global's type.
pub type GlobalType[Idx] = MutType[ValType[Idx]]

///|
/// The size bounds of a memory or table.
///
/// `page_size_log2` is the base-2 logarithm of a custom page size (the
/// `custom-page-sizes` proposal); None means the default 65536. It is stored as
/// the logarithm rather than the size because that is what the binary format
/// encodes, so round-tripping cannot lose a non-power-of-two spelling.
///
/// `mi`/`ma` are u64 to cover 64-bit memories. MoonBit's UInt64 is used
/// directly where the reference has its own Uint64 module.
pub(all) struct Limits {
  mi : UInt64
  ma : UInt64?
  address_type : AddressType
  page_size_log2 : Int?
  shared : Bool
} derive(Eq, Debug)

///|
/// Whether a memory or table is indexed by i32 or i64 (`memory64`).
pub(all) enum AddressType {
  I32
  I64
} derive(Eq, Debug)

///|
/// A 128-bit vector literal, kept as its shape plus the raw component strings.
///
/// The components stay unparsed for the same reason numeric literals do: the
/// printer has to reproduce what was written, and a v128 can be spelled in six
/// different shapes.
pub(all) struct V128 {
  shape : V128Shape
  components : Array[String]
} derive(Eq, Debug)

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

///|
pub fn V128Shape::to_str(self : V128Shape) -> String {
  match self {
    I8x16 => "i8x16"
    I16x8 => "i16x8"
    I32x4 => "i32x4"
    I64x2 => "i64x2"
    F32x4 => "f32x4"
    F64x2 => "f64x2"
  }
}

///|
/// Signedness, where WebAssembly needs an operation to state it.
pub(all) enum Signage {
  Signed
  Unsigned
} derive(Eq, Debug)

///|
/// Comparison operators available inside a conditional-compilation condition.
pub(all) enum CmpOp {
  Eq
  Ne
  Lt
  Gt
  Le
  Ge
} derive(Eq, Debug)

///|
/// A conditional-compilation condition, from `#[if(...)]`.
///
/// Evaluated against the `-D` variables, not against program values: it decides
/// which source survives, so it belongs to the type layer rather than the
/// instruction layer.
pub(all) enum Cond {
  /// A variable, written `$name`.
  Var(@basic.Annotated[String, @basic.Location])
  Str(@basic.Annotated[Bytes, @basic.Location])
  Version(Int, Int, Int)
  And(Array[Cond])
  Or(Array[Cond])
  Not(Cond)
  Cmp(CmpOp, Cond, Cond)
} derive(Eq, Debug)