use core::{str::FromStr, fmt::Write};
use crate::{Read, PeekableRead, Stack, JsonError};
struct SliceWrite<'a>(&'a mut [u8], usize);
impl<'a> Write for SliceWrite<'a> {
#[inline(always)]
fn write_str(&mut self, s: &str) -> core::fmt::Result {
let remaining = self.0.len() - self.1;
if remaining < s.len() {
Err(core::fmt::Error)?;
}
self.0[self.1 .. (self.1 + s.len())].copy_from_slice(s.as_bytes());
self.1 += s.len();
Ok(())
}
}
const I64_SIGNIFICANT_DIGITS: usize = (i64::MAX.ilog10() + 1 + 1) as usize;
const F64_SIGNIFICANT_DIGITS: usize = f64::DIGITS as usize;
const SIGNIFICANT_DIGITS: usize = if I64_SIGNIFICANT_DIGITS > F64_SIGNIFICANT_DIGITS {
I64_SIGNIFICANT_DIGITS
} else {
F64_SIGNIFICANT_DIGITS
};
#[doc(hidden)]
pub struct NumberSink {
sign_character_allowed: bool,
digits_in_current_part: bool,
negative: bool,
digits: [u8; 1 + SIGNIFICANT_DIGITS],
i: usize,
before_decimal: bool,
before_exponent: bool,
negative_exponent: bool,
absolute_exponent: Option<i16>,
exponent_correction: i64,
imprecise: bool,
invalid: bool,
}
impl NumberSink {
#[doc(hidden)]
#[inline(always)]
pub fn new() -> Self {
Self {
sign_character_allowed: true,
digits_in_current_part: false,
negative: false,
digits: [b'0'; _],
i: 0,
before_decimal: true,
before_exponent: true,
negative_exponent: false,
absolute_exponent: Some(0),
exponent_correction: 0,
imprecise: false,
invalid: false,
}
}
#[inline(always)]
fn push_byte(&mut self, c: u8) -> bool {
if self.sign_character_allowed {
self.sign_character_allowed = false;
if c == b'-' {
self.negative |= self.before_exponent;
self.negative_exponent |= !self.before_exponent;
return true;
}
if c == b'+' {
self.invalid |= self.before_exponent;
return true;
}
}
if self.before_decimal {
match c {
b'0' ..= b'9' => {
self.invalid |= self.digits_in_current_part & (self.digits[0] == b'0');
self.digits_in_current_part = true;
let within_precision = self.i != self.digits.len();
if within_precision {
self.digits[self.i] = c;
self.i += 1;
} else {
self.exponent_correction += 1;
self.imprecise |= c != b'0';
}
}
b',' | b']' | b'}' | b'\x20' | b'\x09' | b'\x0A' | b'\x0D' => return false,
b'.' => {
self.invalid |= !self.digits_in_current_part;
self.digits_in_current_part = false;
self.before_decimal = false;
}
b'e' | b'E' => {
self.invalid |= !self.digits_in_current_part;
self.sign_character_allowed = true;
self.digits_in_current_part = false;
self.before_decimal = false;
self.before_exponent = false;
}
_ => self.invalid = true,
}
return true;
}
if self.before_exponent {
match c {
b'0' ..= b'9' => {
self.digits_in_current_part = true;
let within_precision = self.i != self.digits.len();
if within_precision {
self.digits[self.i] = c;
let leading_zero =
(c == b'0') & ((self.i == 0) | ((self.i == 1) & (self.digits[0] == b'0')));
self.i += usize::from(!leading_zero);
self.exponent_correction -= 1;
} else {
self.imprecise = true;
}
}
b',' | b']' | b'}' | b'\x20' | b'\x09' | b'\x0A' | b'\x0D' => return false,
b'e' | b'E' => {
self.invalid |= !self.digits_in_current_part;
self.sign_character_allowed = true;
self.digits_in_current_part = false;
self.before_decimal = false;
self.before_exponent = false;
}
_ => self.invalid = true,
}
return true;
}
match c {
b'0' ..= b'9' => {
self.digits_in_current_part = true;
self.absolute_exponent = self.absolute_exponent.and_then(|absolute_exponent| {
let absolute_exponent = absolute_exponent.checked_mul(10)?;
absolute_exponent.checked_add(i16::from(c - b'0'))
});
}
b',' | b']' | b'}' | b'\x20' | b'\x09' | b'\x0A' | b'\x0D' => return false,
_ => self.invalid = true,
}
true
}
#[inline(always)]
fn significant_digits_and_exponent(&self) -> Option<(usize, i64)> {
let absolute_exponent = self.absolute_exponent?;
let embedded_exponent =
if self.negative_exponent { -absolute_exponent } else { absolute_exponent };
let mut exponent = i64::from(embedded_exponent).checked_add(self.exponent_correction)?;
let mut significant_digits = self.i;
while (exponent < 0) &&
(significant_digits > 0) &&
(self.digits[significant_digits - 1] == b'0')
{
significant_digits -= 1;
exponent += 1;
}
Some((significant_digits, exponent))
}
#[inline(always)]
fn strictly_valid(&self) -> bool {
(!self.invalid) & self.digits_in_current_part
}
#[inline(always)]
pub(crate) fn i64(&self) -> Option<i64> {
let (significant_digits, exponent) = self.significant_digits_and_exponent()?;
if self.imprecise || (exponent < 0) {
None?;
}
let mut accum = 0i64;
if self.negative {
for digit in self.digits.iter().take(significant_digits.min(I64_SIGNIFICANT_DIGITS - 1)) {
accum = accum.wrapping_mul(10);
let digit = i64::from(digit - b'0');
accum = accum.wrapping_sub(digit);
}
for digit in &self.digits
[(I64_SIGNIFICANT_DIGITS - 1) .. significant_digits.max(I64_SIGNIFICANT_DIGITS - 1)]
{
accum = accum.checked_mul(10)?;
let digit = i64::from(digit - b'0');
accum = accum.checked_sub(digit)?;
}
} else {
for digit in self.digits.iter().take(significant_digits.min(I64_SIGNIFICANT_DIGITS - 1)) {
accum = accum.wrapping_mul(10);
let digit = i64::from(digit - b'0');
accum = accum.wrapping_add(digit);
}
for digit in &self.digits
[(I64_SIGNIFICANT_DIGITS - 1) .. significant_digits.max(I64_SIGNIFICANT_DIGITS - 1)]
{
accum = accum.checked_mul(10)?;
let digit = i64::from(digit - b'0');
accum = accum.checked_add(digit)?;
}
}
for _ in 0 .. exponent {
accum = accum.checked_mul(10)?;
}
Some(accum)
}
#[doc(hidden)]
#[inline(always)]
pub fn imprecise_str(
&self,
) -> Option<([u8; 1 + SIGNIFICANT_DIGITS + 1 + 1 + I64_SIGNIFICANT_DIGITS], usize)> {
let (original_significant_digits, mut exponent) = self.significant_digits_and_exponent()?;
if original_significant_digits == 0 {
return Some(([b'0'; _], 1));
}
let mut str = [0; _];
let mut len = 0;
if self.negative {
str[len] = b'-';
len += 1;
}
let significant_digits =
original_significant_digits.min(usize::from(self.digits[0] == b'0') + (f64::DIGITS as usize));
{
#[allow(clippy::cast_possible_wrap)]
let further_exponent_correction = (original_significant_digits - significant_digits) as i64;
exponent = exponent.checked_add(further_exponent_correction)?;
}
if (significant_digits > 1) && (self.digits[0] == b'0') {
str[len .. (len + significant_digits - 1)]
.copy_from_slice(&self.digits[1 .. significant_digits]);
len += significant_digits - 1;
} else {
str[len .. (len + significant_digits)].copy_from_slice(&self.digits[.. significant_digits]);
len += significant_digits;
}
if exponent != 0 {
str[len] = b'e';
len += 1;
let mut writer = SliceWrite(&mut str[len ..], 0);
write!(&mut writer, "{}", exponent).ok()?;
len += writer.1;
}
Some((str, len))
}
#[inline(always)]
pub(crate) fn f64(&self) -> Option<f64> {
let (str, len) = self.imprecise_str()?;
let str = core::str::from_utf8(&str[.. len]).ok()?;
let candidate = f64::from_str(str).ok()?;
candidate.is_finite().then_some(candidate)
}
}
impl Write for NumberSink {
#[inline(always)]
fn write_str(&mut self, s: &str) -> core::fmt::Result {
for s in s.as_bytes() {
self.push_byte(*s);
}
Ok(())
}
}
#[inline(always)]
pub(crate) fn to_number_str<'read, R: Read<'read>, S: Stack>(
reader: &mut PeekableRead<'read, R>,
) -> Result<Number, JsonError<'read, R, S>> {
let mut result = NumberSink::new();
while result.push_byte(reader.peek()) {
reader.read_byte().map_err(JsonError::ReadError)?;
}
if !result.strictly_valid() {
Err(JsonError::InvalidValue)?;
}
Ok(Number(result))
}
pub struct Number(NumberSink);
impl Number {
#[inline(always)]
pub fn i64(&self) -> Option<i64> {
self.0.i64()
}
#[inline(always)]
pub fn f64(&self) -> Option<f64> {
self.0.f64()
}
}
#[test]
fn number_sink() {
{
#[allow(clippy::float_cmp)]
let test = |value: f64, expected| {
let mut sink = NumberSink::new();
write!(&mut sink, "{}", value).unwrap();
assert_eq!(sink.f64().unwrap(), f64::from_str(expected).unwrap());
};
test(0.0, "0");
test(0.1, "0.1");
test(0.01, "0.01");
test(0.001, "0.001");
test(0.0012, "0.0012");
test(0.12345678910111213, "0.123456789101112");
test(0.012345678910111213, "0.0123456789101112");
test(12345678910111213.0, "123456789101112e2");
test(12345678910111213.123, "123456789101112e2");
test(123456789.101112, "123456789.101112");
test(123456789.10111213, "123456789.101112");
test(-1.0, "-1");
test(f64::MIN, "-179769313486231e294");
test(f64::MAX, "179769313486231e294");
test(f64::EPSILON, "222044604925031e-30");
}
{
#[allow(clippy::float_cmp)]
let test = |value: &str, expected: i64| {
let mut sink = NumberSink::new();
write!(&mut sink, "{}", value).unwrap();
assert_eq!(sink.i64().unwrap(), expected);
};
test("0", 0);
test("10e1", 100);
test("10.0e1", 100);
test("10.0", 10);
test("10e-1", 1);
{
let str = format!("{}", i64::MAX);
test(&str, i64::MAX);
}
{
let str = format!("{}", i64::MIN);
test(&str, i64::MIN);
}
}
}