use crate::runtime::stdlib::HexSignificand;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Strtod {
pub value: f64,
pub len: usize,
pub erange: bool,
}
pub fn strtod(s: &[u8]) -> Strtod {
let mut i = 0;
while i < s.len() && s[i].is_ascii_whitespace() {
i += 1;
}
let negative = match s.get(i) {
Some(b'-') => {
i += 1;
true
}
Some(b'+') => {
i += 1;
false
}
_ => false,
};
let Some((magnitude, len, erange)) = magnitude(&s[i..]) else {
return Strtod {
value: 0.0,
len: 0,
erange: false,
};
};
Strtod {
value: if negative { -magnitude } else { magnitude },
len: i + len,
erange,
}
}
fn magnitude(s: &[u8]) -> Option<(f64, usize, bool)> {
if starts_with_ci(s, b"inf") {
let n = if starts_with_ci(s, b"infinity") { 8 } else { 3 };
return Some((f64::INFINITY, n, false));
}
if starts_with_ci(s, b"nan") {
return Some((f64::NAN, 3 + nan_char_sequence(&s[3..]), false));
}
if s.len() > 2 && s[0] == b'0' && matches!(s[1], b'x' | b'X') && s[2].is_ascii_hexdigit() {
return Some(hex(s));
}
decimal(s)
}
fn ranged_out(value: f64, significand_is_nonzero: bool) -> bool {
significand_is_nonzero && (value.is_infinite() || value.abs() < f64::MIN_POSITIVE)
}
fn nan_char_sequence(s: &[u8]) -> usize {
if s.first() != Some(&b'(') {
return 0;
}
match s.iter().position(|&b| b == b')') {
Some(close)
if s[1..close]
.iter()
.all(|b| b.is_ascii_alphanumeric() || *b == b'_') =>
{
close + 1
}
_ => 0,
}
}
fn hex(s: &[u8]) -> (f64, usize, bool) {
let mut i = 2;
let mut sig = HexSignificand::new();
while i < s.len() && s[i].is_ascii_hexdigit() {
sig.push_digit(hex_digit(s[i]), false);
i += 1;
}
if i < s.len() && s[i] == b'.' {
i += 1;
while i < s.len() && s[i].is_ascii_hexdigit() {
sig.push_digit(hex_digit(s[i]), true);
i += 1;
}
}
if i < s.len() && matches!(s[i], b'p' | b'P') {
if let Some((exp, len)) = exponent(&s[i..]) {
sig.apply_binary_exponent(exp);
i += len;
}
}
let (value, erange) = sig.to_f64();
(value, i, erange)
}
fn decimal(s: &[u8]) -> Option<(f64, usize, bool)> {
let mut i = 0;
let mut digits = 0;
let mut nonzero = false;
while i < s.len() && s[i].is_ascii_digit() {
nonzero |= s[i] != b'0';
i += 1;
digits += 1;
}
if i < s.len() && s[i] == b'.' {
i += 1;
while i < s.len() && s[i].is_ascii_digit() {
nonzero |= s[i] != b'0';
i += 1;
digits += 1;
}
}
if digits == 0 {
return None;
}
if i < s.len() && matches!(s[i], b'e' | b'E') {
if let Some((_, len)) = exponent(&s[i..]) {
i += len;
}
}
let text = std::str::from_utf8(&s[..i]).ok()?;
let value: f64 = text.parse().ok()?;
Some((value, i, ranged_out(value, nonzero)))
}
fn exponent(s: &[u8]) -> Option<(i32, usize)> {
let mut i = 1; let negative = match s.get(i) {
Some(b'-') => {
i += 1;
true
}
Some(b'+') => {
i += 1;
false
}
_ => false,
};
let start = i;
let mut exp: i32 = 0;
while i < s.len() && s[i].is_ascii_digit() {
exp = exp.saturating_mul(10).saturating_add((s[i] - b'0') as i32);
i += 1;
}
if i == start {
return None;
}
Some((if negative { -exp } else { exp }, i))
}
fn hex_digit(b: u8) -> u8 {
match b {
b'0'..=b'9' => b - b'0',
b'a'..=b'f' => b - b'a' + 10,
_ => b - b'A' + 10,
}
}
pub fn starts_with_ci(text: &[u8], name: &[u8]) -> bool {
text.len() >= name.len() && text[..name.len()].eq_ignore_ascii_case(name)
}