trivet 3.1.0

The trivet Parser Library
Documentation
// Trivet
// Copyright (c) 2025 by Stacy Prowell.  All rights reserved.
// https://gitlab.com/binary-tools/trivet

//! Parse decimal-encoded floating point numbers.

use crate::{
    errors::{syntax_error, ParseResult},
    ParserCore,
};

use super::NumberParserSettings;

/// Parse a floating point number represented in decimal.  This presumes the sign and any radix
/// signifier have been parsed, and the parser is at the first digit of the decimal number.
/// If the number is negative, indicate this with the `negative` argument.  If underscores are
/// permitted, indicate this with the `underscores` argument.
///
/// This method does not handle `inf`, `infinity`, or `nan`.  Parse those separately.
///
/// Non-decimal digits or an extra decimal point cause an error.  If no digits are found,
/// this will cause an error.  A character that causes the error is consumed.
///
/// If there are too many significant digits then the mantissa will overflow and this will generate
/// an error.
///
/// For example, the following are ways to encode 1/2.
///
/// - `0.5`
/// - `5e-1`
/// - `0.500000`
/// - `.5`
/// - `5000e-4`
///
pub fn parse_decimal_float(
    parser: &mut ParserCore,
    negative: bool,
    settings: &NumberParserSettings,
) -> ParseResult<f64> {
    let loc = parser.loc();

    // Handle negative.
    let mut digits = String::new();
    if negative {
        digits.push('-');
    }

    // Accumulate all the digits of the number.
    let wholedigs = if settings.permit_underscores {
        parser.take_while_unless(|ch| ch.is_ascii_digit(), |ch| ch == '_')
    } else {
        parser.take_while(|ch| ch.is_ascii_digit())
    };
    digits.push_str(wholedigs.as_str());

    // Look for decimal.
    let mut fraction_empty = false;
    if parser.peek_and_consume('.') {
        let fracdigs = if settings.permit_underscores {
            parser.take_while_unless(|ch| ch.is_ascii_digit(), |ch| ch == '_')
        } else {
            parser.take_while(|ch| ch.is_ascii_digit())
        };
        digits.push('.');
        digits.push_str(fracdigs.as_str());
        fraction_empty = fracdigs.is_empty();
    }

    // Look for an exponent.
    let ch = parser.peek();
    if ch == 'e' || ch == 'p' || ch == 'P' || ch == 'E' {
        parser.consume();
        let negexp = if parser.peek_and_consume('-') {
            true
        } else {
            parser.peek_and_consume('+');
            false
        };
        let expdigits = if settings.permit_underscores {
            parser.take_while_unless(|ch| ch.is_ascii_digit(), |ch| ch == '_')
        } else {
            parser.take_while(|ch| ch.is_ascii_digit())
        };
        digits.push('e');
        if negexp {
            digits.push('-');
        }
        digits.push_str(expdigits.as_str());
    }

    if (!settings.permit_leading_zero) && wholedigs.starts_with('0') && wholedigs.len() > 1 {
        return Err(syntax_error(
            loc,
            "A leading zero is not permitted for a non-zero whole part of a number.",
        ));
    }

    if wholedigs.is_empty() && !settings.permit_empty_whole {
        return Err(syntax_error(
            loc,
            "An empty whole part is not permitted; there must be digits to the left of the decimal."
        ));
    }

    if fraction_empty && !settings.permit_empty_fraction {
        return Err(syntax_error(
            loc,
            "An empty fraction part is not permitted; there must be digits to the right of the decimal, if present."
        ));
    }

    // Convert to float.
    match digits.parse::<f64>() {
        Ok(value) => Ok(value),
        Err(msg) => Err(syntax_error(loc, &msg.to_string())),
    }
}

#[cfg(test)]
mod test {
    use crate::{
        decoder::Decode,
        numbers::{decimal_float::parse_decimal_float, NumberParserSettings},
        ParserCore,
    };

    fn parse(value: &str) -> ParserCore {
        let decoder = Decode::from_string(value);
        ParserCore::new("<string>>", decoder)
    }

    #[test]
    fn zero_test() {
        let mut settings = NumberParserSettings::new();
        settings.permit_underscores = false;
        assert!(parse_decimal_float(&mut parse(""), false, &settings).is_err());
        assert_eq!(
            parse_decimal_float(&mut parse("0"), false, &settings).unwrap(),
            0.0
        );
        assert_eq!(
            parse_decimal_float(&mut parse("0"), true, &settings).unwrap(),
            0.0
        );
    }

    #[test]
    fn one_test() {
        let mut settings = NumberParserSettings::new();
        settings.permit_underscores = false;
        assert_eq!(
            parse_decimal_float(&mut parse("1"), false, &settings).unwrap(),
            1.0
        );
        assert_eq!(
            parse_decimal_float(&mut parse("1"), true, &settings).unwrap(),
            -1.0
        );
        assert_eq!(
            parse_decimal_float(&mut parse("01"), false, &settings).unwrap(),
            1.0
        );
        assert_eq!(
            parse_decimal_float(&mut parse("001"), true, &settings).unwrap(),
            -1.0
        );
    }

