go_duration/
nom.rs

1use ::nom::{
2    branch::alt,
3    bytes::complete::{tag, take_till},
4    character::complete::{char, digit0, digit1},
5    combinator::{all_consuming, cut, map_res, opt, value},
6    error::{FromExternalError, ParseError},
7    multi::fold_many1,
8    sequence::{pair, preceded},
9    Err as NomErr, IResult, Parser,
10};
11
12use crate::{
13    GoDuration, GoDurationParseError, NANOS_PER_HOUR, NANOS_PER_MICROSECOND, NANOS_PER_MILLISECOND,
14    NANOS_PER_MINUTE, NANOS_PER_SECOND,
15};
16
17impl<I> ParseError<I> for GoDurationParseError {
18    fn from_error_kind(_input: I, _kind: ::nom::error::ErrorKind) -> Self {
19        GoDurationParseError::InvalidDuration
20    }
21
22    fn append(_input: I, _kind: ::nom::error::ErrorKind, other: Self) -> Self {
23        other
24    }
25}
26
27impl<I, E> FromExternalError<I, E> for GoDurationParseError {
28    fn from_external_error(_input: I, _kind: ::nom::error::ErrorKind, _e: E) -> Self {
29        GoDurationParseError::InvalidDuration
30    }
31}
32
33fn decimal_parts(input: &str) -> IResult<&str, (u64, Option<&str>), GoDurationParseError> {
34    alt((
35        preceded(char::<&str, GoDurationParseError>('.'), digit1).map(|frac| (0, Some(frac))),
36        pair(
37            map_res(digit1, str::parse::<u64>),
38            opt(preceded(char('.'), digit0)),
39        ),
40    ))
41    .parse(input)
42}
43
44fn sign(input: &str) -> IResult<&str, bool, GoDurationParseError> {
45    opt(alt((value(false, char('-')), value(true, char('+')))))
46        .map(|sign| sign.unwrap_or(true))
47        .parse(input)
48}
49
50fn unit(input: &str) -> IResult<&str, u64, GoDurationParseError> {
51    let (input, unit) = take_till(|c: char| c.is_ascii_digit() || c == '.')(input)?;
52    if unit.is_empty() {
53        return Err(NomErr::Error(GoDurationParseError::MissingUnit));
54    }
55    let (_, unit) = all_consuming(alt((
56        value(1u64, tag::<&str, &str, GoDurationParseError>("ns")),
57        value(NANOS_PER_MICROSECOND, tag("\u{00B5}s")),
58        value(NANOS_PER_MICROSECOND, tag("\u{03BC}s")),
59        value(NANOS_PER_MICROSECOND, tag("us")),
60        value(NANOS_PER_MILLISECOND, tag("ms")),
61        value(NANOS_PER_SECOND, char('s')),
62        value(NANOS_PER_MINUTE, char('m')),
63        value(NANOS_PER_HOUR, char('h')),
64    )))
65    .parse(unit)
66    .map_err(|_| NomErr::Error(GoDurationParseError::UnknownUnit(unit.to_string())))?;
67    Ok((input, unit))
68}
69
70pub fn go_duration(input: &str) -> IResult<&str, GoDuration, GoDurationParseError> {
71    let (input, sign) = sign(input)?;
72    let (input, nanos) = all_consuming(fold_many1(
73        (decimal_parts, cut(unit)).map(|((int, frac), scale)| {
74            let nanos = frac.map_or(0, |frac: &str| {
75                let mut total = 0.0;
76                let mut scale = scale as f64;
77                for c in frac.chars() {
78                    scale /= 10.0;
79                    total += scale * f64::from(c.to_digit(10).unwrap());
80                }
81                total as u64
82            });
83            int.saturating_mul(scale).saturating_add(nanos)
84        }),
85        || 0u64,
86        u64::saturating_add,
87    ))
88    .parse(input)?;
89
90    let nanos = if sign {
91        i64::try_from(nanos).map_err(|_| NomErr::Error(GoDurationParseError::InvalidDuration))?
92    } else {
93        0i64.checked_sub_unsigned(nanos)
94            .ok_or(NomErr::Error(GoDurationParseError::InvalidDuration))?
95    };
96    Ok((input, GoDuration(nanos)))
97}
98
99#[cfg(test)]
100mod tests {
101    use nom::{combinator::map_res, Finish};
102
103    use crate::GoDurationParseError;
104
105    use super::*;
106
107    #[test]
108    fn test_from_external_error_trait() {
109        let output = map_res(::nom::combinator::rest, str::parse::<u64>)
110            .parse("invalid")
111            .finish();
112        assert!(output.is_err());
113        let output: GoDurationParseError = output.unwrap_err();
114        assert_eq!(output, GoDurationParseError::InvalidDuration);
115    }
116}