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}