1pub(crate) mod rfc;
4
5use crate::format_description::modifier::Padding;
6use crate::parsing::ParsedItem;
7use crate::parsing::shim::Integer;
8
9#[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#[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#[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#[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#[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#[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 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 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#[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#[derive(Debug)]
150pub(crate) struct ExactlyNDigits<const N: u8>;
151
152impl ExactlyNDigits<1> {
153 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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
397pub(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
407pub(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 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 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 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#[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#[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#[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#[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}