// Operators, sets, calls and casts.

///|
/// A binary operator, with its signedness taken from the operands' types.
fn Lowering::binary(
  self : Lowering,
  op : @wap.BinOp,
  a : @wap.Node,
  b : @wap.Node,
  sp : @wap.Span,
) -> @ast.Instr[@basic.Location] {
  let at = self.loc(sp)
  let lt = match self.type_of(a) {
    Some(t) => Some(t)
    None => self.type_of(b)
  }
  // Set arithmetic: `+` is union, `-` is difference, `&` is intersection.
  if self.set_type(lt) is Some(_) {
    return self.set_binary(op, a, b, lt, sp)
  }
  let sign = self.signage(lt)
  let wop : @ast.BinOp = match op {
    Add => Add
    Sub => Sub
    Mul => Mul
    Div => Div(sign)
    Rem =>
      match sign {
        Some(s) => Rem(s)
        None => {
          self.error("`%` is an integer operator", sp)
          Rem(Signed)
        }
      }
    Shl => Shl
    Shr =>
      match sign {
        Some(s) => Shr(s)
        None => {
          self.error("`>>` is an integer operator", sp)
          Shr(Signed)
        }
      }
    BitAnd => And
    BitOr => Or
    BitXor => Xor
    Eq => Eq
    Ne => Ne
    Lt => Lt(sign)
    Gt => Gt(sign)
    Le => Le(sign)
    Ge => Ge(sign)
  }
  @ast.build(
    BinOpI({ desc: wop, info: at, }, self.expr(a, lt), self.expr(b, lt)),
    at,
  )
}

///|
/// The enumeration a set type is over, if the type is a set.
fn Lowering::set_type(self : Lowering, t : @wap.Type?) -> String? {
  match t {
    Some(Named(n)) =>
      match self.resolve(n) {
        Some(SetOf(e)) => Some(e)
        _ => None
      }
    _ => None
  }
}

///|
/// `+`, `-` and `&` on sets, which are the masks they lower to.
fn Lowering::set_binary(
  self : Lowering,
  op : @wap.BinOp,
  a : @wap.Node,
  b : @wap.Node,
  t : @wap.Type?,
  sp : @wap.Span,
) -> @ast.Instr[@basic.Location] {
  let at = self.loc(sp)
  let l = self.expr(a, t)
  let r = self.expr(b, t)
  match op {
    Add | BitOr => @ast.build(BinOpI({ desc: Or, info: at, }, l, r), at)
    BitAnd => @ast.build(BinOpI({ desc: And, info: at, }, l, r), at)
    BitXor => @ast.build(BinOpI({ desc: Xor, info: at, }, l, r), at)
    Sub => {
      // Wax's `!` is `eqz`, not a bitwise complement, so the complement is a
      // xor with all ones -- which is what the printed expansion shows.
      let ones = @ast.build(Int("-1"), at)
      let comp = @ast.build(BinOpI({ desc: Xor, info: at, }, r, ones), at)
      @ast.build(BinOpI({ desc: And, info: at, }, l, comp), at)
    }
    Eq => @ast.build(BinOpI({ desc: Eq, info: at, }, l, r), at)
    Ne => @ast.build(BinOpI({ desc: Ne, info: at, }, l, r), at)
    _ => {
      self.error(
        "a set supports `+` (union), `-` (difference), `&` (intersection), `^`, `==` and `in`",
        sp,
      )
      l
    }
  }
}

///|
/// `{a, b}` as a bitmask.
fn Lowering::set_literal(
  self : Lowering,
  items : Array[@wap.Node],
  expected : @wap.Type?,
  sp : @wap.Span,
) -> @ast.Instr[@basic.Location] {
  let at = self.loc(sp)
  let mut constant = 0
  let dynamic = []
  for it in items {
    match self.ordinal_of(it, expected) {
      Some(k) => constant = constant | (1 << k)
      None => {
        // A set element that is not a literal: `1 << ord(x)`.
        let one = @ast.build(Int("1"), at)
        dynamic.push(
          @ast.build(
            BinOpI({ desc: Shl, info: at, }, one, self.expr(it, None)),
            at,
          ),
        )
      }
    }
  }
  let mut acc = @ast.build(Int(constant.to_string()), at)
  for d in dynamic {
    acc = @ast.build(BinOpI({ desc: Or, info: at, }, acc, d), at)
  }
  acc
}

