use core::{
fmt::{
self,
Debug,
Display,
Formatter,
},
str::FromStr,
};
use binator_base::{
is,
tag_no_case,
AsciiParse,
BaseAtom,
};
use binator_core::{
Acc,
Contexting,
CoreAtom,
Parse,
Parsed,
Streaming,
Success,
};
use binator_utils::{
Utils,
UtilsAtom,
};
use crate::{
sign,
to_digit,
};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum FloatAtom {
Number,
Inf,
NaN,
Bug,
}
impl Display for FloatAtom {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
FloatAtom::Number => write!(f, "Float: Number"),
FloatAtom::Inf => write!(f, "Float: Inf"),
FloatAtom::NaN => write!(f, "Float: NaN"),
FloatAtom::Bug => write!(f, "Float: Bug"),
}
}
}
pub trait FloatParse<Stream, Context> = AsciiParse<Stream, Context>
where
Stream: Streaming,
<Stream as Streaming>::Item: Into<u8>,
<Stream as Streaming>::Span: AsRef<[u8]>,
Context: Contexting<FloatAtom>,
Context: Contexting<UtilsAtom<Stream>>,
Context: Contexting<BaseAtom<u8>>,
Context: Contexting<CoreAtom<Stream>>;
#[cfg_attr(
feature = "tracing",
tracing::instrument(level = "trace", skip_all, ret(Display))
)]
pub fn float<Token, Stream, Context>(stream: Stream) -> Parsed<Token, Stream, Context>
where
(): FloatParse<Stream, Context>,
Token: FromStr + Debug + num_traits::Zero,
{
let Success {
token: Success { stream: float, .. },
stream,
} = sign
.opt()
.and(
number
.or(tag_no_case("nan").drop().add_atom(|| FloatAtom::NaN))
.or(tag_no_case("inf").drop().add_atom(|| FloatAtom::Inf)),
)
.span()
.parse(stream)?;
let float = unsafe { core::str::from_utf8_unchecked(float.as_ref()) };
if let Ok(float) = Token::from_str(float) {
Parsed::Success {
token: float,
stream,
}
} else {
Parsed::Failure(Context::new(FloatAtom::Bug))
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(level = "trace", skip_all, ret(Display))
)]
fn number<Stream, Context>(stream: Stream) -> Parsed<(), Stream, Context>
where
Stream: FloatParse<Stream, Context>,
{
to_digit
.drop()
.fold_bounds(1.., || (), Acc::acc)
.and(
is(b'.')
.and(to_digit.drop().fold_bounds(.., || (), Acc::acc))
.opt(),
)
.drop()
.or(
is(b'.')
.and(to_digit.drop().fold_bounds(1.., || (), Acc::acc))
.drop(),
)
.and(exp.opt())
.drop()
.parse(stream)
.map_context(|context| context.add(FloatAtom::Number))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(level = "trace", skip_all, ret(Display))
)]
fn exp<Stream, Context>(stream: Stream) -> Parsed<(), Stream, Context>
where
Stream: FloatParse<Stream, Context>,
{
let Success { token: _, stream } = is(b'e').or(is(b'E')).parse(stream)?;
let Success { token: _, stream } = sign.opt().parse(stream)?;
let Success { token: _, stream } = to_digit
.drop()
.fold_bounds(1.., || (), Acc::acc)
.opt()
.parse(stream)?;
Parsed::Success { token: (), stream }
}
#[cfg(test)]
mod tests {
use core::{
convert::Infallible,
mem::discriminant,
};
use std::str::FromStr;
use binator_base::BaseAtom;
use binator_context::Tree;
use binator_core::{
CoreAtom,
Parse,
Parsed,
Streaming,
};
use binator_utils::UtilsAtom;
use derive_more::{
Display,
From,
};
use rand::Rng;
use test_log::test;
use super::{
float,
FloatAtom,
};
#[derive(Display, Debug, Clone, From)]
enum Context<Stream: Streaming> {
Float(FloatAtom),
Core(CoreAtom<Stream, Infallible>),
Utils(UtilsAtom<Stream>),
Base(BaseAtom<u8>),
}
impl<Stream: Streaming> PartialEq for Context<Stream> {
fn eq(&self, other: &Self) -> bool {
discriminant(self) == discriminant(other)
}
}
type HandleAtom<Stream> = Tree<Context<Stream>>;
fn test_float(f: f64) {
let stream = f.to_string();
let stream = stream.as_bytes();
let result: Parsed<_, _, HandleAtom<_>> = float.parse(stream);
let expected = Parsed::Success {
token: f,
stream: "".as_bytes(),
};
println!("{:#?}", result);
assert_eq!(result, expected);
}
fn test_str(stream: &str) {
let f = f64::from_str(stream).unwrap();
let result: Parsed<_, _, HandleAtom<_>> = float.parse(stream.as_bytes());
let expected = Parsed::Success {
token: f,
stream: "".as_bytes(),
};
assert_eq!(result, expected);
}
#[test]
fn float_simple() {
test_str("42.42");
test_str("42.");
test_str(".42");
test_str("0000000000042.");
test_str(".4200000000000");
}
#[test]
fn float_nan() {
let stream = f64::NAN.to_string();
let stream = stream.as_bytes();
let result: Parsed<f64, _, HandleAtom<_>> = float.parse(stream);
assert!(result.unwrap().token.is_nan());
let result: Parsed<f64, _, HandleAtom<_>> = float.parse("nAn".as_bytes());
assert!(result.unwrap().token.is_nan());
let stream = "Na";
let result: Parsed<f64, _, HandleAtom<_>> = float.parse(stream.as_bytes());
println!("{}", result.as_ref().unwrap_context());
assert!(!matches!(result, Parsed::Success { .. }));
}
#[test]
fn float_infite() {
let stream = f64::INFINITY.to_string();
let stream = stream.as_bytes();
let result: Parsed<f64, _, HandleAtom<_>> = float.parse(stream);
assert!(result.unwrap().token.is_infinite());
let result: Parsed<f64, _, HandleAtom<_>> = float.parse("INF".as_bytes());
assert!(result.unwrap().token.is_infinite());
}
#[test]
#[ignore]
fn float_random() {
let mut rng = rand::thread_rng();
let mut buf = [0; 8];
for _ in 0..42_000 {
rng.fill(&mut buf);
let f = f64::from_ne_bytes(buf);
if f.is_finite() {
test_float(f);
}
}
}
}