// Manual number parsing shared by the CSV source and the SQL lexer.
// moonbitlang/core/internal/strconv is import-restricted, so these are
// hand-rolled; the double parser is accurate enough for the 1e-9
// relative tolerance used against duckdb goldens.
///|
/// Optional sign then digits, else None, range-checked to int32: a value
/// outside [-2^31, 2^31-1] returns None so callers promote or report
/// instead of wrapping silently.
pub fn parse_i32(s : String) -> Int32? {
match parse_i64(s) {
Some(v) =>
if v >= -2147483648L && v <= 2147483647L {
Some(v.to_int())
} else {
None
}
None => None
}
}
///|
/// Optional sign then digits, else None, range-checked to int64.
/// Accumulates as a negative magnitude — -2^63 is representable while
/// +2^63 is not, so negating at the end stays exact.
pub fn parse_i64(s : String) -> Int64? {
let mut acc = 0L
let mut neg = false
let mut seen_sign = false
let mut digits = 0
for ch in s {
if !seen_sign && (ch == '-' || ch == '+') {
neg = ch == '-'
seen_sign = true
continue
}
seen_sign = true
let v = ch.to_int()
if v < 48 || v > 57 {
return None
}
digits += 1
// floor(2^63 / 10): at or above this magnitude the * 10 step would
// leave the int64 range (any wrap lands on a positive value)
if acc < -922337203685477580L {
return None
}
acc = acc * 10L - (v - 48).to_int64()
if acc > 0L {
return None // wrapped past -2^63
}
}
if !seen_sign || digits == 0 {
return None // empty or sign-only input
}
if !neg {
if acc == -9223372036854775808L {
return None // 2^63 overflows positive int64
}
acc = -acc
}
Some(acc)
}
///|
/// [sign] digits [ . digits ] [ (e|E) [sign] digits ], else None.
/// Digits accumulate in Double — an Int64 accumulator would wrap exactly
/// the way this parser exists to prevent — so arbitrarily long inputs
/// degrade to the nearest float64 instead of flipping sign.
pub fn parse_f64(s : String) -> Double? {
let chars : Array[Char] = []
for ch in s {
chars.push(ch)
}
let n = chars.length()
if n == 0 {
return None
}
let mut i = 0
let mut neg = false
if chars[0] == '-' || chars[0] == '+' {
neg = chars[0] == '-'
i = 1
}
// exact fast path: a pure-digit run that fits in UInt64 converts
// through the exact uint64->double rounding, so every integer literal
// up to 20 digits keeps its value (2^63 parsed by the decimal
// accumulator below lands on a neighbor double, and `k = -2^63`
// misses). Wider runs fall through to the accumulator's 1e-9 contract.
if i < n {
let mut acc : UInt64 = 0UL
let mut fits = true
let mut j = i
while j < n && is_digit(chars[j]) {
if fits {
let d = (chars[j].to_int() - 48).to_uint64()
if acc > 1844674407370955161UL {
fits = false
} else {
let scaled = acc * 10UL
if scaled > 18446744073709551615UL - d {
fits = false
} else {
acc = scaled + d
}
}
}
j += 1
}
if fits && j == n {
let v = acc.to_double()
return Some(if neg { -v } else { v })
}
}
let mut value = 0.0
let mut int_digits = 0
while i < n && is_digit(chars[i]) {
value = value * 10.0 + (chars[i].to_int() - 48).to_double()
int_digits += 1
i += 1
}
let mut frac_div = 1.0
if i < n && chars[i] == '.' {
i += 1
while i < n && is_digit(chars[i]) {
frac_div = frac_div * 10.0
value = value * 10.0 + (chars[i].to_int() - 48).to_double()
i += 1
}
}
if int_digits == 0 && frac_div == 1.0 {
return None
}
if frac_div > 1.0 {
value = value / frac_div
}
if i < n && (chars[i] == 'e' || chars[i] == 'E') {
i += 1
let mut exp_neg = false
if i < n && (chars[i] == '-' || chars[i] == '+') {
exp_neg = chars[i] == '-'
i += 1
}
let mut exp = 0
let mut exp_digits = 0
while i < n && is_digit(chars[i]) {
exp = exp * 10 + (chars[i].to_int() - 48)
exp_digits += 1
i += 1
if exp > 300 {
return None
}
}
if exp_digits == 0 {
return None
}
if exp_neg {
value = value / pow10(exp)
} else {
value = value * pow10(exp)
}
}
if i != n {
return None // trailing junk
}
Some(if neg { -value } else { value })
}
///|
pub fn parse_bool(s : String) -> Bool? {
if s == "true" {
Some(true)
} else if s == "false" {
Some(false)
} else {
None
}
}
///|
fn is_digit(ch : Char) -> Bool {
let v = ch.to_int()
v >= 48 && v <= 57
}
///|
fn pow10(e : Int32) -> Double {
let mut v = 1.0
for _ in 0..