somen_language/numeric/
integer.rs

1//! Parsers for integers.
2use num_traits::{CheckedAdd, CheckedMul, CheckedNeg, Zero};
3use somen::prelude::*;
4
5use super::{digits, digits_fixed, digits_trailing_zeros};
6use crate::character::Character;
7
8/// An integer with given radix which has no trailing zeros.
9#[inline]
10pub fn integer<'a, N, I, C>(radix: u8, neg: bool) -> impl Parser<I, Output = N> + 'a
11where
12    N: Zero + CheckedMul + CheckedAdd + CheckedNeg + TryFrom<u8> + Clone + 'a,
13    I: Input<Ok = C> + ?Sized + 'a,
14    C: Character + 'a,
15{
16    let integer =
17        fold_digits(digits(radix), N::zero(), radix, neg).try_map(|(acc, _, overflowed)| {
18            if overflowed {
19                Err("a not too large number.")
20            } else {
21                Ok(acc)
22            }
23        });
24
25    #[cfg(feature = "alloc")]
26    {
27        integer.expect(alloc::format!("an integer with radix {radix}"))
28    }
29    #[cfg(not(feature = "alloc"))]
30    {
31        integer.expect("an integer")
32    }
33}
34
35/// An integer with given radix which allows trailing zeros.
36#[inline]
37pub fn integer_trailing_zeros<'a, N, I, C>(radix: u8, neg: bool) -> impl Parser<I, Output = N> + 'a
38where
39    N: Zero + CheckedMul + CheckedAdd + CheckedNeg + TryFrom<u8> + Clone + 'a,
40    I: Input<Ok = C> + ?Sized + 'a,
41    C: Character + 'a,
42{
43    let integer = fold_digits(digits_trailing_zeros(radix), N::zero(), radix, neg).try_map(
44        |(acc, _, overflowed)| {
45            if overflowed {
46                Err("a not too large number.")
47            } else {
48                Ok(acc)
49            }
50        },
51    );
52
53    #[cfg(feature = "alloc")]
54    {
55        integer.expect(alloc::format!("an integer with radix {radix}"))
56    }
57    #[cfg(not(feature = "alloc"))]
58    {
59        integer.expect("an integer")
60    }
61}
62
63/// A fixed-length integer with given radix.
64#[inline]
65pub fn integer_fixed<'a, N, I, C>(
66    length: usize,
67    radix: u8,
68    neg: bool,
69) -> impl Parser<I, Output = N> + 'a
70where
71    N: Zero + CheckedMul + CheckedAdd + CheckedNeg + TryFrom<u8> + Clone + 'a,
72    I: Positioned<Ok = C> + ?Sized + 'a,
73    C: Character + 'a,
74{
75    fold_digits(digits_fixed(length, radix), N::zero(), radix, neg).try_map(
76        |(acc, _, overflowed)| {
77            if overflowed {
78                Err("a not too large number.")
79            } else {
80                Ok(acc)
81            }
82        },
83    )
84}
85
86/// Takes a streamed parser of digits, folds it to an `acc` as following digits.
87///
88/// The output value consists of a folded result of `acc`, a number of folded digits and the last
89/// item will be `true` is the number has overflowed.
90pub fn fold_digits<'a, N, S, I, C>(
91    streamed: S,
92    acc: N,
93    radix: u8,
94    neg: bool,
95) -> impl Parser<I, Output = (N, usize, bool)> + 'a
96where
97    N: CheckedMul + CheckedAdd + CheckedNeg + TryFrom<u8> + Clone + 'a,
98    S: IterableParser<I, Item = C> + 'a,
99    I: Positioned<Ok = C> + ?Sized + 'a,
100    C: Character + 'a,
101{
102    let n_radix = N::try_from(radix).ok();
103    streamed.fold(
104        value((acc, 0, n_radix.is_none())),
105        move |(acc, count, overflowed), x| {
106            if overflowed {
107                return (acc, count, true);
108            }
109            let res = acc
110                .checked_mul(n_radix.as_ref().unwrap())
111                .zip(
112                    x.to_digit(radix)
113                        .and_then(|d| N::try_from(d).ok())
114                        .and_then(|x| if neg { x.checked_neg() } else { Some(x) }),
115                )
116                .and_then(|(acc, x)| acc.checked_add(&x));
117            match res {
118                Some(x) => (x, count + 1, false),
119                None => (acc, count, true),
120            }
121        },
122    )
123}