use alloc::borrow::Cow;
use alloc::format;
use alloc::string::ToString;
use core::fmt;
use crate::State;
use crate::de::{Deserialize, Slot, default_atom};
use crate::error::Error;
use crate::event::Atom;
use crate::ext::known::invalid;
use crate::ext::{BorrowedExtension, ExtValue};
use crate::ser::{Emit, Serialize};
#[derive(Clone, PartialEq)]
pub struct Number<'a> {
text: Cow<'a, str>,
value: f64,
}
impl<'a> Number<'a> {
pub fn new<T: Into<Cow<'a, str>>>(text: T, value: f64) -> Number<'a> {
let text = text.into();
debug_assert!(is_json_number(&text), "invalid number text {:?}", text);
Number { text, value }
}
pub fn parse<T: Into<Cow<'a, str>>>(text: T) -> Result<Number<'a>, Error> {
let text = text.into();
if !is_json_number(&text) {
return Err(invalid("invalid number"));
}
let value = text.parse().map_err(|_| invalid("invalid number"))?;
Ok(Number { text, value })
}
pub fn as_str(&self) -> &str {
&self.text
}
pub fn value(&self) -> f64 {
self.value
}
pub fn is_integer(&self) -> bool {
!self.text.contains(['.', 'e', 'E'])
}
pub fn into_owned(self) -> Number<'static> {
Number {
text: Cow::Owned(self.text.into_owned()),
value: self.value,
}
}
}
pub(crate) fn is_json_number(s: &str) -> bool {
let bytes = s.as_bytes();
let mut pos = 0;
let digits = |pos: &mut usize| {
let start = *pos;
while bytes.get(*pos).is_some_and(u8::is_ascii_digit) {
*pos += 1;
}
*pos - start
};
if bytes.first() == Some(&b'-') {
pos += 1;
}
let int_start = pos;
let int_len = digits(&mut pos);
if int_len == 0 || (int_len > 1 && bytes[int_start] == b'0') {
return false;
}
if bytes.get(pos) == Some(&b'.') {
pos += 1;
if digits(&mut pos) == 0 {
return false;
}
}
if let Some(b'e' | b'E') = bytes.get(pos) {
pos += 1;
if let Some(b'+' | b'-') = bytes.get(pos) {
pos += 1;
}
if digits(&mut pos) == 0 {
return false;
}
}
pos == bytes.len()
}
impl<'a> fmt::Display for Number<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.text)
}
}
impl<'a> fmt::Debug for Number<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Number({})", self.text)
}
}
impl BorrowedExtension for Number<'static> {
type Value<'a> = Number<'a>;
fn name<'v>(_value: &'v Number<'_>) -> &'v str {
"number"
}
fn fallback<'v>(value: &'v Number<'_>) -> Atom<'v> {
Atom::F64(value.value)
}
fn to_static(value: &Number<'_>) -> Number<'static> {
value.clone().into_owned()
}
fn shorten<'s, 'l: 's>(value: &'s Number<'l>) -> &'s Number<'s> {
value
}
}
impl<'a> Serialize for Number<'a> {
fn serialize<'b>(value: &'b Self, _state: &mut State) -> Result<Emit<'b>, Error> {
Ok(Emit::Atom(Atom::Ext(ExtValue::borrowed_value::<Number>(
value,
))))
}
}
impl<'de, 'a> Deserialize<'de> for Number<'a> {
fn deserialize_atom(slot: &mut Slot<Self>, atom: Atom, state: &mut State) -> Result<(), Error> {
let number = match atom {
Atom::Ext(ref ext) => match ext.downcast_value_ref::<Number>() {
Some(value) => value.clone().into_owned(),
None => return default_atom(slot, atom, state),
},
Atom::U64(value) => Number::new(value.to_string(), value as f64),
Atom::I64(value) => Number::new(value.to_string(), value as f64),
Atom::F64(value) if value.is_finite() => Number::new(format!("{:?}", value), value),
Atom::F32(value) if value.is_finite() => Number::parse(format!("{:?}", value))?,
Atom::Str(ref value) => Number::parse(value.to_string())?,
other => return default_atom(slot, other, state),
};
slot.set(number);
Ok(())
}
fn expecting() -> Cow<'static, str> {
Cow::Borrowed("number")
}
}
#[test]
fn test_number() {
let number = Number::parse("-12.50e3").unwrap();
assert_eq!(number.as_str(), "-12.50e3");
assert_eq!(number.value(), -12500.0);
assert!(!number.is_integer());
assert!(
Number::parse("12345678901234567890123456789")
.unwrap()
.is_integer()
);
for invalid in ["", "1.", ".1", "+1", "01", "1e", "NaN", "inf", "0x10"] {
assert!(Number::parse(invalid).is_err(), "{}", invalid);
}
let ext = ExtValue::borrowed_value::<Number>(&number);
assert_eq!(ext.fallback(), Atom::F64(-12500.0));
assert_eq!(ext.name(), "number");
assert_eq!(
ext.downcast_value_ref::<Number>().unwrap().as_str(),
"-12.50e3"
);
}