    #[test]
    fn integer_test_1() {
        let mut settings = NumberParserSettings::new();
        settings.permit_underscores = false;
        assert_eq!(
            parse_decimal_float(&mut parse("15"), false, &settings).unwrap(),
            15.0
        );
        assert_eq!(
            parse_decimal_float(&mut parse("8"), true, &settings).unwrap(),
            -8.0
        );
        assert_eq!(
            parse_decimal_float(&mut parse("33252"), true, &settings).unwrap(),
            -33252.0
        );
        assert_eq!(
            parse_decimal_float(&mut parse("8480"), true, &settings).unwrap(),
            -8480.0
        );
    }

    #[test]
    fn integer_test_2() {
        let mut settings = NumberParserSettings::new();
        settings.permit_underscores = false;
        assert_eq!(
            parse_decimal_float(&mut parse("111"), false, &settings).unwrap(),
            111.0
        );
        settings.permit_underscores = true;
        assert_eq!(
            parse_decimal_float(&mut parse("18658"), false, &settings).unwrap(),
            18658.0
        );
        assert_eq!(
            parse_decimal_float(&mut parse("7177605032489779"), true, &settings).unwrap(),
            -7177605032489779.0
        );
        settings.permit_underscores = true;
        assert_eq!(
            parse_decimal_float(&mut parse("665"), false, &settings).unwrap(),
            665.0
        );
    }

    #[test]
    fn fraction_test_1() {
        let mut settings = NumberParserSettings::new();
        settings.permit_underscores = false;
        assert_eq!(
            parse_decimal_float(&mut parse("0.1"), false, &settings).unwrap(),
            0.1
        );
        assert_eq!(
            parse_decimal_float(&mut parse("0.02"), false, &settings).unwrap(),
            0.02
        );
        assert_eq!(
            parse_decimal_float(&mut parse("0.8"), false, &settings).unwrap(),
            0.8
        );
        assert_eq!(
            parse_decimal_float(&mut parse("1.8"), false, &settings).unwrap(),
            1.8
        );
    }

    #[test]
    fn fraction_test_2() {
        let mut settings = NumberParserSettings::new();
        settings.permit_underscores = false;
        assert_eq!(
            parse_decimal_float(&mut parse("0.08"), false, &settings).unwrap(),
            0.08
        );
        assert_eq!(
            parse_decimal_float(&mut parse("10.008"), false, &settings).unwrap(),
            10.008
        );
        assert_eq!(
            parse_decimal_float(&mut parse("14.014"), false, &settings).unwrap(),
            14.014
        );
        assert_eq!(
            parse_decimal_float(&mut parse("000.0000"), false, &settings).unwrap(),
            0.0
        );
    }

    #[test]
    fn exponent_test_1() {
        let mut settings = NumberParserSettings::new();
        settings.permit_underscores = false;
        assert_eq!(
            parse_decimal_float(&mut parse("0p1000"), false, &settings).unwrap(),
            0.0
        );
        assert_eq!(
            parse_decimal_float(&mut parse("10p1"), false, &settings).unwrap(),
            100.0
        );
        assert_eq!(
            parse_decimal_float(&mut parse("10p-1"), false, &settings).unwrap(),
            1.0
        );
        assert_eq!(
            parse_decimal_float(&mut parse("0.001p3"), false, &settings).unwrap(),
            1.0
        );
    }

    #[test]
    fn exponent_test_2() {
        let mut settings = NumberParserSettings::new();
        settings.permit_underscores = false;
        assert_eq!(
            parse_decimal_float(&mut parse("1021p-3"), false, &settings).unwrap(),
            1.021
        );
        assert_eq!(
            parse_decimal_float(&mut parse("44p-3"), false, &settings).unwrap(),
            0.044
        );
    }

    #[test]
    fn limits_test() {
        let mut settings = NumberParserSettings::new();
        settings.permit_underscores = true;
        assert_eq!(
            parse_decimal_float(&mut parse("1.7976931348623157E+308"), false, &settings).unwrap(),
            f64::MAX
        );
        assert_eq!(
            parse_decimal_float(&mut parse("1.7976931348623157E+308"), true, &settings).unwrap(),
            f64::MIN
        );
        assert_eq!(
            parse_decimal_float(&mut parse("2.2250738585072014E-308"), false, &settings).unwrap(),
            f64::MIN_POSITIVE
        );
    }

    #[test]
    fn overflow_test() {
        let mut settings = NumberParserSettings::new();
        settings.permit_underscores = false;
        assert_eq!(
            parse_decimal_float(&mut parse("2p309"), false, &settings).unwrap(),
            f64::INFINITY
        );
        assert_eq!(
            parse_decimal_float(&mut parse("1p-324"), false, &settings).unwrap(),
            0.0
        );
    }

    #[test]
    fn leading_zero_test() {
        let mut settings = NumberParserSettings::new();
        settings.permit_leading_zero = false;
        assert!(parse_decimal_float(&mut parse("01.1"), false, &settings).is_err());
        settings.permit_leading_zero = true;
        assert_eq!(
            parse_decimal_float(&mut parse("01.1"), false, &settings).unwrap(),
            1.1
        );
    }

    #[test]
    fn empty_parts_test() {
        let mut settings = NumberParserSettings::new();
        settings.permit_empty_fraction = false;
        settings.permit_empty_whole = false;
        assert!(parse_decimal_float(&mut parse("1.e1"), false, &settings).is_err());
        assert!(parse_decimal_float(&mut parse("1."), false, &settings).is_err());
        assert!(parse_decimal_float(&mut parse(".1e1"), false, &settings).is_err());
        assert!(parse_decimal_float(&mut parse(".1"), false, &settings).is_err());
    }
}