///|
/// The value of a digit in any radix up to 16, or 99 for a non-digit.
fn digit_value(c : Char) -> Int32 {
  if c >= '0' && c <= '9' {
    c.to_int() - '0'.to_int()
  } else if c >= 'a' && c <= 'f' {
    c.to_int() - 'a'.to_int() + 10
  } else if c >= 'A' && c <= 'F' {
    c.to_int() - 'A'.to_int() + 10
  } else {
    99
  }
}

///|
/// The compiler's `is_digit_of_radix`.
fn is_digit_of_radix(c : Char?, radix : Int32) -> Bool {
  match c {
    Some(c) => digit_value(c) < radix
    None => false
  }
}

///|
/// The number an `Int` literal denotes (the compiler's `parse_int_value`):
/// an optional `-`, then `0x`, `0o` or `0b` for radix 16, 8 or 2, then
/// digits with `_` separators. `None` when a character does not fit.
///
/// ```mbt check
/// test {
///   debug_inspect(@scanner.parse_int_value("-0x1_F"), content="Some(-31)")
///   debug_inspect(@scanner.parse_int_value("0x"), content="None")
/// }
/// ```
pub fn parse_int_value(value : String) -> BigInt? {
  let negative = value.has_prefix("-")
  let body = if negative { value[1:].to_owned() } else { value }
  let (radix, digits) = if body.has_prefix("0x") {
    (16, body[2:].to_owned())
  } else if body.has_prefix("0o") {
    (8, body[2:].to_owned())
  } else if body.has_prefix("0b") {
    (2, body[2:].to_owned())
  } else {
    (10, body)
  }
  let clean = StringBuilder()
  for i = 0; i < digits.length(); i = i + 1 {
    let c = digits.code_unit_at(i).to_int().unsafe_to_char()
    if c == '_' {
      continue
    }
    if digit_value(c) >= radix {
      return None
    }
    clean.write_char(c)
  }
  let text = clean.to_string()
  if text == "" {
    return None
  }
  let n = BigInt::from_string(text, radix~)
  Some(if negative { -n } else { n })
}

///|
/// The number a `Float` literal denotes (the compiler's
/// `LiteralFloatValue::parse`, Rust `f64::from_str` after removing `_`):
/// `1.`, `1.e3`, `1.0e-3` are numbers; a literal too big is infinity.
/// `None` when the text is not a number.
pub fn parse_float_value(value : String) -> Double? {
  let sb = StringBuilder()
  for i = 0; i < value.length(); i = i + 1 {
    let c = value.code_unit_at(i).to_int().unsafe_to_char()
    if c != '_' {
      sb.write_char(c)
    }
  }
  // MoonBit's parser needs a digit after `.`; Rust's does not.
  let text = sb.to_string().replace(old=".e", new=".0e")
  let text = if text.has_suffix(".") { text + "0" } else { text }
  if text == "" || text.has_suffix("e") || text.has_suffix("e-") {
    return None
  }
  try @string.parse_double(text) catch {
    _ => out_of_range(text)
  } noraise {
    d => if d != d { None } else { Some(d) }
  }
}

///|
/// Rust reads a float whose exponent is too big as infinity and one whose
/// exponent is too small as zero; MoonBit's parser rejects both. Read the
/// mantissa, and only the sign and the digits of the exponent (it may be
/// larger than any integer type), and pick the limit.
fn out_of_range(text : String) -> Double? {
  guard text.split_once("e") is Some((mantissa, exponent)) else { return None }
  let m = @string.parse_double(mantissa) catch { _ => return None }
  let negative_exponent = exponent.has_prefix("-")
  let digits = if negative_exponent { exponent[1:] } else { exponent[:] }
  if digits.length() == 0 {
    return None
  }
  let mut nonzero = false
  for i = 0; i < digits.length(); i = i + 1 {
    let c = digits.code_unit_at(i).to_int()
    if c < '0'.to_int() || c > '9'.to_int() {
      return None
    }
    if c != '0'.to_int() {
      nonzero = true
    }
  }
  let sign = if m < 0.0 { -1.0 } else { 1.0 }
  if m == 0.0 || !nonzero {
    Some(m)
  } else if negative_exponent {
    Some(sign * 0.0)
  } else {
    Some(sign * (1.0 / 0.0))
  }
}