1pub(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
11pub(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
19pub(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
43pub(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
55pub(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
68pub(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 let orig_input = input;
82
83 for _ in 0..N {
85 input = parser(input)?.0;
86 }
87
88 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
103pub(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
111pub(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
116pub(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
123pub(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
160pub(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
168pub(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
177pub(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
186pub(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}