Skip to main content

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 zero or more instances of the provided parser. The parser must return the unit value.
31#[inline]
32pub(crate) fn zero_or_more<P>(parser: P) -> impl for<'a> FnMut(&'a [u8]) -> ParsedItem<'a, ()>
33where
34    P: for<'a> Fn(&'a [u8]) -> Option<ParsedItem<'a, ()>>,
35{
36    move |mut input| {
37        while let Some(remaining) = parser(input) {
38            input = remaining.into_inner();
39        }
40        ParsedItem(input, ())
41    }
42}
43
44/// Consume one of or more instances of the provided parser. The parser must produce the unit value.
45#[inline]
46pub(crate) fn one_or_more<P>(parser: P) -> impl for<'a> Fn(&'a [u8]) -> Option<ParsedItem<'a, ()>>
47where
48    P: for<'a> Fn(&'a [u8]) -> Option<ParsedItem<'a, ()>>,
49{
50    move |mut input| {
51        input = parser(input)?.into_inner();
52        while let Some(remaining) = parser(input) {
53            input = remaining.into_inner();
54        }
55        Some(ParsedItem(input, ()))
56    }
57}
58
59/// Consume between `n` and `m` digits, returning the numerical value.
60#[inline]
61pub(crate) fn n_to_m_digits<const N: u8, const M: u8, T>(
62    mut input: &[u8],
63) -> Option<ParsedItem<'_, T>>
64where
65    T: Integer,
66{
67    const {
68        assert!(N > 0);
69        assert!(M >= N);
70    }
71
72    let mut value = T::ZERO;
73
74    // Mandatory
75    for i in 0..N {
76        let digit;
77        ParsedItem(input, digit) = any_digit(input)?;
78
79        if i != T::MAX_NUM_DIGITS - 1 {
80            value = value.push_digit(digit - b'0');
81        } else {
82            value = value.checked_push_digit(digit - b'0')?;
83        }
84    }
85
86    // Optional
87    for i in N..M {
88        let Some(ParsedItem(new_input, digit)) = any_digit(input) else {
89            break;
90        };
91        input = new_input;
92
93        if i != T::MAX_NUM_DIGITS - 1 {
94            value = value.push_digit(digit - b'0');
95        } else {
96            value = value.checked_push_digit(digit - b'0')?;
97        }
98    }
99
100    Some(ParsedItem(input, value))
101}
102
103/// Consume one or two digits, returning the numerical value.
104#[inline]
105pub(crate) fn one_or_two_digits(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
106    match input {
107        [a @ b'0'..=b'9', b @ b'0'..=b'9', remaining @ ..] => {
108            let a = *a - b'0';
109            let b = *b - b'0';
110            Some(ParsedItem(remaining, a * 10 + b))
111        }
112        [a @ b'0'..=b'9', remaining @ ..] => {
113            let a = *a - b'0';
114            Some(ParsedItem(remaining, a))
115        }
116        _ => None,
117    }
118}
119
120/// Parse an exact number of digits without padding.
121#[derive(Debug)]
122pub(crate) struct ExactlyNDigits<const N: u8>;
123
124impl ExactlyNDigits<1> {
125    /// Consume exactly one digit.
126    #[inline]
127    pub(crate) const fn parse(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
128        match input {
129            [a @ b'0'..=b'9', remaining @ ..] => Some(ParsedItem(remaining, *a - b'0')),
130            _ => None,
131        }
132    }
133}
134
135impl ExactlyNDigits<2> {
136    /// Consume exactly two digits.
137    #[inline]
138    pub(crate) const fn parse(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
139        match input {
140            [a @ b'0'..=b'9', b @ b'0'..=b'9', remaining @ ..] => {
141                let a = *a - b'0';
142                let b = *b - b'0';
143                Some(ParsedItem(remaining, a * 10 + b))
144            }
145            _ => None,
146        }
147    }
148}
149
150impl ExactlyNDigits<3> {
151    /// Consume exactly three digits.
152    #[inline]
153    pub(crate) const fn parse(input: &[u8]) -> Option<ParsedItem<'_, u16>> {
154        match input {
155            [
156                a @ b'0'..=b'9',
157                b @ b'0'..=b'9',
158                c @ b'0'..=b'9',
159                remaining @ ..,
160            ] => {
161                let a = (*a - b'0') as u16;
162                let b = (*b - b'0') as u16;
163                let c = (*c - b'0') as u16;
164                Some(ParsedItem(remaining, a * 100 + b * 10 + c))
165            }
166            _ => None,
167        }
168    }
169}
170
171impl ExactlyNDigits<4> {
172    /// Consume exactly four digits.
173    #[inline]
174    pub(crate) fn parse(input: &[u8]) -> Option<ParsedItem<'_, u16>> {
175        let [a, b, c, d, remaining @ ..] = input else {
176            return None;
177        };
178
179        let digits = [a, b, c, d].map(|d| (*d as u16).wrapping_sub(b'0' as u16));
180        if digits.iter().any(|&digit| digit > 9) {
181            return None;
182        }
183
184        let value = digits[0] * 1000 + digits[1] * 100 + digits[2] * 10 + digits[3];
185        Some(ParsedItem(remaining, value))
186    }
187}
188
189impl ExactlyNDigits<5> {
190    /// Consume exactly five digits.
191    #[inline]
192    pub(crate) fn parse(input: &[u8]) -> Option<ParsedItem<'_, u32>> {
193        let [a, b, c, d, e, remaining @ ..] = input else {
194            return None;
195        };
196
197        let digits = [a, b, c, d, e].map(|d| (*d as u32).wrapping_sub(b'0' as u32));
198        if digits.iter().any(|&digit| digit > 9) {
199            return None;
200        }
201
202        let value =
203            digits[0] * 10_000 + digits[1] * 1_000 + digits[2] * 100 + digits[3] * 10 + digits[4];
204        Some(ParsedItem(remaining, value))
205    }
206}
207
208impl ExactlyNDigits<6> {
209    /// Consume exactly six digits.
210    #[inline]
211    pub(crate) fn parse(input: &[u8]) -> Option<ParsedItem<'_, u32>> {
212        let [a, b, c, d, e, f, remaining @ ..] = input else {
213            return None;
214        };
215
216        // Calling `.map` successively results in slightly better codegen.
217        let digits = [a, b, c, d, e, f]
218            .map(|d| *d as u32)
219            .map(|d| d.wrapping_sub(b'0' as u32));
220        if digits.iter().any(|&digit| digit > 9) {
221            return None;
222        }
223
224        let value = digits[0] * 100_000
225            + digits[1] * 10_000
226            + digits[2] * 1_000
227            + digits[3] * 100
228            + digits[4] * 10
229            + digits[5];
230        Some(ParsedItem(remaining, value))
231    }
232}
233
234impl ExactlyNDigits<7> {
235    /// Consume exactly seven digits.
236    #[inline]
237    pub(crate) fn parse(input: &[u8]) -> Option<ParsedItem<'_, u32>> {
238        let [a, b, c, d, e, f, g, remaining @ ..] = input else {
239            return None;
240        };
241
242        // For whatever reason, the compiler does *not* autovectorize if `.map` is applied directly.
243        let mut digits = [*a, *b, *c, *d, *e, *f, *g];
244        digits = digits.map(|d| d.wrapping_sub(b'0'));
245
246        if digits.iter().any(|&digit| digit > 9) {
247            return None;
248        }
249
250        let value = digits[0] as u32 * 1_000_000
251            + digits[1] as u32 * 100_000
252            + digits[2] as u32 * 10_000
253            + digits[3] as u32 * 1_000
254            + digits[4] as u32 * 100
255            + digits[5] as u32 * 10
256            + digits[6] as u32;
257        Some(ParsedItem(remaining, value))
258    }
259}
260
261impl ExactlyNDigits<8> {
262    /// Consume exactly eight digits.
263    #[inline]
264    pub(crate) fn parse(input: &[u8]) -> Option<ParsedItem<'_, u32>> {
265        let [a, b, c, d, e, f, g, h, remaining @ ..] = input else {
266            return None;
267        };
268
269        let mut digits = [*a, *b, *c, *d, *e, *f, *g, *h];
270        digits = [
271            digits[0].wrapping_sub(b'0'),
272            digits[1].wrapping_sub(b'0'),
273            digits[2].wrapping_sub(b'0'),
274            digits[3].wrapping_sub(b'0'),
275            digits[4].wrapping_sub(b'0'),
276            digits[5].wrapping_sub(b'0'),
277            digits[6].wrapping_sub(b'0'),
278            digits[7].wrapping_sub(b'0'),
279        ];
280
281        if digits.iter().any(|&digit| digit > 9) {
282            return None;
283        }
284
285        let value = digits[0] as u32 * 10_000_000
286            + digits[1] as u32 * 1_000_000
287            + digits[2] as u32 * 100_000
288            + digits[3] as u32 * 10_000
289            + digits[4] as u32 * 1_000
290            + digits[5] as u32 * 100
291            + digits[6] as u32 * 10
292            + digits[7] as u32;
293        Some(ParsedItem(remaining, value))
294    }
295}
296
297impl ExactlyNDigits<9> {
298    /// Consume exactly nine digits.
299    #[inline]
300    pub(crate) fn parse(input: &[u8]) -> Option<ParsedItem<'_, u32>> {
301        let [a, b, c, d, e, f, g, h, i, remaining @ ..] = input else {
302            return None;
303        };
304
305        let mut digits = [*a, *b, *c, *d, *e, *f, *g, *h];
306        digits = [
307            digits[0] - b'0',
308            digits[1] - b'0',
309            digits[2] - b'0',
310            digits[3] - b'0',
311            digits[4] - b'0',
312            digits[5] - b'0',
313            digits[6] - b'0',
314            digits[7] - b'0',
315        ];
316        let ones_digit = (*i as u32).wrapping_sub(b'0' as u32);
317
318        if digits.iter().any(|&digit| digit > 9) || ones_digit > 9 {
319            return None;
320        }
321
322        let value = digits[0] as u32 * 100_000_000
323            + digits[1] as u32 * 10_000_000
324            + digits[2] as u32 * 1_000_000
325            + digits[3] as u32 * 100_000
326            + digits[4] as u32 * 10_000
327            + digits[5] as u32 * 1_000
328            + digits[6] as u32 * 100
329            + digits[7] as u32 * 10
330            + ones_digit;
331        Some(ParsedItem(remaining, value))
332    }
333}
334
335/// Consume exactly `n` digits, returning the numerical value.
336pub(crate) fn exactly_n_digits_padded<const N: u8, T>(
337    padding: Padding,
338) -> impl for<'a> Fn(&'a [u8]) -> Option<ParsedItem<'a, T>>
339where
340    T: Integer,
341{
342    n_to_m_digits_padded::<N, N, _>(padding)
343}
344
345/// Consume between `n` and `m` digits, returning the numerical value.
346pub(crate) fn n_to_m_digits_padded<const N: u8, const M: u8, T>(
347    padding: Padding,
348) -> impl for<'a> Fn(&'a [u8]) -> Option<ParsedItem<'a, T>>
349where
350    T: Integer,
351{
352    const {
353        assert!(N > 0);
354        assert!(M >= N);
355    }
356
357    move |mut input| match padding {
358        Padding::None => n_to_m_digits::<1, M, _>(input),
359        Padding::Space => {
360            let mut value = T::ZERO;
361
362            // Consume the padding.
363            let mut pad_width = 0;
364            for _ in 0..(N - 1) {
365                match ascii_char::<b' '>(input) {
366                    Some(parsed) => {
367                        pad_width += 1;
368                        input = parsed.0;
369                    }
370                    None => break,
371                }
372            }
373
374            // Mandatory
375            for i in 0..(N - pad_width) {
376                let digit;
377                ParsedItem(input, digit) = any_digit(input)?;
378
379                value = if i != T::MAX_NUM_DIGITS - 1 {
380                    value.push_digit(digit - b'0')
381                } else {
382                    value.checked_push_digit(digit - b'0')?
383                };
384            }
385
386            // Optional
387            for i in N..M {
388                let Some(ParsedItem(new_input, digit)) = any_digit(input) else {
389                    break;
390                };
391                input = new_input;
392
393                value = if i - pad_width != T::MAX_NUM_DIGITS - 1 {
394                    value.push_digit(digit - b'0')
395                } else {
396                    value.checked_push_digit(digit - b'0')?
397                };
398            }
399
400            Some(ParsedItem(input, value))
401        }
402        Padding::Zero => n_to_m_digits::<N, M, _>(input),
403    }
404}
405
406/// Consume exactly one digit.
407#[inline]
408pub(crate) const fn any_digit(input: &[u8]) -> Option<ParsedItem<'_, u8>> {
409    match input {
410        [c @ b'0'..=b'9', remaining @ ..] => Some(ParsedItem(remaining, *c)),
411        _ => None,
412    }
413}
414
415/// Consume exactly one of the provided ASCII characters.
416#[inline]
417pub(crate) fn ascii_char<const CHAR: u8>(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
418    const {
419        assert!(CHAR.is_ascii_graphic() || CHAR.is_ascii_whitespace());
420    }
421    match input {
422        [c, remaining @ ..] if *c == CHAR => Some(ParsedItem(remaining, ())),
423        _ => None,
424    }
425}
426
427/// Consume exactly one of the provided ASCII characters, case-insensitive.
428#[inline]
429pub(crate) fn ascii_char_ignore_case<const CHAR: u8>(input: &[u8]) -> Option<ParsedItem<'_, ()>> {
430    const {
431        assert!(CHAR.is_ascii_graphic() || CHAR.is_ascii_whitespace());
432    }
433    match input {
434        [c, remaining @ ..] if c.eq_ignore_ascii_case(&CHAR) => Some(ParsedItem(remaining, ())),
435        _ => None,
436    }
437}
438
439/// Optionally consume an input with a given parser.
440#[inline]
441pub(crate) fn opt<T>(
442    parser: impl for<'a> Fn(&'a [u8]) -> Option<ParsedItem<'a, T>>,
443) -> impl for<'a> Fn(&'a [u8]) -> ParsedItem<'a, Option<T>> {
444    move |input| match parser(input) {
445        Some(value) => value.map(Some),
446        None => ParsedItem(input, None),
447    }
448}