somen_language/numeric/
float.rs

1//! Parsers for floating point decimals.
2use somen::prelude::*;
3
4use super::integer::fold_digits;
5use super::{digits, digits_trailing_zeros, signed};
6use crate::character::{character, Character};
7
8#[doc(no_inline)]
9pub use compute_float::compute_float;
10
11/// A floating point number.
12///
13/// This parser requires an integer part, but a decimal part and an exponent are optional.
14/// If you want to apply different rules, you can implement it by yourself using [`compute_float()`]
15/// helper function.
16///
17/// Also note that this function doesn't support infinities and NaNs.
18#[cfg(any(feature = "std", feature = "libm"))]
19#[inline]
20pub fn float<'a, N, I, C>(neg: bool) -> impl Parser<I, Output = N> + 'a
21where
22    N: compute_float::Float + num_traits::Float + 'a,
23    I: Input<Ok = C> + ?Sized + 'a,
24    C: Character + 'a,
25{
26    float_inner().map(move |(man, exp10)| {
27        compute_float(neg, man, exp10).unwrap_or_else(|| {
28            let res = N::from(man).unwrap() * N::from(10u8).unwrap().powi(exp10);
29            if neg {
30                -res
31            } else {
32                res
33            }
34        })
35    })
36}
37
38#[inline]
39#[cfg(not(any(feature = "std", feature = "libm")))]
40pub fn float<'a, N, I, C>(neg: bool) -> impl Parser<I, Output = N> + 'a
41where
42    N: compute_float::Float + 'a,
43    I: Input<Ok = C> + ?Sized + 'a,
44    C: Character + 'a,
45{
46    float_inner().try_map(move |(man, exp10)| {
47        compute_float(neg, man, exp10).ok_or("a valid floating point number")
48    })
49}
50
51fn float_inner<'a, I, C>() -> impl Parser<I, Output = (u64, i32)> + 'a
52where
53    I: Input<Ok = C> + ?Sized + 'a,
54    C: Character + 'a,
55{
56    fold_digits::<u64, _, _, _>(digits(10), 0, 10, false)
57        .then(|(int, _, overflowed)| {
58            if overflowed {
59                value((int, 0, true)).left()
60            } else {
61                character(b'.')
62                    .prefix(fold_digits(digits_trailing_zeros(10), int, 10, false))
63                    .or(value((int, 0, false)))
64                    .right()
65            }
66        })
67        .and(
68            character(b'e')
69                .or(character(b'E'))
70                .prefix(signed(
71                    |neg| fold_digits(digits_trailing_zeros(10), 0i32, 10, neg),
72                    true,
73                ))
74                .or(value((0, 0, false))),
75        )
76        .map(
77            |((mantissa, count, man_overflowed), (exp, _, exp_overflowed))| {
78                (
79                    if man_overflowed { u64::MAX } else { mantissa },
80                    if exp_overflowed {
81                        if exp < 0 {
82                            i32::MIN
83                        } else {
84                            i32::MAX
85                        }
86                    } else {
87                        exp.saturating_sub(count as i32)
88                    },
89                )
90            },
91        )
92}