///|
/// The ordinal of a set element written as an enumerator or an integer.
fn Lowering::ordinal_of(
  self : Lowering,
  e : @wap.Node,
  expected : @wap.Type?,
) -> Int? {
  match e.it {
    Var(n) => {
      let owner = match self.set_type(expected) {
        Some(o) => Some(o)
        None => self.member_owner.get(self.qualify(n))
      }
      match owner {
        Some(o) =>
          match self.enum_members.get(o) {
            Some(ms) => ms.get(n)
            None => None
          }
        None => None
      }
    }
    Int(s) => int_of(s)
    _ => None
  }
}

///|
/// `x in s`, which is one mask and one comparison.
fn Lowering::in_set(
  self : Lowering,
  x : @wap.Node,
  s : @wap.Node,
  sp : @wap.Span,
) -> @ast.Instr[@basic.Location] {
  let at = self.loc(sp)
  let stype = self.type_of(s)
  let set = self.expr(s, stype)
  let mask = match self.ordinal_of(x, stype) {
    Some(k) => @ast.build(Int((1 << k).to_string()), at)
    None =>
      @ast.build(
        BinOpI(
          { desc: Shl, info: at, },
          @ast.build(Int("1"), at),
          self.expr(x, None),
        ),
        at,
      )
  }
  let anded = @ast.build(BinOpI({ desc: And, info: at, }, set, mask), at)
  @ast.build(
    BinOpI({ desc: Ne, info: at, }, anded, @ast.build(Int("0"), at)),
    at,
  )
}

///|
/// `e as t`.
fn Lowering::cast(
  self : Lowering,
  inner : @wap.Node,
  t : @wap.Type,
  sp : @wap.Span,
) -> @ast.Instr[@basic.Location] {
  let at = self.loc(sp)
  let value = self.expr(inner, None)
  let from = self.type_of(inner)
  // Widening a packed or narrow integer needs the signedness Wax makes you
  // write; wap has it in the source type.
  let widening = match (from, self.underlying(t)) {
    (Some(f), I32) | (Some(f), I64) => self.is_packed(f)
    _ => false
  }
  // `x as i32_u` -- Wax's spelling, kept because there is no shorter way to
  // say "widen this, treating it as unsigned" when the source type does not
  // already say so.
  match t {
    Named(n) =>
      match signed_cast(n) {
        Some((nt, signage)) =>
          return @ast.build(
            Cast(value, Signed(typ=nt, signage~, strict=false)),
            at,
          )
        None => ()
      }
    _ => ()
  }
  if widening {
    let signage = match self.signage(from) {
      Some(s) => s
      None => @wasm_types.Signage::Unsigned
    }
    let nt : @ast.NumType = match self.underlying(t) {
      I64 | U64 => I64
      _ => I32
    }
    return @ast.build(Cast(value, Signed(typ=nt, signage~, strict=false)), at)
  }
  @ast.build(Cast(value, Value(self.valtype(t, sp))), at)
}

