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 }