time/parsing/combinator/
mod.rs

1//! Implementations of the low-level parser combinators.
2
3pub(crate) mod rfc;
4
5use num_conv::prelude::*;
6
7use crate::format_description::modifier::Padding;
8use crate::parsing::shim::{Integer, IntegerParseBytes};
9use crate::parsing::ParsedItem;
10
11/// Parse a "+" or "-" sign. Returns the ASCII byte representing the sign, if present.
12pub(crate) const fn sign(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
13    match input {
14        [sign @ (b'-' | b'+'), remaining @ ..] => Some(ParsedItem(remaining, *sign)),
15        _ => None,
16    }
17}
18
19/// Consume the first matching item, returning its associated value.
20pub(crate) fn first_match<'a, T>(
21    options: impl IntoIterator<Item = (&'a [u8], T)>,
22    case_sensitive: bool,
23) -> impl FnMut(&'a [u8]) -> Option<ParsedItem<'a, T>> {
24    let mut options = options.into_iter();
25    move |input| {
26        options.find_map(|(expected, t)| {
27            if case_sensitive {
28                Some(ParsedItem(input.strip_prefix(expected)?, t))
29            } else {
30                let n = expected.len();
31                if n <= input.len() {
32                    let (head, tail) = input.split_at(n);
33                    if head.eq_ignore_ascii_case(expected) {
34                        return Some(ParsedItem(tail, t));
35                    }
36                }
37                None
38            }
39        })
40    }
41}
42
43/// Consume zero or more instances of the provided parser. The parser must return the unit value.
44pub(crate) fn zero_or_more<'a, P: Fn(&'a [u8]) -> Option<ParsedItem<'a, ()>>>(
45    parser: P,
46) -> impl FnMut(&'a [u8]) -> ParsedItem<'a, ()> {
47    move |mut input| {
48        while let Some(remaining) = parser(input) {
49            input = remaining.into_inner();
50        }
51        ParsedItem(input, ())
52    }
53}
54
55/// Consume one of or more instances of the provided parser. The parser must produce the unit value.
56pub(crate) fn one_or_more<'a, P: Fn(&'a [u8]) -> Option<ParsedItem<'a, ()>>>(
57    parser: P,
58) -> impl Fn(&'a [u8]) -> Option<ParsedItem<'a, ()>> {
59    move |mut input| {
60        input = parser(input)?.into_inner();
61        while let Some(remaining) = parser(input) {
62            input = remaining.into_inner();
63        }
64        Some(ParsedItem(input, ()))
65    }
66}
67
68/// Consume between `n` and `m` instances of the provided parser.
69pub(crate) fn n_to_m<
70    'a,
71    const N: u8,
72    const M: u8,
73    T,
74    P: Fn(&'a [u8]) -> Option<ParsedItem<'a, T>>,
75>(
76    parser: P,
77) -> impl Fn(&'a [u8]) -> Option<ParsedItem<'a, &'a [u8]>> {
78    debug_assert!(M >= N);
79    move |mut input| {
80        // We need to keep this to determine the total length eventually consumed.
81        let orig_input = input;
82
83        // Mandatory
84        for _ in 0..N {
85            input = parser(input)?.0;
86        }
87
88        // Optional
89        for _ in N..M {
90            match parser(input) {
91                Some(parsed) => input = parsed.0,
92                None => break,
93            }
94        }
95
96        Some(ParsedItem(
97            input,
98            &orig_input[..(orig_input.len() - input.len())],
99        ))
100    }
101}
102
103/// Consume between `n` and `m` digits, returning the numerical value.
104pub(crate) fn n_to_m_digits<const N: u8, const M: u8, T: Integer>(
105    input: &[u8],
106) -> Option<ParsedItem<'_, T>> {
107    debug_assert!(M >= N);
108    n_to_m::<N, M, _, _>(any_digit)(input)?.flat_map(|value| value.parse_bytes())
109}
110
111/// Consume exactly `n` digits, returning the numerical value.
112pub(crate) fn exactly_n_digits<const N: u8, T: Integer>(input: &[u8]) -> Option<ParsedItem<'_, T>> {
113    n_to_m_digits::<N, N, _>(input)
114}
115
116/// Consume exactly `n` digits, returning the numerical value.
117pub(crate) fn exactly_n_digits_padded<'a, const N: u8, T: Integer>(
118    padding: Padding,
119) -> impl Fn(&'a [u8]) -> Option<ParsedItem<'a, T>> {
120    n_to_m_digits_padded::<N, N, _>(padding)
121}
122
123/// Consume between `n` and `m` digits, returning the numerical value.
124pub(crate) fn n_to_m_digits_padded<'a, const N: u8, const M: u8, T: Integer>(
125    padding: Padding,
126) -> impl Fn(&'a [u8]) -> Option<ParsedItem<'a, T>> {
127    debug_assert!(M >= N);
128    move |mut input| match padding {
129        Padding::None => n_to_m_digits::<1, M, _>(input),
130        Padding::Space => {
131            debug_assert!(N > 0);
132
133            let mut orig_input = input;
134            for _ in 0..(N - 1) {
135                match ascii_char::<b' '>(input) {
136                    Some(parsed) => input = parsed.0,
137                    None => break,
138                }
139            }
140            let pad_width = (orig_input.len() - input.len()).truncate::<u8>();
141
142            orig_input = input;
143            for _ in 0..(N - pad_width) {
144                input = any_digit(input)?.0;
145            }
146            for _ in N..M {
147                match any_digit(input) {
148                    Some(parsed) => input = parsed.0,
149                    None => break,
150                }
151            }
152
153            ParsedItem(input, &orig_input[..(orig_input.len() - input.len())])
154                .flat_map(|value| value.parse_bytes())
155        }
156        Padding::Zero => n_to_m_digits::<N, M, _>(input),
157    }
158}
159
160/// Consume exactly one digit.
161pub(crate) const fn any_digit(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
162    match input {
163        [c, remaining @ ..] if c.is_ascii_digit() => Some(ParsedItem(remaining, *c)),
164        _ => None,
165    }
166}
167
168/// Consume exactly one of the provided ASCII characters.
169pub(crate) fn ascii_char<const CHAR: u8>(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
170    debug_assert!(CHAR.is_ascii_graphic() || CHAR.is_ascii_whitespace());
171    match input {
172        [c, remaining @ ..] if *c == CHAR => Some(ParsedItem(remaining, ())),
173        _ => None,
174    }
175}
176
177/// Consume exactly one of the provided ASCII characters, case-insensitive.
178pub(crate) fn ascii_char_ignore_case<const CHAR: u8>(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
179    debug_assert!(CHAR.is_ascii_graphic() || CHAR.is_ascii_whitespace());
180    match input {
181        [c, remaining @ ..] if c.eq_ignore_ascii_case(&CHAR) => Some(ParsedItem(remaining, ())),
182        _ => None,
183    }
184}
185
186/// Optionally consume an input with a given parser.
187pub(crate) fn opt<'a, T>(
188    parser: impl Fn(&'a [u8]) -> Option<ParsedItem<'a, T>>,
189) -> impl Fn(&'a [u8]) -> ParsedItem<'a, Option<T>> {
190    move |input| match parser(input) {
191        Some(value) => value.map(Some),
192        None => ParsedItem(input, None),
193    }
194}