// Reading a literal's value out of its source spelling.
//
// Ported from the constant-folding of wax/src/lib-conversion/to_wasm.ml.
//
// The literals reach here as the TEXT the author wrote, which is deliberate: a
// float's exact bit pattern survives only in its spelling, and re-deriving it
// from a parsed double would lose a NaN payload that the module may depend on.
// So the conversion happens once, here, at the point the bytes are chosen.

///|
/// A wax integer literal as an `Int64`, underscores ignored.
///
/// Read UNSIGNED on a signed-parse failure, because a hex literal past 2^63 is
/// written as a bit pattern rather than a negative number, and that pattern is
/// what the encoder emits.
fn parse_i64(s : String, loc : @basic.Location) -> Int64 raise LowerError {
  let cleaned = s.split("_").join("")
  @string.parse_int64(cleaned) catch {
    _ =>
      @string.parse_uint64(cleaned).reinterpret_as_int64() catch {
        _ => raise Unresolved("integer literal", loc)
      }
  }
}

///|
/// The same, truncated to the 32 bits an `i32.const` carries.
fn parse_i32(s : String, loc : @basic.Location) -> Int raise LowerError {
  parse_i64(s, loc).to_int()
}

///|
/// A readable name for an instruction, for the "not lowered" report.
fn construct_name(d : @ast.InstrDesc[@typing_env.InferredAnnotation]) -> String {
  match d {
    Block(..) | Loop(..) => "block"
    While(..) => "while"
    If(..) => "if"
    TryTable(..) => "try_table"
    Try(..) | TryCatch(..) => "try"
    Dispatch(..) => "dispatch"
    Match(..) => "match"
    Hole => "hole"
    Null => "null"
    Path(_, _) => "qualified name"
    Call(_, _) => "call"
    TailCall(_, _) => "tail call"
    Labelled(_, _) => "labelled argument"
    Str(_, _) => "string literal"
    Cast(_, _) | CastDesc(_, _, _) => "cast"
    Test(_, _) => "type test"
    NonNull(_) => "non-null assertion"
    Struct(_, _) | StructDefault(_) | StructDesc(_, _) | StructDefaultDesc(_) =>
      "struct construction"
    StructGet(_, _) | StructSet(_, _, _) | GetDescriptor(_) => "field access"
    Array(_, _, _)
    | ArrayDefault(_, _)
    | ArrayFixed(_, _)
    | ArraySegment(_, _, _, _) => "array construction"
    ArrayGet(_, _) | ArraySet(_, _, _) => "array access"
    BrTable(_, _) => "br_table"
    BrOnNull(_, _) | BrOnNonNull(_, _) => "br_on_null"
    BrOnCast(_, _, _) | BrOnCastFail(_, _, _) => "br_on_cast"
    BrOnCastDescEq(_, _, _, _) | BrOnCastDescEqFail(_, _, _, _) =>
      "br_on_cast_desc"
    Throw(_, _) | ThrowRef(_) => "throw"
    ContNew(_, _)
    | ContBind(_, _, _)
    | Suspend(_, _)
    | Resume(_, _, _)
    | ResumeThrow(_, _, _, _)
    | ResumeThrowRef(_, _, _)
    | Switch(_, _, _)
    | On(_, _) => "stack switching"
    Select(_, _, _) => "select"
    _ => "instruction"
  }
}

///|
/// A wax float literal as the exact bits it names.
///
/// Three spellings, and only the first is ordinary. A HEX float
/// (`0x1.ce45573fd156bp-252`) names a mantissa and a binary exponent directly,
/// which is how a decompiler writes a double without losing a bit. A `nan:0x..`
/// names a NaN PAYLOAD, which no decimal spelling can carry -- two NaNs with
/// different payloads are different values to a program that inspects them, and
/// re-deriving one through a parsed double would silently pick the canonical
/// payload instead.
///
/// So the bits are assembled here rather than parsed: for these two forms there
/// is nothing to parse them INTO that would keep them.
fn parse_float_bits(
  s : String,
  wide : Bool,
  loc : @basic.Location,
) -> Double raise LowerError {
  let cleaned = s.split("_").join("")
  let (negative, body) = if cleaned.has_prefix("-") {
    (true, cleaned[1:].to_owned())
  } else if cleaned.has_prefix("+") {
    (false, cleaned[1:].to_owned())
  } else {
    (false, cleaned)
  }
  if body.has_prefix("nan") {
    return nan_of(body, negative, wide, loc)
  }
  if body.has_prefix("0x") || body.has_prefix("0X") {
    return hex_float(body, negative, loc)
  }
  let v = @string.parse_double(body) catch {
    _ =>
      @string.parse_int64(body).to_double() catch {
        _ => raise NotLowered("float literal spelling", loc)
      }
  }
  if negative {
    -v
  } else {
    v
  }
}

