hcl-edit 0.9.3

Parse and modify HCL while preserving comments and whitespace
Documentation
use super::prelude::*;

use crate::Number;

use std::str::FromStr;
use winnow::ascii::digit1;
use winnow::combinator::{alt, cut_err, opt, preceded, terminated};
use winnow::token::one_of;

pub(super) fn number(input: &mut Input) -> ModalResult<Number> {
    alt((
        float.verify_map(Number::from_f64),
        integer.map(Number::from),
    ))
    .parse_next(input)
}

fn integer(input: &mut Input) -> ModalResult<u64> {
    digit1.try_map(|s: &str| u64::from_str(s)).parse_next(input)
}

fn float(input: &mut Input) -> ModalResult<f64> {
    let fraction = preceded('.', digit1);

    terminated(digit1, alt((terminated(fraction, opt(exponent)), exponent)))
        .take()
        .try_map(|s: &str| f64::from_str(s))
        .parse_next(input)
}

fn exponent<'a>(input: &mut Input<'a>) -> ModalResult<&'a str> {
    (
        one_of(b"eE"),
        opt(one_of(b"+-")),
        cut_err(digit1).context(StrContext::Expected(StrContextValue::Description("digit"))),
    )
        .take()
        .parse_next(input)
}

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

    #[test]
    fn parse_integer() {
        let tests: &[(&str, u64)] = &[
            ("1", 1),
            ("99", 99),
            ("0", 0),
            ("18446744073709551615", u64::MAX),
        ];

        for (input, expected) in tests {
            let parsed = integer.parse(Input::new(input));
            assert!(parsed.is_ok(), "expected `{input}` to parse correctly");
            assert_eq!(parsed.unwrap(), *expected);
        }
    }

    #[test]
    // Strict comparison is safe because we don't do any math with these floats
    #[allow(clippy::float_cmp)]
    fn parse_float() {
        let tests: &[(&str, f64)] = &[
            ("1.0", 1.0),
            ("1e10", 10_000_000_000.0),
            ("2.5E3", 2500.0),
            ("42e-3", 0.042),
            ("0.1E-4", 0.00001),
            ("1.7976931348623157e308", f64::MAX),
        ];

        for (input, expected) in tests {
            let parsed = float.parse(Input::new(input));
            assert!(parsed.is_ok(), "expected `{input}` to parse correctly");
            assert_eq!(parsed.unwrap(), *expected);
        }
    }
}