use std::convert::TryFrom;
use crate::value::IValue;
pub(crate) enum NumberShape {
Integer,
Float,
}
pub(crate) enum InlineNumberError {
Invalid,
Spill(NumberShape),
}
pub(crate) trait InlineNumber {
fn encode_int(value: i64) -> Option<usize>;
fn encode_f64(value: f64) -> Option<usize>;
fn from_str(s: &str) -> Result<usize, InlineNumberError>;
fn from_i64(value: i64) -> Option<IValue> {
Self::encode_int(value).map(|bits| unsafe { IValue::new_inline_number(bits) })
}
fn from_u64(value: u64) -> Option<IValue> {
i64::try_from(value).ok().and_then(Self::from_i64)
}
fn from_f64(value: f64) -> Option<IValue> {
Self::encode_f64(value).map(|bits| unsafe { IValue::new_inline_number(bits) })
}
}
#[cfg_attr(not(feature = "arbitrary_precision"), allow(dead_code))]
pub(crate) struct JsonNumber<'a> {
pub(crate) negative: bool,
int_digits: &'a [u8],
frac_digits: &'a [u8],
written_exp: i64,
shape: NumberShape,
}
#[cfg_attr(not(feature = "arbitrary_precision"), allow(dead_code))]
impl JsonNumber<'_> {
pub(crate) fn significand(&self) -> Option<(Vec<u8>, i32)> {
let exp = self
.written_exp
.saturating_sub(self.frac_digits.len() as i64);
if exp.unsigned_abs() > MAX_EXP {
return None;
}
let mut digits = Vec::with_capacity(self.int_digits.len() + self.frac_digits.len());
digits.extend_from_slice(self.int_digits);
digits.extend_from_slice(self.frac_digits);
Some((digits, exp as i32))
}
}
#[cfg_attr(not(feature = "arbitrary_precision"), allow(dead_code))]
pub(crate) const MAX_EXP: u64 = 1 << 30;
pub(crate) fn parse_json_number(s: &str) -> Option<JsonNumber<'_>> {
let b = s.as_bytes();
let n = b.len();
let mut i = 0;
let negative = i < n && b[i] == b'-';
if negative {
i += 1;
}
let int_start = i;
match b.get(i) {
Some(b'0') => i += 1,
Some(&c) if c.is_ascii_digit() => {
i += 1;
while i < n && b[i].is_ascii_digit() {
i += 1;
}
}
_ => return None,
}
let int_digits = &b[int_start..i];
let mut is_float = false;
let mut frac_digits: &[u8] = &[];
if i < n && b[i] == b'.' {
is_float = true;
i += 1;
if !matches!(b.get(i), Some(c) if c.is_ascii_digit()) {
return None;
}
let frac_start = i;
while i < n && b[i].is_ascii_digit() {
i += 1;
}
frac_digits = &b[frac_start..i];
}
let mut written_exp: i64 = 0;
if i < n && (b[i] == b'e' || b[i] == b'E') {
is_float = true;
i += 1;
let exp_negative = i < n && b[i] == b'-';
if i < n && (b[i] == b'+' || b[i] == b'-') {
i += 1;
}
if !matches!(b.get(i), Some(c) if c.is_ascii_digit()) {
return None;
}
while i < n && b[i].is_ascii_digit() {
written_exp = written_exp
.saturating_mul(10)
.saturating_add(i64::from(b[i] - b'0'));
i += 1;
}
if exp_negative {
written_exp = -written_exp;
}
}
if i != n {
return None;
}
Some(JsonNumber {
negative,
int_digits,
frac_digits,
written_exp,
shape: if is_float {
NumberShape::Float
} else {
NumberShape::Integer
},
})
}
pub(crate) fn from_str_with(
s: &str,
encode_int: impl FnOnce(i64) -> Option<usize>,
encode_float: impl FnOnce(&str) -> Option<usize>,
) -> Result<usize, InlineNumberError> {
match parse_json_number(s)
.ok_or(InlineNumberError::Invalid)?
.shape
{
NumberShape::Integer => s
.parse::<i64>()
.ok()
.and_then(encode_int)
.ok_or(InlineNumberError::Spill(NumberShape::Integer)),
NumberShape::Float => encode_float(s).ok_or(InlineNumberError::Spill(NumberShape::Float)),
}
}