1use core::fmt::{self, Display, Formatter};
2#[cfg(feature = "std")]
3use std::error::Error;
4
5#[cfg_attr(not(any(feature = "types", feature = "macro")), expect(unused))]
6pub trait FromStrRadix: Sized {
7 fn from_str_radix(s: &str, radix: u32) -> Result<Self, ParseError>;
8}
9
10macro_rules! from_str_radix_impl {
11 ($($ty:ident)*) => { $(
12 impl FromStrRadix for $ty {
13 fn from_str_radix(src: &str, radix: u32) -> Result<Self, ParseError> {
14 assert!(
15 (2..=36).contains(&radix),
16 "from_str_radix: radix must lie in the range `[2, 36]` - found {}",
17 radix,
18 );
19
20 let src = src.as_bytes();
21
22 let (positive, digits) = match *src {
23 [b'+', ref digits @ ..] => (true, digits),
24 [b'-', ref digits @ ..] => (false, digits),
25 ref digits => (true, digits),
26 };
27
28 if digits.is_empty() {
29 return Err(ParseError {
30 kind: ParseErrorKind::NoDigits,
31 });
32 }
33
34 let overflow_kind = if positive {
35 ParseErrorKind::AboveMax
36 } else {
37 ParseErrorKind::BelowMin
38 };
39
40 let mut result: Self = 0;
41
42 for &digit in digits {
43 let digit_value =
44 char::from(digit)
45 .to_digit(radix)
46 .ok_or_else(|| ParseError {
47 kind: ParseErrorKind::InvalidDigit,
48 })?;
49
50 result = result
51 .checked_mul(radix as Self)
52 .ok_or_else(|| ParseError {
53 kind: overflow_kind,
54 })?;
55
56 result = if positive {
57 result.checked_add(digit_value as Self)
58 } else {
59 result.checked_sub(digit_value as Self)
60 }
61 .ok_or_else(|| ParseError {
62 kind: overflow_kind,
63 })?;
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#[derive(Debug, Clone)]
79pub struct ParseError {
80 kind: ParseErrorKind,
81}
82
83impl ParseError {
84 #[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
102#[cfg(feature = "std")]
103#[cfg_attr(doc_cfg, doc(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#[cfg_attr(not(any(feature = "types", feature = "macro")), expect(unused))]
127pub fn error_below_min() -> ParseError {
128 ParseError {
129 kind: ParseErrorKind::BelowMin,
130 }
131}
132#[cfg_attr(not(any(feature = "types", feature = "macro")), expect(unused))]
133pub fn error_above_max() -> ParseError {
134 ParseError {
135 kind: ParseErrorKind::AboveMax,
136 }
137}