///|
/// Parse an OpenMetrics floating-point token.
///
/// The grammar admits regular decimal/scientific notation plus `NaN`, `+Inf`
/// and `-Inf`.
pub fn parse_number(value : StringView) -> Result[Double, String] {
  if value.equal_ignore_ascii_case("nan") {
    return Ok(@double.not_a_number)
  }
  if value.equal_ignore_ascii_case("inf") ||
    value.equal_ignore_ascii_case("+inf") ||
    value.equal_ignore_ascii_case("infinity") ||
    value.equal_ignore_ascii_case("+infinity") {
    return Ok(@double.infinity)
  }
  if value.equal_ignore_ascii_case("-inf") ||
    value.equal_ignore_ascii_case("-infinity") {
    return Ok(-@double.infinity)
  }
  if !is_decimal_number(value) {
    return Err("invalid OpenMetrics number '\{value}'")
  }
  let number = @string.parse_double(value) catch {
    _ => return Err("invalid OpenMetrics number '\{value}'")
  }
  if number.is_nan() ||
    number == @double.infinity ||
    number == -@double.infinity {
    Err("OpenMetrics number '\{value}' exceeds the finite float64 range")
  } else {
    Ok(number)
  }
}

///|
fn is_decimal_number(value : StringView) -> Bool {
  let chars = value.to_array()
  if chars.is_empty() {
    return false
  }
  let mut index = 0
  if chars[index] == '+' || chars[index] == '-' {
    index += 1
  }
  let mut integer_digits = 0
  while index < chars.length() && chars[index].is_ascii_digit() {
    integer_digits += 1
    index += 1
  }
  let mut fraction_digits = 0
  if index < chars.length() && chars[index] == '.' {
    index += 1
    while index < chars.length() && chars[index].is_ascii_digit() {
      fraction_digits += 1
      index += 1
    }
  }
  if integer_digits + fraction_digits == 0 {
    return false
  }
  if index < chars.length() && (chars[index] == 'e' || chars[index] == 'E') {
    index += 1
    if index < chars.length() && (chars[index] == '+' || chars[index] == '-') {
      index += 1
    }
    let mut exponent_digits = 0
    while index < chars.length() && chars[index].is_ascii_digit() {
      exponent_digits += 1
      index += 1
    }
    if exponent_digits == 0 {
      return false
    }
  }
  index == chars.length()
}

///|
/// Emit an OpenMetrics floating-point token.
pub fn format_number(value : Double) -> String {
  if value.is_nan() {
    "NaN"
  } else if value == @double.infinity {
    "+Inf"
  } else if value == -@double.infinity {
    "-Inf"
  } else {
    value.to_string()
  }
}

///|
/// Parse an optional Unix timestamp in seconds.
///
/// OpenMetrics timestamps use the finite `realnumber` grammar and may contain
/// fractional seconds.
pub fn parse_timestamp(value : StringView) -> Result[Double, String] {
  if !is_decimal_number(value) {
    return Err("invalid timestamp '\{value}'")
  }
  let timestamp = @string.parse_double(value) catch {
    _ => return Err("invalid timestamp '\{value}'")
  }
  if timestamp.is_nan() ||
    timestamp == @double.infinity ||
    timestamp == -@double.infinity {
    Err("timestamp '\{value}' exceeds the finite float64 range")
  } else {
    Ok(timestamp)
  }
}