///|
/// 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)
}
}