bstr_parse/lib.rs
1//! # bstr_parse
2//!
3//! Adds the ability to parse numbers out of `&[u8]`s. Like so:
4//!
5//! ```
6//! use bstr_parse::*;
7//!
8//! let text: &[u8] = b"1234";
9//! let num: u32 = text.parse().unwrap();
10//! assert_eq!(num, 1234);
11//! ```
12
13#![warn(missing_docs)]
14#![warn(missing_crate_level_docs)]
15#![warn(private_doc_tests)]
16#![warn(clippy::all)]
17#![warn(clippy::cargo)]
18#![forbid(unsafe_code)]
19
20/// An extension trait for `parse`.
21pub trait BStrParse {
22 /// Parses this string slice into another type.
23 ///
24 /// Because `parse` is so general, it can cause problems with type
25 /// inference. As such, `parse` is one of the few times you'll see
26 /// the syntax affectionately known as the 'turbofish': `::<>`. This
27 /// helps the inference algorithm understand specifically which type
28 /// you're trying to parse into.
29 ///
30 /// `parse` can parse any type that implements the [`FromBStr`] trait.
31
32 ///
33 /// # Errors
34 ///
35 /// Will return [`Err`] if it's not possible to parse this string slice into
36 /// the desired type.
37 ///
38 /// [`Err`]: FromBStr::Err
39 ///
40 /// # Examples
41 ///
42 /// Basic usage
43 ///
44 /// ```
45 /// use bstr_parse::*;
46 /// let four: u32 = b"4".parse().unwrap();
47 ///
48 /// assert_eq!(4, four);
49 /// ```
50 ///
51 /// Using the 'turbofish' instead of annotating `four`:
52 ///
53 /// ```
54 /// use bstr_parse::*;
55 /// let four = b"4".parse::<u32>();
56 ///
57 /// assert_eq!(Ok(4), four);
58 /// ```
59 ///
60 /// Failing to parse:
61 ///
62 /// ```
63 /// use bstr_parse::*;
64 /// let nope = b"j".parse::<u32>();
65 ///
66 /// assert!(nope.is_err());
67 /// ```
68 fn parse<F: FromBStr>(&self) -> Result<F, F::Err>;
69}
70
71impl BStrParse for [u8] {
72 /// Parses this string slice into another type.
73 ///
74 /// Because `parse` is so general, it can cause problems with type
75 /// inference. As such, `parse` is one of the few times you'll see
76 /// the syntax affectionately known as the 'turbofish': `::<>`. This
77 /// helps the inference algorithm understand specifically which type
78 /// you're trying to parse into.
79 ///
80 /// `parse` can parse any type that implements the [`FromBStr`] trait.
81
82 ///
83 /// # Errors
84 ///
85 /// Will return [`Err`] if it's not possible to parse this string slice into
86 /// the desired type.
87 ///
88 /// [`Err`]: FromBStr::Err
89 ///
90 /// # Examples
91 ///
92 /// Basic usage
93 ///
94 /// ```
95 /// use bstr_parse::*;
96 /// let four: u32 = b"4".parse().unwrap();
97 ///
98 /// assert_eq!(4, four);
99 /// ```
100 ///
101 /// Using the 'turbofish' instead of annotating `four`:
102 ///
103 /// ```
104 /// use bstr_parse::*;
105 /// let four = b"4".parse::<u32>();
106 ///
107 /// assert_eq!(Ok(4), four);
108 /// ```
109 ///
110 /// Failing to parse:
111 ///
112 /// ```
113 /// use bstr_parse::*;
114 /// let nope = b"j".parse::<u32>();
115 ///
116 /// assert!(nope.is_err());
117 /// ```
118 #[inline]
119 fn parse<F: FromBStr>(&self) -> Result<F, F::Err> {
120 FromBStr::from_bstr(self)
121 }
122}
123
124/// Parse a value from a string
125///
126/// `FromBStr`'s [`from_bstr`] method is often used implicitly, through
127/// `[u8]`'s [`parse`] method. See [`parse`]'s documentation for examples.
128///
129/// [`from_bstr`]: FromBStr::from_bstr
130/// [`parse`]: BStrParse::parse
131///
132/// `FromBStr` does not have a lifetime parameter, and so you can only parse types
133/// that do not contain a lifetime parameter themselves. In other words, you can
134/// parse an `i32` with `FromBStr`, but not a `&i32`. You can parse a struct that
135/// contains an `i32`, but not one that contains an `&i32`.
136///
137/// # Examples
138///
139/// Basic implementation of `FromBStr` on an example `Point` type:
140///
141/// ```
142/// use bstr_parse::*;
143///
144/// #[derive(Debug, PartialEq)]
145/// struct Point {
146/// x: i32,
147/// y: i32
148/// }
149///
150/// impl FromBStr for Point {
151/// type Err = ParseIntError;
152///
153/// fn from_bstr(s: &[u8]) -> Result<Self, Self::Err> {
154/// let coords: Vec<&[u8]> = s.split(|&p| p == b'(' || p == b')' || p == b',')
155/// .skip_while(|s| s.is_empty())
156/// .take_while(|s| !s.is_empty())
157/// .collect();
158///
159/// let x_frombstr = coords[0].parse::<i32>()?;
160/// let y_frombstr = coords[1].parse::<i32>()?;
161///
162/// Ok(Point { x: x_frombstr, y: y_frombstr })
163/// }
164/// }
165///
166/// let p = Point::from_bstr(b"(1,2)");
167/// assert_eq!(p.unwrap(), Point{ x: 1, y: 2} )
168/// ```
169pub trait FromBStr: Sized {
170 /// The associated error which can be returned from parsing.
171 type Err;
172
173 /// Parses a string `s` to return a value of this type.
174 ///
175 /// If parsing succeeds, return the value inside [`Ok`], otherwise
176 /// when the string is ill-formatted return an error specific to the
177 /// inside [`Err`]. The error type is specific to implementation of the trait.
178 ///
179 /// # Examples
180 ///
181 /// Basic usage with [`i32`][ithirtytwo], a type that implements `FromBStr`:
182 ///
183 /// [ithirtytwo]: ../../std/primitive.i32.html
184 ///
185 /// ```
186 /// use bstr_parse::*;
187 ///
188 /// let s = b"5";
189 /// let x = i32::from_bstr(s).unwrap();
190 ///
191 /// assert_eq!(5, x);
192 /// ```
193 fn from_bstr(s: &[u8]) -> Result<Self, Self::Err>;
194}
195
196#[doc(hidden)]
197trait FromBStrRadixHelper: PartialOrd + Copy {
198 fn min_value() -> Self;
199 fn max_value() -> Self;
200 fn from_u32(u: u32) -> Self;
201 fn checked_mul(&self, other: u32) -> Option<Self>;
202 fn checked_sub(&self, other: u32) -> Option<Self>;
203 fn checked_add(&self, other: u32) -> Option<Self>;
204}
205
206macro_rules! from_bstr_radix_int_impl {
207 ($($t:ty)*) => {$(
208 impl FromBStr for $t {
209 type Err = ParseIntError;
210
211 #[inline]
212 fn from_bstr(src: &[u8]) -> Result<Self, ParseIntError> {
213 from_bstr_radix(src, 10)
214 }
215 }
216 )*}
217}
218from_bstr_radix_int_impl! { isize i8 i16 i32 i64 i128 usize u8 u16 u32 u64 u128 }
219
220macro_rules! doit {
221 ($($t:ty)*) => ($(impl FromBStrRadixHelper for $t {
222 #[inline]
223 fn min_value() -> Self { Self::MIN }
224 #[inline]
225 fn max_value() -> Self { Self::MAX }
226 #[inline]
227 fn from_u32(u: u32) -> Self { u as Self }
228 #[inline]
229 fn checked_mul(&self, other: u32) -> Option<Self> {
230 Self::checked_mul(*self, other as Self)
231 }
232 #[inline]
233 fn checked_sub(&self, other: u32) -> Option<Self> {
234 Self::checked_sub(*self, other as Self)
235 }
236 #[inline]
237 fn checked_add(&self, other: u32) -> Option<Self> {
238 Self::checked_add(*self, other as Self)
239 }
240 })*)
241}
242doit! { i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize }
243
244fn from_bstr_radix<T: FromBStrRadixHelper>(src: &[u8], radix: u32) -> Result<T, ParseIntError> {
245 use self::IntErrorKind::*;
246 use self::ParseIntError as PIE;
247
248 assert!(
249 radix >= 2 && radix <= 36,
250 "from_bstr_radix_int: must lie in the range `[2, 36]` - found {}",
251 radix
252 );
253
254 if src.is_empty() {
255 return Err(PIE { kind: Empty });
256 }
257
258 let is_signed_ty = T::from_u32(0) > T::min_value();
259
260 let (is_positive, digits) = match src[0] {
261 b'+' => (true, &src[1..]),
262 b'-' if is_signed_ty => (false, &src[1..]),
263 _ => (true, src),
264 };
265
266 if digits.is_empty() {
267 return Err(PIE { kind: Empty });
268 }
269
270 let mut result = T::from_u32(0);
271 if is_positive {
272 // The number is positive
273 for &c in digits {
274 let x = match (c as char).to_digit(radix) {
275 Some(x) => x,
276 None => return Err(PIE { kind: InvalidDigit }),
277 };
278 result = match result.checked_mul(radix) {
279 Some(result) => result,
280 None => return Err(PIE { kind: Overflow }),
281 };
282 result = match result.checked_add(x) {
283 Some(result) => result,
284 None => return Err(PIE { kind: Overflow }),
285 };
286 }
287 } else {
288 // The number is negative
289 for &c in digits {
290 let x = match (c as char).to_digit(radix) {
291 Some(x) => x,
292 None => return Err(PIE { kind: InvalidDigit }),
293 };
294 result = match result.checked_mul(radix) {
295 Some(result) => result,
296 None => return Err(PIE { kind: Underflow }),
297 };
298 result = match result.checked_sub(x) {
299 Some(result) => result,
300 None => return Err(PIE { kind: Underflow }),
301 };
302 }
303 }
304 Ok(result)
305}
306
307/// An error which can be returned when parsing an integer.
308///
309/// This error is used as the error type for the `from_bstr_radix()` functions
310/// on the primitive integer types.
311///
312/// # Potential causes
313///
314/// Among other causes, `ParseIntError` can be thrown because of leading or trailing whitespace
315/// in the string e.g., when it is obtained from the standard input.
316///
317/// [`i8::from_bstr_radix`]: i8::from_bstr_radix
318///
319/// # Example
320///
321/// ```
322/// use bstr_parse::*;
323///
324/// if let Err(e) = i32::from_bstr_radix(b"a12", 10) {
325/// println!("Failed conversion to i32: {}", e);
326/// }
327/// ```
328#[derive(Debug, Clone, PartialEq, Eq)]
329pub struct ParseIntError {
330 kind: IntErrorKind,
331}
332
333/// Enum to store the various types of errors that can cause parsing an integer to fail.
334///
335/// # Example
336///
337/// ```
338/// use bstr_parse::*;
339///
340/// # fn main() {
341/// if let Err(e) = i32::from_bstr_radix(b"a12", 10) {
342/// println!("Failed conversion to i32: {:?}", e.kind());
343/// }
344/// # }
345/// ```
346#[derive(Debug, Clone, PartialEq, Eq)]
347#[non_exhaustive]
348pub enum IntErrorKind {
349 /// Value being parsed is empty.
350 ///
351 /// Among other causes, this variant will be constructed when parsing an empty string.
352 Empty,
353 /// Contains an invalid digit.
354 ///
355 /// Among other causes, this variant will be constructed when parsing a string that
356 /// contains a letter.
357 InvalidDigit,
358 /// Integer is too large to store in target integer type.
359 Overflow,
360 /// Integer is too small to store in target integer type.
361 Underflow,
362 /// Value was Zero
363 ///
364 /// This variant will be emitted when the parsing string has a value of zero, which
365 /// would be illegal for non-zero types.
366 Zero,
367}
368
369impl ParseIntError {
370 /// Outputs the detailed cause of parsing an integer failing.
371 pub fn kind(&self) -> &IntErrorKind {
372 &self.kind
373 }
374
375 #[doc(hidden)]
376 pub fn __description(&self) -> &str {
377 match self.kind {
378 IntErrorKind::Empty => "cannot parse integer from empty string",
379 IntErrorKind::InvalidDigit => "invalid digit found in string",
380 IntErrorKind::Overflow => "number too large to fit in target type",
381 IntErrorKind::Underflow => "number too small to fit in target type",
382 IntErrorKind::Zero => "number would be zero for non-zero type",
383 }
384 }
385}
386
387impl std::fmt::Display for ParseIntError {
388 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
389 self.__description().fmt(f)
390 }
391}
392
393impl std::error::Error for ParseIntError {
394 #[allow(deprecated)]
395 fn description(&self) -> &str {
396 self.__description()
397 }
398}
399
400/// An extension trait for `from_bstr_radix`.
401pub trait FromBStrRadix: Sized {
402 /// Converts a string slice in a given base to an integer.
403 ///
404 /// The string is expected to be an optional `+` or `-` sign followed by digits.
405 /// Leading and trailing whitespace represent an error. Digits are a subset of these characters,
406 /// depending on `radix`:
407 ///
408 /// * `0-9`
409 /// * `a-z`
410 /// * `A-Z`
411 ///
412 /// # Panics
413 ///
414 /// This function panics if `radix` is not in the range from 2 to 36.
415 ///
416 /// # Examples
417 ///
418 /// Basic usage:
419 ///
420 /// ```
421 /// use bstr_parse::*;
422 ///
423 /// assert_eq!(u8::from_bstr_radix(b"A", 16), Ok(10));
424 /// ```
425 fn from_bstr_radix(src: &[u8], radix: u32) -> Result<Self, ParseIntError>;
426}
427
428macro_rules! doc_comment {
429 ($x:expr, $($tt:tt)*) => {
430 #[doc = $x]
431 $($tt)*
432 };
433}
434
435macro_rules! from_bstr_radix_impl {
436 ($($t:ty)*) => {$(
437 impl FromBStrRadix for $t {
438 doc_comment! {
439 concat!("Converts a string slice in a given base to an integer.
440
441The string is expected to be an optional `+` or `-` sign followed by digits.
442Leading and trailing whitespace represent an error. Digits are a subset of these characters,
443depending on `radix`:
444
445 * `0-9`
446 * `a-z`
447 * `A-Z`
448
449# Panics
450
451This function panics if `radix` is not in the range from 2 to 36.
452
453# Examples
454
455Basic usage:
456
457```
458use bstr_parse::*;
459
460assert_eq!(", stringify!($t), "::from_bstr_radix(b\"A\", 16), Ok(10));
461```"),
462 #[inline]
463 fn from_bstr_radix(src: &[u8], radix: u32) -> Result<Self, ParseIntError> {
464 from_bstr_radix(src, radix)
465 }
466 }
467 }
468 )*}
469}
470from_bstr_radix_impl! { i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize }