const-frac 0.0.3

Types for supporting floating point constants.
Documentation
extern crate alloc;

use combine:: {
    Parser, RangeStream, ParseError,
    choice, many, many1, one_of, optional, token,
    parser:: {
        char:: { digit },
        range:: recognize_with_value,
    },
};
use core::num::ParseIntError;
use alloc::string::String;
use super::Frac;

struct Numbers(String);

impl Numbers {
    fn trim_trailing_zeros(&mut self) -> usize {
        let z = self.0.as_bytes()
            .iter()
            .rev()
            .try_fold(0, |count, &c| if c == b'0' { Ok(count + 1) } else { Err(count) })
            .map_or_else(|z| z, |z| z)
        ;

        self.0.truncate(self.0.len() - z);
        z
    }
}

impl Extend<char> for Numbers {
    fn extend<T>(&mut self, iter: T)
    where
        T: IntoIterator<Item = char>,
    {
        for c in iter {
            if c == '_' {
                continue;
            }
            self.0.push(c);
        }
    }
}

impl Default for Numbers {
    fn default() -> Self {
        Self(String::new())
    }
}

impl TryInto<u128> for Numbers {
    type Error = ParseIntError;

    fn try_into(self) -> Result<u128, Self::Error> {
        Ok(if self.0.len() == 0 {
            0
        } else {
            self.0.parse()?
        })
    }
}

pub fn parser<'a, I>() -> impl 'a + Parser<I, Output = Frac>
where
    I: 'a + RangeStream<Token = char, Range = &'a str>,
    I::Error: ParseError<I::Token, I::Range, I::Position>,
{
    (
        sign(),
        recognize_with_value(number1()).map(|(_, int)| int),
        optional((
            token('.').map(|_| ()),
            optional(recognize_with_value(number()).map(|(_, frac)| frac))
                .map(|frac| frac.unwrap_or_else(|| Default::default()))
        ).map(|(_, frac)| frac))
            .map(|frac| frac.unwrap_or_else(|| Default::default())),
        optional((
            one_of("eE".chars()).map(|_| ()),
            sign(),
            recognize_with_value(number1()).map(|(_, exp)| TryInto::<u128>::try_into(exp).unwrap() as u16)
        ).map(|(_, sign, exp)| if sign { -1 } else { 1 } * exp as i16))
            .map(|exp| exp.unwrap_or_else(|| 0)),
    ).map_input(|(sign, mut int, mut frac, mut exp), input: &mut I| {
        frac.trim_trailing_zeros();
        match frac.0.len() {
            0 => exp += int.trim_trailing_zeros() as i16,
            n => {
                exp -= n as i16;
                int.0.extend(Some(frac.0));
            },
        }
        let numer: u128 = int.try_into().map_err(|_| I::Error::empty(input.position()))?;

        Ok(if numer == 0 {
            Frac::from_int(0)
        } else {
            Frac::from_ratio(if sign { -(numer as i128) } else { numer as i128 }, 1, exp)
        })
    }).flat_map(|r| Ok(r?))
}

fn sign<'a, I>() -> impl Parser<I, Output = bool>
where
    I: RangeStream<Token = char, Range = &'a str>,
    I::Error: ParseError<I::Token, I::Range, I::Position>,
{
    optional(one_of("+-".chars()).map(|sign| match sign {
        '-' => true,
        _ => false,
    })).map(|sign| match sign {
        Some(n) => n,
        _ => false,
    })
}

fn number<'a, I>() -> impl Parser<I, Output = Numbers>
where
    I: RangeStream<Token = char, Range = &'a str>,
    I::Error: ParseError<I::Token, I::Range, I::Position>,
{
    many::<Numbers, _, _>(choice((digit(),token('_'))))
}

fn number1<'a, I>() -> impl Parser<I, Output = Numbers>
where
    I: RangeStream<Token = char, Range = &'a str>,
    I::Error: ParseError<I::Token, I::Range, I::Position>,
{
    many1::<Numbers, _, _>(choice((digit(), token('_'))))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_pi() {
        let left: Frac = "3.14159_26535_89793_23846_26433_83279_50288".parse().unwrap();
        let int = Frac::from_int(3_14159_26535_89793_23846_26433_83279_50288);
        let right = int * Frac::from_exp10(-35);

        assert_eq!(left, right);
    }

    #[test]
    fn test_big() {
        let left: Frac = "3.14159_26535_89793_23846_26433_83279_50288e35".parse().unwrap();
        let right = Frac::from_int(3_14159_26535_89793_23846_26433_83279_50288);

        assert_eq!(left, right);
    }

    #[test]
    fn test_exp() {
        let left: Frac = "3.14e-20".parse().unwrap();
        let int = Frac::from_int(314);
        let right = int * Frac::from_exp10(-22);

        assert_eq!(left, right);
    }

    #[test]
    fn test_trailing_zeros() {
        let left: Frac = "1000.000".parse().unwrap();
        let right = Frac::from_int(1000);

        assert_eq!(left, right);
    }

    #[test]
    fn test_zero() {
        let left: Frac = "0000.000e100".parse().unwrap();
        let right = Frac::default();

        assert_eq!(left, right);
    }

    #[test]
    fn test_simple() {
        let left: Frac = "1e5".parse().unwrap();

        assert_eq!(left.to_f64(), 1e5);
    }
}