///|
/// An f32 literal, at f32 width throughout.
///
/// A NaN cannot be routed through a double: widening an f32 NaN to f64 and
/// narrowing it back sets the quiet bit, because that is what a hardware
/// conversion does to a signalling NaN. The payload the source wrote is then
/// silently not the payload emitted -- `nan:0x12345` becomes `nan:0x412345` --
/// which is precisely the bit pattern these literals exist to name.
fn parse_f32(s : String, loc : @basic.Location) -> Float raise LowerError {
  let cleaned = s.split("_").join("")
  let (negative, body) = if cleaned.has_prefix("-") {
    (true, cleaned[1:].to_owned())
  } else if cleaned.has_prefix("+") {
    (false, cleaned[1:].to_owned())
  } else {
    (false, cleaned)
  }
  if body.has_prefix("nan") {
    return Float::reinterpret_from_int(
      nan_bits32(body, negative, loc).reinterpret_as_int(),
    )
  }
  Float::from_double(parse_float_bits(s, false, loc))
}

///|
/// The 32 bits of an f32 NaN with the payload the source named.
fn nan_bits32(
  body : String,
  negative : Bool,
  loc : @basic.Location,
) -> UInt raise LowerError {
  let payload = if body == "nan" {
    0x400000U
  } else if body.has_prefix("nan:0x") {
    let hex = body[6:].to_owned()
    (@string.parse_uint64("0x" + hex) catch {
      _ => raise NotLowered("nan payload", loc)
    }).to_uint()
  } else {
    raise NotLowered("float literal spelling", loc)
  }
  (if negative { 0xFF800000U } else { 0x7F800000U }) | payload
}

///|
/// A NaN, with the payload the source named.
///
/// `nan` alone is the canonical one; `nan:0x..` names the mantissa bits. The
/// width matters: an f32 payload occupies 23 bits and an f64's 52, and a value
/// destined for an f32 is built at that width so the demotion does not discard
/// what the source went to the trouble of writing.
fn nan_of(
  body : String,
  negative : Bool,
  wide : Bool,
  loc : @basic.Location,
) -> Double raise LowerError {
  let payload = if body == "nan" {
    // The canonical quiet NaN: the top mantissa bit and nothing else.
    if wide {
      0x8000000000000UL
    } else {
      0x400000UL
    }
  } else if body.has_prefix("nan:0x") {
    let hex = body[6:].to_owned()
    @string.parse_uint64("0x" + hex) catch {
      _ => raise NotLowered("nan payload", loc)
    }
  } else {
    raise NotLowered("float literal spelling", loc)
  }
  if wide {
    let bits = (if negative {
        0xFFF0000000000000UL
      } else {
        0x7FF0000000000000UL
      }) |
      payload
    bits.reinterpret_as_int64().reinterpret_as_double()
  } else {
    let bits32 = (if negative { 0xFF800000U } else { 0x7F800000U }) |
      payload.to_uint()
    Float::reinterpret_from_int(bits32.reinterpret_as_int()).to_double()
  }
}

///|
/// A hex float: `0x.p<±dec>`.
///
/// Assembled by scaling rather than parsed, because there is no decimal
/// spelling of these bits to go through. Each hex digit after the point is
/// worth a sixteenth of the one before it, and `p` is a power of TWO.
fn hex_float(
  body : String,
  negative : Bool,
  loc : @basic.Location,
) -> Double raise LowerError {
  let rest = body[2:].to_owned()
  let (mantissa_part, exponent) = match rest.split("p").collect() {
    [m] => (m.to_owned(), 0)
    [m, e] =>
      (
        m.to_owned(),
        @string.parse_int(e.to_owned()) catch {
          _ => raise NotLowered("hex float exponent", loc)
        },
      )
    _ => raise NotLowered("hex float spelling", loc)
  }
  let (whole, frac) = match mantissa_part.split(".").collect() {
    [w] => (w.to_owned(), "")
    [w, f] => (w.to_owned(), f.to_owned())
    _ => raise NotLowered("hex float spelling", loc)
  }
  let mut v = 0.0
  for k in 0.. 0 {
    v = v * 2.0
    e = e - 1
  }
  while e < 0 {
    v = v / 2.0
    e = e + 1
  }
  if negative {
    -v
  } else {
    v
  }
}

///|
fn hex_digit(c : UInt16, loc : @basic.Location) -> Int raise LowerError {
  let n = c.to_int()
  if n >= 48 && n <= 57 {
    n - 48
  } else if n >= 97 && n <= 102 {
    n - 87
  } else if n >= 65 && n <= 70 {
    n - 55
  } else {
    raise NotLowered("hex float digit", loc)
  }
}