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
11#[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#[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#[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#[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#[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 let orig_input = input;
87
88 for _ in 0..N {
90 input = parser(input)?.0;
91 }
92
93 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#[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#[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#[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#[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#[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#[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#[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#[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}