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 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#[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#[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 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 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#[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#[derive(Debug)]
122pub(crate) struct ExactlyNDigits<const N: u8>;
123
124impl ExactlyNDigits<1> {
125 #[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 #[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 #[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 #[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 #[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 #[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 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 #[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 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 #[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 #[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
335pub(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
345pub(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 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 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 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#[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#[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#[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#[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}