Skip to main content

sl_types/
utils.rs

1//! Some small helper utilities
2
3#[cfg(feature = "chumsky")]
4use chumsky::{
5    IterParser as _, Parser,
6    prelude::{any, just, one_of},
7    text::digits,
8};
9
10/// parse some text in a URL component and URL decode it
11///
12/// # Errors
13///
14/// returns and error if the string could not be parsed
15#[cfg(feature = "chumsky")]
16#[must_use]
17pub fn url_text_component_parser<'src>()
18-> impl Parser<'src, &'src str, String, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
19    any()
20        .filter(|c: &char| {
21            c.is_alphabetic() || c.is_numeric() || *c == '%' || *c == '-' || *c == '~' || *c == '.'
22        })
23        .repeated()
24        .at_least(1)
25        .collect::<String>()
26        .try_map(|s, span| {
27            percent_encoding::percent_decode(s.as_bytes())
28                .decode_utf8()
29                .map(|s| s.into_owned())
30                .map_err(|e| chumsky::error::Rich::custom(span, format!("{e:?}")))
31        })
32}
33
34/// parse a usize
35///
36/// # Errors
37///
38/// returns an error if the string could not be parsed
39#[cfg(feature = "chumsky")]
40#[must_use]
41pub fn usize_parser<'src>()
42-> impl Parser<'src, &'src str, usize, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
43    digits(10).collect::<String>().try_map(|c: String, span| {
44        c.parse().map_err(|err| {
45            chumsky::error::Rich::custom(span, format!("failed to parse {c} as usize: {err:?}"))
46        })
47    })
48}
49
50/// parse a isize
51///
52/// # Errors
53///
54/// returns an error if the string could not be parsed
55#[cfg(feature = "chumsky")]
56#[must_use]
57pub fn isize_parser<'src>()
58-> impl Parser<'src, &'src str, isize, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
59    one_of("+-")
60        .or_not()
61        .then(digits(10).collect::<String>())
62        .try_map(|(sign, c): (Option<char>, String), span| {
63            let c = if let Some(sign) = sign {
64                format!("{sign}{c}")
65            } else {
66                c
67            };
68            c.parse().map_err(|err| {
69                chumsky::error::Rich::custom(span, format!("failed to parse {c} as isize: {err:?}"))
70            })
71        })
72}
73
74/// parse a u8
75///
76/// # Errors
77///
78/// returns an error if the string could not be parsed
79#[cfg(feature = "chumsky")]
80#[must_use]
81pub fn u8_parser<'src>()
82-> impl Parser<'src, &'src str, u8, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
83    digits(10).collect::<String>().try_map(|c: String, span| {
84        c.parse().map_err(|err| {
85            chumsky::error::Rich::custom(span, format!("failed to parse {c} as u8: {err:?}"))
86        })
87    })
88}
89
90/// parse a u16
91///
92/// # Errors
93///
94/// returns an error if the string could not be parsed
95#[cfg(feature = "chumsky")]
96#[must_use]
97pub fn u16_parser<'src>()
98-> impl Parser<'src, &'src str, u16, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
99    digits(10).collect::<String>().try_map(|c: String, span| {
100        c.parse().map_err(|err| {
101            chumsky::error::Rich::custom(span, format!("failed to parse {c} as u16: {err:?}"))
102        })
103    })
104}
105
106/// parse a u32
107///
108/// # Errors
109///
110/// returns an error if the string could not be parsed
111#[cfg(feature = "chumsky")]
112#[must_use]
113pub fn u32_parser<'src>()
114-> impl Parser<'src, &'src str, u32, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
115    digits(10).collect::<String>().try_map(|c: String, span| {
116        c.parse().map_err(|err| {
117            chumsky::error::Rich::custom(span, format!("failed to parse {c} as u32: {err:?}"))
118        })
119    })
120}
121
122/// parse a u64
123///
124/// # Errors
125///
126/// returns an error if the string could not be parsed
127#[cfg(feature = "chumsky")]
128#[must_use]
129pub fn u64_parser<'src>()
130-> impl Parser<'src, &'src str, u64, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
131    digits(10).collect::<String>().try_map(|c: String, span| {
132        c.parse().map_err(|err| {
133            chumsky::error::Rich::custom(span, format!("failed to parse {c} as u64: {err:?}"))
134        })
135    })
136}
137
138/// parse a i8
139///
140/// # Errors
141///
142/// returns an error if the string could not be parsed
143#[cfg(feature = "chumsky")]
144#[must_use]
145pub fn i8_parser<'src>()
146-> impl Parser<'src, &'src str, i8, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
147    one_of("+-")
148        .or_not()
149        .then(digits(10).collect::<String>())
150        .try_map(|(sign, c): (Option<char>, String), span| {
151            let c = if let Some(sign) = sign {
152                format!("{sign}{c}")
153            } else {
154                c
155            };
156            c.parse().map_err(|err| {
157                chumsky::error::Rich::custom(span, format!("failed to parse {c} as i8: {err:?}"))
158            })
159        })
160}
161
162/// parse a i16
163///
164/// # Errors
165///
166/// returns an error if the string could not be parsed
167#[cfg(feature = "chumsky")]
168#[must_use]
169pub fn i16_parser<'src>()
170-> impl Parser<'src, &'src str, i16, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
171    one_of("+-")
172        .or_not()
173        .then(digits(10).collect::<String>())
174        .try_map(|(sign, c): (Option<char>, String), span| {
175            let c = if let Some(sign) = sign {
176                format!("{sign}{c}")
177            } else {
178                c
179            };
180            c.parse().map_err(|err| {
181                chumsky::error::Rich::custom(span, format!("failed to parse {c} as i16: {err:?}"))
182            })
183        })
184}
185
186/// parse a i32
187///
188/// # Errors
189///
190/// returns an error if the string could not be parsed
191#[cfg(feature = "chumsky")]
192#[must_use]
193pub fn i32_parser<'src>()
194-> impl Parser<'src, &'src str, i32, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
195    one_of("+-")
196        .or_not()
197        .then(digits(10).collect::<String>())
198        .try_map(|(sign, c): (Option<char>, String), span| {
199            let c = if let Some(sign) = sign {
200                format!("{sign}{c}")
201            } else {
202                c
203            };
204            c.parse().map_err(|err| {
205                chumsky::error::Rich::custom(span, format!("failed to parse {c} as i32: {err:?}"))
206            })
207        })
208}
209
210/// parse a i64
211///
212/// # Errors
213///
214/// returns an error if the string could not be parsed
215#[cfg(feature = "chumsky")]
216#[must_use]
217pub fn i64_parser<'src>()
218-> impl Parser<'src, &'src str, i64, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
219    one_of("+-")
220        .or_not()
221        .then(digits(10).collect::<String>())
222        .try_map(|(sign, c): (Option<char>, String), span| {
223            let c = if let Some(sign) = sign {
224                format!("{sign}{c}")
225            } else {
226                c
227            };
228            c.parse().map_err(|err| {
229                chumsky::error::Rich::custom(span, format!("failed to parse {c} as i64: {err:?}"))
230            })
231        })
232}
233
234/// parse a float without a sign
235///
236/// # Errors
237///
238/// returns an error if the string could not be parsed
239#[cfg(feature = "chumsky")]
240#[must_use]
241pub fn unsigned_f32_parser<'src>()
242-> impl Parser<'src, &'src str, f32, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
243    digits(10)
244        .collect::<String>()
245        .then(
246            just('.')
247                .ignore_then(digits(10).collect::<String>())
248                .or_not(),
249        )
250        .try_map(|(before_point, after_point), span| {
251            let raw_float = format!(
252                "{}.{}",
253                before_point,
254                after_point.unwrap_or_else(|| "0".to_string())
255            );
256            raw_float.parse().map_err(|err| {
257                chumsky::error::Rich::custom(
258                    span,
259                    format!("Could not parse {raw_float} as f32: {err:?}"),
260                )
261            })
262        })
263}
264
265/// parse a float without a sign
266///
267/// # Errors
268///
269/// returns an error if the string could not be parsed
270#[cfg(feature = "chumsky")]
271#[must_use]
272pub fn unsigned_f64_parser<'src>()
273-> impl Parser<'src, &'src str, f64, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
274    digits(10)
275        .collect::<String>()
276        .then(
277            just('.')
278                .ignore_then(digits(10).collect::<String>())
279                .or_not(),
280        )
281        .try_map(|(before_point, after_point), span| {
282            let raw_float = format!(
283                "{}.{}",
284                before_point,
285                after_point.unwrap_or_else(|| "0".to_string())
286            );
287            raw_float.parse().map_err(|err| {
288                chumsky::error::Rich::custom(
289                    span,
290                    format!("Could not parse {raw_float} as f64: {err:?}"),
291                )
292            })
293        })
294}
295
296/// parse a float with or without a sign
297///
298/// # Errors
299///
300/// returns an error if the string could not be parsed
301#[cfg(feature = "chumsky")]
302#[must_use]
303pub fn f32_parser<'src>()
304-> impl Parser<'src, &'src str, f32, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
305    one_of("+-")
306        .or_not()
307        .then(unsigned_f32_parser())
308        .map(
309            |(sign, value)| {
310                if sign == Some('-') { -value } else { value }
311            },
312        )
313}
314
315/// parse a float with or without a sign
316///
317/// # Errors
318///
319/// returns an error if the string could not be parsed
320#[cfg(feature = "chumsky")]
321#[must_use]
322pub fn f64_parser<'src>()
323-> impl Parser<'src, &'src str, f64, chumsky::extra::Err<chumsky::error::Rich<'src, char>>> {
324    one_of("+-")
325        .or_not()
326        .then(unsigned_f64_parser())
327        .map(
328            |(sign, value)| {
329                if sign == Some('-') { -value } else { value }
330            },
331        )
332}