///|
/// A method call: an array intrinsic, or a function whose first parameter is
/// the receiver.
fn Lowering::method_call(
  self : Lowering,
  recv : @wap.Node,
  name : String,
  args : Array[@wap.Node],
  sp : @wap.Span,
) -> @ast.Instr[@basic.Location] {
  let at = self.loc(sp)
  // `hashing.i32_value(x)` is a call into another module, not a method.
  match self.module_ref(recv, name) {
    Some(q) => {
      self.check_visible(q, sp)
      let out = []
      for a in args {
        out.push(self.expr(a, None))
      }
      return @ast.build(Call(self.var_ref(q, sp), out), at)
    }
    None => ()
  }
  // The array intrinsics keep Wax's spelling, except that `len` is shorter
  // than `length` and is the only one worth renaming.
  let intrinsic = match name {
    "len" => Some("length")
    "copy" | "fill" | "init" => Some(name)
    _ => None
  }
  match intrinsic {
    Some(wax_name) => {
      let callee = @ast.build(
        StructGet(self.expr(recv, None), self.ident(wax_name, sp)),
        at,
      )
      let out = []
      for a in args {
        out.push(self.expr(a, None))
      }
      return @ast.build(Call(callee, out), at)
    }
    None => ()
  }
  let record = match self.type_of(recv) {
    Some(t) => self.record_name(t)
    None => None
  }
  // A method on something that is not a record with that method is Wax's --
  // `x.rotl(15)`, `m.load8(p)`, `f.to_bits()`. Wap does not carry a table of
  // them: it emits the shape Wax reads as a method call and lets Wax resolve
  // it, so every intrinsic Wax gains is one wap gains.
  let known = match record {
    Some(r) => self.lookup_method(r, name) is Some(_)
    None => false
  }
  if !known {
    let callee = @ast.build(
      StructGet(self.expr(recv, None), self.ident(name, sp)),
      at,
    )
    let out = []
    for a in args {
      out.push(self.expr(a, None))
    }
    return @ast.build(Call(callee, out), at)
  }
  match record {
    Some(r) => {
      let target = match self.method_owners.get(name) {
        // More than one type implements it, so the call goes through the
        // generated dispatcher for the receiver's static type.
        Some(owners) =>
          if owners.length() > 1 && self.has_subtype(r) {
            self.mangle_method(r, name + "__dyn")
          } else {
            match self.owner_of_method(r, name) {
              Some(o) => self.mangle_method(o, name)
              None => self.mangle_method(r, name)
            }
          }
        None => self.mangle_method(r, name)
      }
      let out = [self.expr(recv, None)]
      for a in args {
        out.push(self.expr(a, None))
      }
      @ast.build(Call(@ast.build(Get(self.ident(target, sp)), at), out), at)
    }
    None => @ast.build(Unreachable, at)
  }
}

///|
/// The nearest ancestor of `record` that defines the method.
fn Lowering::owner_of_method(
  self : Lowering,
  record : String,
  name : String,
) -> String? {
  if self.methods.contains(self.qualify(record) + "." + name) {
    return Some(record)
  }
  match self.resolve(record) {
    Some(Record(parent=Some(p), ..)) => self.owner_of_method(p, name)
    _ => None
  }
}

///|
/// True when some record extends this one.
fn Lowering::has_subtype(self : Lowering, record : String) -> Bool {
  let q = self.qualify(record)
  for _, def in self.types {
    if def is Record(parent=Some(p), ..) && p == q {
      return true
    }
  }
  false
}

///|
/// The Wax `as i32_u` family.
///
/// The float targets are here for the same reason the integer ones are: each
/// name is one instruction and the signedness picks it. `f64.convert_i64_s`
/// and `f64.convert_i64_u` read the same bits and answer different numbers, so
/// there is nothing for wap to infer -- the source type says how the integer
/// is signed, not how the conversion should read it. Going the other way,
/// `x as i32_s` on a float is `i32.trunc_f32_s`, which is why the integer
/// names already covered the truncations.
fn signed_cast(name : String) -> (@ast.NumType, @wasm_types.Signage)? {
  match name {
    "i32_s" => Some((I32, Signed))
    "i32_u" => Some((I32, Unsigned))
    "i64_s" => Some((I64, Signed))
    "i64_u" => Some((I64, Unsigned))
    "f32_s" => Some((F32, Signed))
    "f32_u" => Some((F32, Unsigned))
    "f64_s" => Some((F64, Signed))
    "f64_u" => Some((F64, Unsigned))
    _ => None
  }
}

///|
/// An integer literal in any radix.
fn int_of(text : String) -> Int? {
  Some(@string.parse_int(text)) catch {
    _ => None
  }
}