1use core::fmt::{self, Display, Formatter};
2#[cfg(feature = "std")]
3use std::error::Error;
4
5macro_rules! from_str_radix_impl {
6 ($($ty:ident)*) => { $(
7 impl $crate::__private::Dispatch<$ty> {
8 pub const fn from_ascii_radix(src: &[u8], radix: u32) -> Result<$ty, ParseError> {
11 assert!(
12 2 <= radix && radix <= 36,
13 "from_str_radix: radix must lie in the range `[2, 36]`",
14 );
15
16 macro_rules! yeet {
17 ($e:expr) => { return Err(ParseError { kind: $e }) };
18 }
19
20 let (positive, digits) = match *src {
21 [b'+', ref digits @ ..] => (true, digits),
22 [b'-', ref digits @ ..] => (false, digits),
23 ref digits => (true, digits),
24 };
25
26 if digits.is_empty() {
27 yeet!(ParseErrorKind::NoDigits);
28 }
29
30 let overflow_kind = if positive {
31 ParseErrorKind::AboveMax
32 } else {
33 ParseErrorKind::BelowMin
34 };
35
36 let mut result: $ty = 0;
37
38 let mut i = 0;
39 while i < digits.len() {
40 let digit = digits[i];
41
42 let Some(digit_value) = (digit as char).to_digit(radix) else {
43 yeet!(ParseErrorKind::InvalidDigit);
44 };
45
46 #[allow(clippy::cast_possible_wrap)]
47 #[allow(clippy::cast_possible_truncation)]
48 let Some(new_result) = result.checked_mul(radix as $ty) else {
49 yeet!(overflow_kind);
50 };
51
52 #[allow(clippy::cast_possible_wrap)]
53 #[allow(clippy::cast_possible_truncation)]
54 let Some(new_result) = (if positive {
55 new_result.checked_add(digit_value as $ty)
56 } else {
57 new_result.checked_sub(digit_value as $ty)
58 }) else {
59 yeet!(overflow_kind);
60 };
61
62 result = new_result;
63
64 i += 1;
65 }
66
67 Ok(result)
68 }
69 }
70 )* }
71}
72from_str_radix_impl! { u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize }
73
74#[derive(Debug, Clone)]
80pub struct ParseError {
81 kind: ParseErrorKind,
82}
83
84impl ParseError {
85 #[must_use]
87 pub fn kind(&self) -> ParseErrorKind {
88 self.kind
89 }
90}
91
92impl Display for ParseError {
93 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
94 match self.kind() {
95 ParseErrorKind::NoDigits => f.write_str("no digits found"),
96 ParseErrorKind::InvalidDigit => f.write_str("invalid digit found in string"),
97 ParseErrorKind::AboveMax => f.write_str("number too high to fit in target range"),
98 ParseErrorKind::BelowMin => f.write_str("number too low to fit in target range"),
99 }
100 }
101}
102
103#[cfg(feature = "std")]
104impl Error for ParseError {}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108#[non_exhaustive]
109pub enum ParseErrorKind {
110 #[non_exhaustive]
114 NoDigits,
115 #[non_exhaustive]
117 InvalidDigit,
118 #[non_exhaustive]
120 AboveMax,
121 #[non_exhaustive]
123 BelowMin,
124}
125
126#[must_use]
127pub const fn error_below_min() -> ParseError {
128 ParseError {
129 kind: ParseErrorKind::BelowMin,
130 }
131}
132#[must_use]
133pub const fn error_above_max() -> ParseError {
134 ParseError {
135 kind: ParseErrorKind::AboveMax,
136 }
137}