use super::ZEROS;
use crate::error::{ErrorCode, PResult};
use crate::swar::{first_match, load_u64};
#[inline(always)]
pub(crate) const fn digit_stop_mask(v: u64) -> u64 {
let a = v.wrapping_add(0x4646_4646_4646_4646);
let b = v.wrapping_sub(ZEROS);
(a | b) & 0x8080_8080_8080_8080
}
#[inline(always)]
pub(crate) const fn parse_8_digits(v: u64) -> u64 {
const MASK: u64 = 0x0000_00FF_0000_00FF;
const MUL1: u64 = 0x000F_4240_0000_0064; const MUL2: u64 = 0x0000_2710_0000_0001; let mut val = v - ZEROS;
val = (val * 10) + (val >> 8);
(((val & MASK).wrapping_mul(MUL1)) + (((val >> 16) & MASK).wrapping_mul(MUL2))) >> 32
}
#[inline(always)]
pub(crate) const fn is_digit(c: u8) -> bool {
c.wrapping_sub(b'0') < 10
}
pub(crate) const SAFE_DIGITS: usize = 19;
pub(crate) const POW10_U64: [u64; 9] = [
1,
10,
100,
1_000,
10_000,
100_000,
1_000_000,
10_000_000,
100_000_000,
];
#[inline(always)]
pub(crate) fn parse_leading_digits(word: u64, k: usize) -> u64 {
debug_assert!(k <= 7);
let s = 8 * (8 - k.max(1));
let filled = (word << s) | (ZEROS >> (64 - s));
parse_8_digits(core::hint::select_unpredictable(k == 0, ZEROS, filled))
}
#[inline(always)]
pub(crate) fn parse_u64(buf: &[u8], i: &mut usize) -> PResult<u64> {
let idx = *i;
if idx + 16 <= buf.len() {
let (first_word, second_word) = unsafe { (load_u64(buf, idx), load_u64(buf, idx + 8)) };
let first = first_word as u8;
let stop = digit_stop_mask(first_word);
if stop != 0 {
let k = first_match(stop);
if k == 0 {
return Err(ErrorCode::ExpectedNumber);
}
if first == b'0' && k > 1 {
return Err(ErrorCode::InvalidNumber);
}
*i = idx + k;
return Ok(parse_leading_digits(first_word, k));
}
if first == b'0' {
return Err(ErrorCode::InvalidNumber);
}
let stop = digit_stop_mask(second_word);
if stop != 0 {
let k = first_match(stop);
*i = idx + 8 + k;
return Ok(
parse_8_digits(first_word) * POW10_U64[k] + parse_leading_digits(second_word, k)
);
}
}
match parse_u64_wide(buf, idx) {
Ok((value, end)) => {
*i = end;
Ok(value)
}
Err((code, at)) => {
*i = at;
Err(code)
}
}
}
#[inline(always)]
pub(crate) fn fold_digits(buf: &[u8], mut idx: usize, mut acc: u64) -> (u64, usize) {
let n = buf.len();
while idx + 8 <= n {
let word = unsafe { load_u64(buf, idx) };
let stop = digit_stop_mask(word);
if stop != 0 {
let k = first_match(stop);
acc = acc
.wrapping_mul(POW10_U64[k])
.wrapping_add(parse_leading_digits(word, k));
return (acc, idx + k);
}
acc = acc
.wrapping_mul(100_000_000)
.wrapping_add(parse_8_digits(word));
idx += 8;
}
while idx < n {
let d = buf[idx].wrapping_sub(b'0');
if d >= 10 {
break;
}
acc = acc.wrapping_mul(10).wrapping_add(d as u64);
idx += 1;
}
(acc, idx)
}
type Wide = Result<(u64, usize), (ErrorCode, usize)>;
#[inline(never)]
fn parse_u64_wide(buf: &[u8], start: usize) -> Wide {
if start >= buf.len() {
return Err((ErrorCode::UnexpectedEnd, start));
}
if !is_digit(buf[start]) {
return Err((ErrorCode::ExpectedNumber, start));
}
let (value, end) = fold_digits(buf, start, 0);
finish_wide(buf, start, end, value)
}
#[inline(always)]
fn finish_wide(buf: &[u8], start: usize, end: usize, value: u64) -> Wide {
let len = end - start;
if buf[start] == b'0' && len > 1 {
return Err((ErrorCode::InvalidNumber, start));
}
if len > SAFE_DIGITS {
return recheck_wide(buf, start, end);
}
Ok((value, end))
}
#[cold]
#[inline(never)]
fn recheck_wide(buf: &[u8], start: usize, end: usize) -> Wide {
let mut value: u64 = 0;
let mut idx = start;
while idx < end {
let c = buf[idx] - b'0';
value = match value.checked_mul(10).and_then(|v| v.checked_add(c as u64)) {
Some(v) => v,
None => return Err((out_of_range(buf, end), end)),
};
idx += 1;
}
Ok((value, end))
}
#[inline(always)]
pub(crate) fn reject_float_tail(buf: &[u8], i: usize) -> PResult<()> {
if i < buf.len() {
match buf[i] {
b'.' | b'e' | b'E' => return Err(ErrorCode::InvalidNumber),
_ => {}
}
}
Ok(())
}
#[cold]
#[inline(never)]
pub(crate) fn out_of_range(buf: &[u8], end: usize) -> ErrorCode {
match reject_float_tail(buf, end) {
Ok(()) => ErrorCode::NumberOutOfRange,
Err(e) => e,
}
}
#[inline(always)]
pub(crate) fn parse_i64(buf: &[u8], i: &mut usize) -> PResult<i64> {
let negative = buf.get(*i) == Some(&b'-');
*i += negative as usize;
let magnitude = parse_u64(buf, i)?;
if magnitude > (i64::MAX as u64) + negative as u64 {
return Err(out_of_range(buf, *i));
}
let flip = (negative as i64).wrapping_neg();
Ok(((magnitude as i64) ^ flip).wrapping_sub(flip))
}
#[inline(always)]
pub(crate) fn parse_unsigned_u64(buf: &[u8], i: &mut usize) -> PResult<u64> {
match parse_u64(buf, i) {
Err(ErrorCode::ExpectedNumber) if buf.get(*i) == Some(&b'-') => {
let (zero, at) = negative_unsigned(buf, *i);
*i = at;
zero.map(|()| 0)
}
parsed => parsed,
}
}
pub(crate) fn parse_unsigned_u128(buf: &[u8], i: &mut usize) -> PResult<u128> {
match parse_u128(buf, i) {
Err(ErrorCode::ExpectedNumber) if buf.get(*i) == Some(&b'-') => {
let (zero, at) = negative_unsigned(buf, *i);
*i = at;
zero.map(|()| 0)
}
parsed => parsed,
}
}
#[cold]
#[inline(never)]
fn negative_unsigned(buf: &[u8], sign: usize) -> (PResult<()>, usize) {
let mut end = sign + 1;
let zero = match parse_u64(buf, &mut end) {
Ok(0) => Ok(()),
Ok(_) => Err(out_of_range(buf, end)),
Err(e) => Err(e),
};
(zero, end)
}
pub(crate) fn parse_u128(buf: &[u8], i: &mut usize) -> PResult<u128> {
let start = *i;
let Some(&first) = buf.get(start) else {
return Err(ErrorCode::UnexpectedEnd);
};
if !is_digit(first) {
return Err(ErrorCode::ExpectedNumber);
}
if first == b'0' {
if buf.get(start + 1).is_some_and(|&c| is_digit(c)) {
return Err(ErrorCode::InvalidNumber);
}
*i = start + 1;
return Ok(0);
}
let mut idx = start;
let mut v: u128 = 0;
while let Some(&c) = buf.get(idx)
&& is_digit(c)
{
let Some(next) = v
.checked_mul(10)
.and_then(|x| x.checked_add((c - b'0') as u128))
else {
let end = idx + buf[idx..].iter().take_while(|&&c| is_digit(c)).count();
*i = end;
return Err(out_of_range(buf, end));
};
v = next;
idx += 1;
}
*i = idx;
Ok(v)
}
pub(crate) fn parse_int_text<T: core::str::FromStr>(s: &str) -> Option<T> {
match s.parse() {
Ok(v) => Some(v),
Err(_) => match s.strip_prefix('-') {
Some(zeros) if !zeros.is_empty() && zeros.bytes().all(|b| b == b'0') => {
zeros.parse().ok()
}
_ => None,
},
}
}