Skip to main content

bounded_integer/
parse.rs

1use core::error::Error;
2use core::fmt::{self, Display, Formatter};
3
4macro_rules! from_str_radix_impl {
5    ($($ty:ident)*) => { $(
6        impl $crate::__private::Dispatch<$ty> {
7            // Implement it ourselves (copying the implementation from std) because `IntErrorKind`
8            // is non-exhaustive.
9            pub const fn from_ascii_radix(src: &[u8], radix: u32) -> Result<$ty, ParseError> {
10                assert!(
11                    2 <= radix && radix <= 36,
12                    "from_str_radix: radix must lie in the range `[2, 36]`",
13                );
14
15                macro_rules! yeet {
16                    ($e:expr) => { return Err(ParseError { kind: $e }) };
17                }
18
19                let (positive, digits) = match *src {
20                    [b'+', ref digits @ ..] => (true, digits),
21                    [b'-', ref digits @ ..] => (false, digits),
22                    ref digits => (true, digits),
23                };
24
25                if digits.is_empty() {
26                    yeet!(ParseErrorKind::NoDigits);
27                }
28
29                let overflow_kind = if positive {
30                    ParseErrorKind::AboveMax
31                } else {
32                    ParseErrorKind::BelowMin
33                };
34
35                let mut result: $ty = 0;
36
37                let mut i = 0;
38                while i < digits.len() {
39                    let digit = digits[i];
40
41                    let Some(digit_value) = (digit as char).to_digit(radix) else {
42                        yeet!(ParseErrorKind::InvalidDigit);
43                    };
44
45                    #[allow(clippy::cast_possible_wrap)]
46                    #[allow(clippy::cast_possible_truncation)]
47                    let Some(new_result) = result.checked_mul(radix as $ty) else {
48                        yeet!(overflow_kind);
49                    };
50
51                    #[allow(clippy::cast_possible_wrap)]
52                    #[allow(clippy::cast_possible_truncation)]
53                    let Some(new_result) = (if positive {
54                        new_result.checked_add(digit_value as $ty)
55                    } else {
56                        new_result.checked_sub(digit_value as $ty)
57                    }) else {
58                        yeet!(overflow_kind);
59                    };
60
61                    result = new_result;
62
63                    i += 1;
64                }
65
66                Ok(result)
67            }
68        }
69    )* }
70}
71from_str_radix_impl! { u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize }
72
73/// An error which can be returned when parsing a bounded integer.
74///
75/// This is the error type of all bounded integers' `from_str_radix()` functions (such as
76/// [`BoundedI8::from_str_radix`](crate::BoundedI8::from_str_radix)) as well as their
77/// [`FromStr`](std::str::FromStr) implementations.
78#[derive(Debug, Clone)]
79pub struct ParseError {
80    kind: ParseErrorKind,
81}
82
83impl ParseError {
84    /// Gives the cause of the error.
85    #[must_use]
86    pub fn kind(&self) -> ParseErrorKind {
87        self.kind
88    }
89}
90
91impl Display for ParseError {
92    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
93        match self.kind() {
94            ParseErrorKind::NoDigits => f.write_str("no digits found"),
95            ParseErrorKind::InvalidDigit => f.write_str("invalid digit found in string"),
96            ParseErrorKind::AboveMax => f.write_str("number too high to fit in target range"),
97            ParseErrorKind::BelowMin => f.write_str("number too low to fit in target range"),
98        }
99    }
100}
101
102impl Error for ParseError {}
103
104/// The cause of the failure to parse the integer.
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106#[non_exhaustive]
107pub enum ParseErrorKind {
108    /// No digits were found in the input string.
109    ///
110    /// This happens when the input is an empty string, or when it only contains a `+` or `-`.
111    #[non_exhaustive]
112    NoDigits,
113    /// An invalid digit was found in the input.
114    #[non_exhaustive]
115    InvalidDigit,
116    /// The integer is too high to fit in the bounded integer's range.
117    #[non_exhaustive]
118    AboveMax,
119    /// The integer is too low to fit in the bounded integer's range.
120    #[non_exhaustive]
121    BelowMin,
122}
123
124#[must_use]
125pub const fn error_below_min() -> ParseError {
126    ParseError {
127        kind: ParseErrorKind::BelowMin,
128    }
129}
130#[must_use]
131pub const fn error_above_max() -> ParseError {
132    ParseError {
133        kind: ParseErrorKind::AboveMax,
134    }
135}