time/parsing/combinator/
mod.rs

1//! Implementations of the low-level parser combinators.
2
3pub(crate) mod rfc;
4
5use crate::format_description::modifier::Padding;
6use crate::parsing::ParsedItem;
7use crate::parsing::shim::Integer;
8
9/// The sign of a number.
10#[allow(
11    clippy::missing_docs_in_private_items,
12    reason = "self-explanatory variants"
13)]
14#[derive(Debug)]
15pub(crate) enum Sign {
16    Negative,
17    Positive,
18}
19
20/// Parse a "+" or "-" sign.
21#[inline]
22pub(crate) const fn sign(input: &[u8]) -> Option<ParsedItem<'_, Sign>> {
23    match input {
24        [b'-', remaining @ ..] => Some(ParsedItem(remaining, Sign::Negative)),
25        [b'+', remaining @ ..] => Some(ParsedItem(remaining, Sign::Positive)),
26        _ => None,
27    }
28}
29
30/// Consume the first matching item, returning its associated value.
31#[inline]
32pub(crate) fn first_match<'a, T, I>(
33    options: I,
34    case_sensitive: bool,
35) -> impl for<'b> FnMut(&'b [u8]) -> Option<ParsedItem<'b, T>>
36where
37    I: IntoIterator<Item = (&'a [u8], T)>,
38{
39    let mut options = options.into_iter();
40    move |input| {
41        if case_sensitive {
42            options.find_map(|(expected, t)| Some(ParsedItem(input.strip_prefix(expected)?, t)))
43        } else {
44            options.find_map(|(expected, t)| {
45                let n = expected.len();
46                if n <= input.len() {
47                    let (head, tail) = input.split_at(n);
48                    if head.eq_ignore_ascii_case(expected) {
49                        return Some(ParsedItem(tail, t));
50                    }
51                }
52                None
53            })
54        }
55    }
56}
57
58/// Consume zero or more instances of the provided parser. The parser must return the unit value.
59#[inline]
60pub(crate) fn zero_or_more<P>(parser: P) -> impl for<'a> FnMut(&'a [u8]) -> ParsedItem<'a, ()>
61where
62    P: for<'a> Fn(&'a [u8]) -> Option<ParsedItem<'a, ()>>,
63{
64    move |mut input| {
65        while let Some(remaining) = parser(input) {
66            input = remaining.into_inner();
67        }
68        ParsedItem(input, ())
69    }
70}
71
72/// Consume one of or more instances of the provided parser. The parser must produce the unit value.
73#[inline]
74pub(crate) fn one_or_more<P>(parser: P) -> impl for<'a> Fn(&'a [u8]) -> Option<ParsedItem<'a, ()>>
75where
76    P: for<'a> Fn(&'a [u8]) -> Option<ParsedItem<'a, ()>>,
77{
78    move |mut input| {
79        input = parser(input)?.into_inner();
80        while let Some(remaining) = parser(input) {
81            input = remaining.into_inner();
82        }
83        Some(ParsedItem(input, ()))
84    }
85}
86
87/// Consume between `n` and `m` digits, returning the numerical value.
88#[inline]
89pub(crate) fn n_to_m_digits<const N: u8, const M: u8, T>(
90    mut input: &[u8],
91) -> Option<ParsedItem<'_, T>>
92where
93    T: Integer,
94{
95    const {
96        assert!(N > 0);
97        assert!(M >= N);
98    }
99
100    let mut value = T::ZERO;
101
102    // Mandatory
103    for i in 0..N {
104        let digit;
105        ParsedItem(input, digit) = any_digit(input)?;
106
107        if i != T::MAX_NUM_DIGITS - 1 {
108            value = value.push_digit(digit - b'0');
109        } else {
110            value = value.checked_push_digit(digit - b'0')?;
111        }
112    }
113
114    // Optional
115    for i in N..M {
116        let Some(ParsedItem(new_input, digit)) = any_digit(input) else {
117            break;
118        };
119        input = new_input;
120
121        if i != T::MAX_NUM_DIGITS - 1 {
122            value = value.push_digit(digit - b'0');
123        } else {
124            value = value.checked_push_digit(digit - b'0')?;
125        }
126    }
127
128    Some(ParsedItem(input, value))
129}
130
131/// Consume one or two digits, returning the numerical value.
132#[inline]
133pub(crate) fn one_or_two_digits(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
134    match input {
135        [a @ b'0'..=b'9', b @ b'0'..=b'9', remaining @ ..] => {
136            let a = *a - b'0';
137            let b = *b - b'0';
138            Some(ParsedItem(remaining, a * 10 + b))
139        }
140        [a @ b'0'..=b'9', remaining @ ..] => {
141            let a = *a - b'0';
142            Some(ParsedItem(remaining, a))
143        }
144        _ => None,
145    }
146}
147
148/// Parse an exact number of digits without padding.
149#[derive(Debug)]
150pub(crate) struct ExactlyNDigits<const N: u8>;
151
152impl ExactlyNDigits<1> {
153    /// Consume exactly one digit.
154    #[inline]
155    pub(crate) const fn parse(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
156        match input {
157            [a @ b'0'..=b'9', remaining @ ..] => Some(ParsedItem(remaining, *a - b'0')),
158            _ => None,
159        }
160    }
161}
162
163impl ExactlyNDigits<2> {
164    /// Consume exactly two digits.
165    #[inline]
166    pub(crate) const fn parse(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
167        match input {
168            [a @ b'0'..=b'9', b @ b'0'..=b'9', remaining @ ..] => {
169                let a = *a - b'0';
170                let b = *b - b'0';
171                Some(ParsedItem(remaining, a * 10 + b))
172            }
173            _ => None,
174        }
175    }
176}
177
178impl ExactlyNDigits<3> {
179    /// Consume exactly three digits.
180    #[inline]
181    pub(crate) const fn parse(input: &[u8]) -> Option<ParsedItem<'_, u16>> {
182        match input {
183            [
184                a @ b'0'..=b'9',
185                b @ b'0'..=b'9',
186                c @ b'0'..=b'9',
187                remaining @ ..,
188            ] => {
189                let a = (*a - b'0') as u16;
190                let b = (*b - b'0') as u16;
191                let c = (*c - b'0') as u16;
192                Some(ParsedItem(remaining, a * 100 + b * 10 + c))
193            }
194            _ => None,
195        }
196    }
197}
198
199impl ExactlyNDigits<4> {
200    /// Consume exactly four digits.
201    #[inline]
202    pub(crate) const fn parse(input: &[u8]) -> Option<ParsedItem<'_, u16>> {
203        match input {
204            [
205                a @ b'0'..=b'9',
206                b @ b'0'..=b'9',
207                c @ b'0'..=b'9',
208                d @ b'0'..=b'9',
209                remaining @ ..,
210            ] => {
211                let a = (*a - b'0') as u16;
212                let b = (*b - b'0') as u16;
213                let c = (*c - b'0') as u16;
214                let d = (*d - b'0') as u16;
215                Some(ParsedItem(remaining, a * 1000 + b * 100 + c * 10 + d))
216            }
217            _ => None,
218        }
219    }
220}
221
222impl ExactlyNDigits<5> {
223    /// Consume exactly five digits.
224    #[inline]
225    pub(crate) const fn parse(input: &[u8]) -> Option<ParsedItem<'_, u32>> {
226        match input {
227            [
228                a @ b'0'..=b'9',
229                b @ b'0'..=b'9',
230                c @ b'0'..=b'9',
231                d @ b'0'..=b'9',
232                e @ b'0'..=b'9',
233                remaining @ ..,
234            ] => {
235                let a = (*a - b'0') as u32;
236                let b = (*b - b'0') as u32;
237                let c = (*c - b'0') as u32;
238                let d = (*d - b'0') as u32;
239                let e = (*e - b'0') as u32;
240                Some(ParsedItem(
241                    remaining,
242                    a * 10000 + b * 1000 + c * 100 + d * 10 + e,
243                ))
244            }
245            _ => None,
246        }
247    }
248}
249
250impl ExactlyNDigits<6> {
251    /// Consume exactly six digits.
252    #[inline]
253    pub(crate) const fn parse(input: &[u8]) -> Option<ParsedItem<'_, u32>> {
254        match input {
255            [
256                a @ b'0'..=b'9',
257                b @ b'0'..=b'9',
258                c @ b'0'..=b'9',
259                d @ b'0'..=b'9',
260                e @ b'0'..=b'9',
261                f @ b'0'..=b'9',
262                remaining @ ..,
263            ] => {
264                let a = (*a - b'0') as u32;
265                let b = (*b - b'0') as u32;
266                let c = (*c - b'0') as u32;
267                let d = (*d - b'0') as u32;
268                let e = (*e - b'0') as u32;
269                let f = (*f - b'0') as u32;
270                Some(ParsedItem(
271                    remaining,
272                    a * 100000 + b * 10000 + c * 1000 + d * 100 + e * 10 + f,
273                ))
274            }
275            _ => None,
276        }
277    }
278}
279
280impl ExactlyNDigits<7> {
281    /// Consume exactly seven digits.
282    #[inline]
283    pub(crate) const fn parse(input: &[u8]) -> Option<ParsedItem<'_, u32>> {
284        match input {
285            [
286                a @ b'0'..=b'9',
287                b @ b'0'..=b'9',
288                c @ b'0'..=b'9',
289                d @ b'0'..=b'9',
290                e @ b'0'..=b'9',
291                f @ b'0'..=b'9',
292                g @ b'0'..=b'9',
293                remaining @ ..,
294            ] => {
295                let a = (*a - b'0') as u32;
296                let b = (*b - b'0') as u32;
297                let c = (*c - b'0') as u32;
298                let d = (*d - b'0') as u32;
299                let e = (*e - b'0') as u32;
300                let f = (*f - b'0') as u32;
301                let g = (*g - b'0') as u32;
302                Some(ParsedItem(
303                    remaining,
304                    a * 1_000_000 + b * 100_000 + c * 10_000 + d * 1_000 + e * 100 + f * 10 + g,
305                ))
306            }
307            _ => None,
308        }
309    }
310}
311
312impl ExactlyNDigits<8> {
313    /// Consume exactly eight digits.
314    #[inline]
315    pub(crate) const fn parse(input: &[u8]) -> Option<ParsedItem<'_, u32>> {
316        match input {
317            [
318                a @ b'0'..=b'9',
319                b @ b'0'..=b'9',
320                c @ b'0'..=b'9',
321                d @ b'0'..=b'9',
322                e @ b'0'..=b'9',
323                f @ b'0'..=b'9',
324                g @ b'0'..=b'9',
325                h @ b'0'..=b'9',
326                remaining @ ..,
327            ] => {
328                let a = (*a - b'0') as u32;
329                let b = (*b - b'0') as u32;
330                let c = (*c - b'0') as u32;
331                let d = (*d - b'0') as u32;
332                let e = (*e - b'0') as u32;
333                let f = (*f - b'0') as u32;
334                let g = (*g - b'0') as u32;
335                let h = (*h - b'0') as u32;
336                Some(ParsedItem(
337                    remaining,
338                    a * 10_000_000
339                        + b * 1_000_000
340                        + c * 100_000
341                        + d * 10_000
342                        + e * 1_000
343                        + f * 100
344                        + g * 10
345                        + h,
346                ))
347            }
348            _ => None,
349        }
350    }
351}
352
353impl ExactlyNDigits<9> {
354    /// Consume exactly nine digits.
355    #[inline]
356    pub(crate) const fn parse(input: &[u8]) -> Option<ParsedItem<'_, u32>> {
357        match input {
358            [
359                a @ b'0'..=b'9',
360                b @ b'0'..=b'9',
361                c @ b'0'..=b'9',
362                d @ b'0'..=b'9',
363                e @ b'0'..=b'9',
364                f @ b'0'..=b'9',
365                g @ b'0'..=b'9',
366                h @ b'0'..=b'9',
367                i @ b'0'..=b'9',
368                remaining @ ..,
369            ] => {
370                let a = (*a - b'0') as u32;
371                let b = (*b - b'0') as u32;
372                let c = (*c - b'0') as u32;
373                let d = (*d - b'0') as u32;
374                let e = (*e - b'0') as u32;
375                let f = (*f - b'0') as u32;
376                let g = (*g - b'0') as u32;
377                let h = (*h - b'0') as u32;
378                let i = (*i - b'0') as u32;
379                Some(ParsedItem(
380                    remaining,
381                    a * 100_000_000
382                        + b * 10_000_000
383                        + c * 1_000_000
384                        + d * 100_000
385                        + e * 10_000
386                        + f * 1_000
387                        + g * 100
388                        + h * 10
389                        + i,
390                ))
391            }
392            _ => None,
393        }
394    }
395}
396
397/// Consume exactly `n` digits, returning the numerical value.
398pub(crate) fn exactly_n_digits_padded<const N: u8, T>(
399    padding: Padding,
400) -> impl for<'a> Fn(&'a [u8]) -> Option<ParsedItem<'a, T>>
401where
402    T: Integer,
403{
404    n_to_m_digits_padded::<N, N, _>(padding)
405}
406
407/// Consume between `n` and `m` digits, returning the numerical value.
408pub(crate) fn n_to_m_digits_padded<const N: u8, const M: u8, T>(
409    padding: Padding,
410) -> impl for<'a> Fn(&'a [u8]) -> Option<ParsedItem<'a, T>>
411where
412    T: Integer,
413{
414    const {
415        assert!(N > 0);
416        assert!(M >= N);
417    }
418
419    move |mut input| match padding {
420        Padding::None => n_to_m_digits::<1, M, _>(input),
421        Padding::Space => {
422            let mut value = T::ZERO;
423
424            // Consume the padding.
425            let mut pad_width = 0;
426            for _ in 0..(N - 1) {
427                match ascii_char::<b' '>(input) {
428                    Some(parsed) => {
429                        pad_width += 1;
430                        input = parsed.0;
431                    }
432                    None => break,
433                }
434            }
435
436            // Mandatory
437            for i in 0..(N - pad_width) {
438                let digit;
439                ParsedItem(input, digit) = any_digit(input)?;
440
441                value = if i != T::MAX_NUM_DIGITS - 1 {
442                    value.push_digit(digit - b'0')
443                } else {
444                    value.checked_push_digit(digit - b'0')?
445                };
446            }
447
448            // Optional
449            for i in N..M {
450                let Some(ParsedItem(new_input, digit)) = any_digit(input) else {
451                    break;
452                };
453                input = new_input;
454
455                value = if i - pad_width != T::MAX_NUM_DIGITS - 1 {
456                    value.push_digit(digit - b'0')
457                } else {
458                    value.checked_push_digit(digit - b'0')?
459                };
460            }
461
462            Some(ParsedItem(input, value))
463        }
464        Padding::Zero => n_to_m_digits::<N, M, _>(input),
465    }
466}
467
468/// Consume exactly one digit.
469#[inline]
470pub(crate) const fn any_digit(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
471    match input {
472        [c @ b'0'..=b'9', remaining @ ..] => Some(ParsedItem(remaining, *c)),
473        _ => None,
474    }
475}
476
477/// Consume exactly one of the provided ASCII characters.
478#[inline]
479pub(crate) fn ascii_char<const CHAR: u8>(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
480    const {
481        assert!(CHAR.is_ascii_graphic() || CHAR.is_ascii_whitespace());
482    }
483    match input {
484        [c, remaining @ ..] if *c == CHAR => Some(ParsedItem(remaining, ())),
485        _ => None,
486    }
487}
488
489/// Consume exactly one of the provided ASCII characters, case-insensitive.
490#[inline]
491pub(crate) fn ascii_char_ignore_case<const CHAR: u8>(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
492    const {
493        assert!(CHAR.is_ascii_graphic() || CHAR.is_ascii_whitespace());
494    }
495    match input {
496        [c, remaining @ ..] if c.eq_ignore_ascii_case(&CHAR) => Some(ParsedItem(remaining, ())),
497        _ => None,
498    }
499}
500
501/// Optionally consume an input with a given parser.
502#[inline]
503pub(crate) fn opt<T>(
504    parser: impl for<'a> Fn(&'a [u8]) -> Option<ParsedItem<'a, T>>,
505) -> impl for<'a> Fn(&'a [u8]) -> ParsedItem<'a, Option<T>> {
506    move |input| match parser(input) {
507        Some(value) => value.map(Some),
508        None => ParsedItem(input, None),
509    }
510}