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