use crate::value::Number;
#[inline]
pub fn is_null(s: &str) -> bool {
s.is_empty() || s == "~" || s.eq_ignore_ascii_case("null")
}
#[inline]
pub fn parse_bool(s: &str) -> Option<bool> {
match s {
"true" | "True" | "TRUE" | "yes" | "Yes" | "YES" | "on" | "On" | "ON" => Some(true),
"false" | "False" | "FALSE" | "no" | "No" | "NO" | "off" | "Off" | "OFF" => Some(false),
_ => None,
}
}
pub fn parse_i64(s: &str) -> Option<i64> {
let s = s.trim();
if s.is_empty() {
return None;
}
let (neg, s) = if let Some(rest) = s.strip_prefix('-') {
(true, rest)
} else if let Some(rest) = s.strip_prefix('+') {
(false, rest)
} else {
(false, s)
};
let magnitude: i128 = if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
i128::from_str_radix(hex, 16).ok()?
} else if let Some(oct) = s.strip_prefix("0o").or_else(|| s.strip_prefix("0O")) {
i128::from_str_radix(oct, 8).ok()?
} else if let Some(bin) = s.strip_prefix("0b").or_else(|| s.strip_prefix("0B")) {
i128::from_str_radix(bin, 2).ok()?
} else {
s.parse().ok()?
};
let value = if neg { -magnitude } else { magnitude };
i64::try_from(value).ok()
}
pub fn parse_u64(s: &str) -> Option<u64> {
let s = s.trim();
if s.is_empty() {
return None;
}
let s = if let Some(rest) = s.strip_prefix('-') {
if !rest.is_empty() {
return None;
}
rest
} else if let Some(rest) = s.strip_prefix('+') {
rest
} else {
s
};
if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
u64::from_str_radix(hex, 16).ok()
} else if let Some(oct) = s.strip_prefix("0o").or_else(|| s.strip_prefix("0O")) {
u64::from_str_radix(oct, 8).ok()
} else if let Some(bin) = s.strip_prefix("0b").or_else(|| s.strip_prefix("0B")) {
u64::from_str_radix(bin, 2).ok()
} else {
s.parse().ok()
}
}
pub fn parse_f64(s: &str) -> Option<f64> {
if s.eq_ignore_ascii_case(".inf") || s.eq_ignore_ascii_case("+.inf") {
return Some(f64::INFINITY);
}
if s.eq_ignore_ascii_case("-.inf") {
return Some(f64::NEG_INFINITY);
}
if s.eq_ignore_ascii_case(".nan") {
return Some(f64::NAN);
}
s.parse().ok()
}
#[inline]
pub fn needs_quoting(s: &str) -> bool {
is_null(s) || parse_bool(s).is_some() || parse_number(s).is_some()
}
pub fn parse_number(s: &str) -> Option<Number> {
let s = s.trim();
if s.is_empty() {
return None;
}
if let Some(n) = parse_i64(s) {
if n >= 0 {
return Some(Number::UInt(n as u64));
}
return Some(Number::Int(n));
}
if let Some(n) = parse_u64(s) {
return Some(Number::UInt(n));
}
if s.eq_ignore_ascii_case(".inf") || s.eq_ignore_ascii_case("+.inf") {
return Some(Number::Float(f64::INFINITY));
}
if s.eq_ignore_ascii_case("-.inf") {
return Some(Number::Float(f64::NEG_INFINITY));
}
if s.eq_ignore_ascii_case(".nan") {
return Some(Number::Float(f64::NAN));
}
let has_decimal = s.contains('.');
let has_exponent = s.bytes().any(|b| b == b'e' || b == b'E');
if has_decimal || has_exponent {
if let Ok(f) = s.parse::<f64>() {
return Some(Number::Float(f));
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_null() {
assert!(is_null(""));
assert!(is_null("~"));
assert!(is_null("null"));
assert!(is_null("NULL"));
assert!(is_null("Null"));
assert!(!is_null("nil"));
assert!(!is_null("none"));
}
#[test]
fn test_parse_bool() {
assert_eq!(parse_bool("true"), Some(true));
assert_eq!(parse_bool("True"), Some(true));
assert_eq!(parse_bool("TRUE"), Some(true));
assert_eq!(parse_bool("yes"), Some(true));
assert_eq!(parse_bool("on"), Some(true));
assert_eq!(parse_bool("false"), Some(false));
assert_eq!(parse_bool("False"), Some(false));
assert_eq!(parse_bool("no"), Some(false));
assert_eq!(parse_bool("off"), Some(false));
assert_eq!(parse_bool("maybe"), None);
}
#[test]
fn test_parse_i64_decimal() {
assert_eq!(parse_i64("42"), Some(42));
assert_eq!(parse_i64("-10"), Some(-10));
assert_eq!(parse_i64("+5"), Some(5));
assert_eq!(parse_i64("0"), Some(0));
}
#[test]
fn test_parse_i64_radix() {
assert_eq!(parse_i64("0xFF"), Some(255));
assert_eq!(parse_i64("0xff"), Some(255));
assert_eq!(parse_i64("-0xFF"), Some(-255));
assert_eq!(parse_i64("0o77"), Some(63));
assert_eq!(parse_i64("-0o77"), Some(-63));
assert_eq!(parse_i64("0b1010"), Some(10));
assert_eq!(parse_i64("-0b1010"), Some(-10));
}
#[test]
fn test_parse_i64_boundaries() {
assert_eq!(parse_i64(&i64::MAX.to_string()), Some(i64::MAX));
assert_eq!(parse_i64(&i64::MIN.to_string()), Some(i64::MIN));
assert_eq!(parse_i64("9223372036854775808"), None); }
#[test]
fn test_parse_u64() {
assert_eq!(parse_u64("42"), Some(42));
assert_eq!(parse_u64("+5"), Some(5));
assert_eq!(parse_u64("-10"), None); assert_eq!(parse_u64("0xFF"), Some(255));
}
#[test]
fn test_parse_u64_large() {
assert_eq!(parse_u64(&u64::MAX.to_string()), Some(u64::MAX));
let large = (i64::MAX as u64) + 1;
assert_eq!(parse_u64(&large.to_string()), Some(large));
}
#[test]
fn test_parse_f64() {
assert_eq!(parse_f64("2.5"), Some(2.5));
assert_eq!(parse_f64("1e10"), Some(1e10));
assert!(parse_f64(".inf").unwrap().is_infinite());
assert!(parse_f64("+.inf").unwrap().is_infinite());
assert!(parse_f64("-.inf").unwrap().is_infinite());
assert!(parse_f64("-.inf").unwrap().is_sign_negative());
assert!(parse_f64(".nan").unwrap().is_nan());
assert!(parse_f64(".NaN").unwrap().is_nan());
}
#[test]
fn test_parse_number() {
assert_eq!(parse_number("42"), Some(Number::UInt(42)));
assert_eq!(parse_number("-10"), Some(Number::Int(-10)));
assert_eq!(parse_number("2.5"), Some(Number::Float(2.5)));
let large = u64::MAX;
assert_eq!(parse_number(&large.to_string()), Some(Number::UInt(large)));
}
}