1use alloc::string::String;
6use core::fmt;
7use core::str::FromStr;
8
9use internals::write_err;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
21#[non_exhaustive]
22pub struct ParseIntError {
23 pub(crate) input: String,
24 bits: u8,
26 is_signed: bool,
30 pub(crate) source: core::num::ParseIntError,
31}
32
33impl ParseIntError {
34 pub fn input(&self) -> &str { &self.input }
36}
37
38impl fmt::Display for ParseIntError {
39 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
40 let signed = if self.is_signed { "signed" } else { "unsigned" };
41 let n = if self.bits == 8 { "n" } else { "" };
42 write_err!(f, "failed to parse '{}' as a{} {}-bit {} integer", self.input, n, self.bits, signed; self.source)
43 }
44}
45
46#[cfg(feature = "std")]
47impl std::error::Error for ParseIntError {
48 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.source) }
49}
50
51impl From<ParseIntError> for core::num::ParseIntError {
52 fn from(value: ParseIntError) -> Self { value.source }
53}
54
55impl AsRef<core::num::ParseIntError> for ParseIntError {
56 fn as_ref(&self) -> &core::num::ParseIntError { &self.source }
57}
58
59pub trait Integer: FromStr<Err = core::num::ParseIntError> + TryFrom<i8> + Sized {}
62
63macro_rules! impl_integer {
64 ($($type:ty),* $(,)?) => {
65 $(
66 impl Integer for $type {}
67 )*
68 }
69}
70
71impl_integer!(u8, i8, u16, i16, u32, i32, u64, i64, u128, i128);
72
73pub fn int<T: Integer, S: AsRef<str> + Into<String>>(s: S) -> Result<T, ParseIntError> {
78 s.as_ref().parse().map_err(|error| {
79 ParseIntError {
80 input: s.into(),
81 bits: u8::try_from(core::mem::size_of::<T>() * 8).expect("max is 128 bits for u128"),
82 is_signed: T::try_from(-1i8).is_ok(),
86 source: error,
87 }
88 })
89}
90
91pub fn hex_u32<S: AsRef<str> + Into<String>>(s: S) -> Result<u32, ParseIntError> {
95 let stripped = strip_hex_prefix(s.as_ref());
96 u32::from_str_radix(stripped, 16).map_err(|error| ParseIntError {
97 input: s.into(),
98 bits: 32,
99 is_signed: false,
100 source: error,
101 })
102}
103
104pub fn hex_u128<S: AsRef<str> + Into<String>>(s: S) -> Result<u128, ParseIntError> {
108 let stripped = strip_hex_prefix(s.as_ref());
109 u128::from_str_radix(stripped, 16).map_err(|error| ParseIntError {
110 input: s.into(),
111 bits: 128,
112 is_signed: false,
113 source: error,
114 })
115}
116
117pub(crate) fn strip_hex_prefix(s: &str) -> &str {
119 if let Some(stripped) = s.strip_prefix("0x") {
120 stripped
121 } else if let Some(stripped) = s.strip_prefix("0X") {
122 stripped
123 } else {
124 s
125 }
126}
127
128#[macro_export]
131macro_rules! impl_tryfrom_str_from_int_infallible {
132 ($($from:ty, $to:ident, $inner:ident, $fn:ident);*) => {
133 $(
134 impl core::convert::TryFrom<$from> for $to {
135 type Error = $crate::parse::ParseIntError;
136
137 fn try_from(s: $from) -> core::result::Result<Self, Self::Error> {
138 $crate::parse::int::<$inner, $from>(s).map($to::$fn)
139 }
140 }
141 )*
142 }
143}
144
145#[macro_export]
150macro_rules! impl_parse_str_from_int_infallible {
151 ($to:ident, $inner:ident, $fn:ident) => {
152 #[cfg(all(feature = "alloc", not(feature = "std")))]
153 $crate::impl_tryfrom_str_from_int_infallible!(&str, $to, $inner, $fn; alloc::string::String, $to, $inner, $fn; alloc::boxed::Box<str>, $to, $inner, $fn);
154 #[cfg(feature = "std")]
155 $crate::impl_tryfrom_str_from_int_infallible!(&str, $to, $inner, $fn; std::string::String, $to, $inner, $fn; std::boxed::Box<str>, $to, $inner, $fn);
156
157 impl core::str::FromStr for $to {
158 type Err = $crate::parse::ParseIntError;
159
160 fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
161 $crate::parse::int::<$inner, &str>(s).map($to::$fn)
162 }
163 }
164
165 }
166}
167
168#[macro_export]
170macro_rules! impl_tryfrom_str {
171 ($($from:ty, $to:ty, $err:ty, $inner_fn:expr);*) => {
172 $(
173 impl core::convert::TryFrom<$from> for $to {
174 type Error = $err;
175
176 fn try_from(s: $from) -> core::result::Result<Self, Self::Error> {
177 $inner_fn(s)
178 }
179 }
180 )*
181 }
182}
183
184#[macro_export]
186macro_rules! impl_parse_str {
187 ($to:ty, $err:ty, $inner_fn:expr) => {
188 $crate::impl_tryfrom_str!(&str, $to, $err, $inner_fn; String, $to, $err, $inner_fn; Box<str>, $to, $err, $inner_fn);
189
190 impl core::str::FromStr for $to {
191 type Err = $err;
192
193 fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
194 $inner_fn(s)
195 }
196 }
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 #[test]
205 fn parse_u32_from_hex_prefixed() {
206 let want = 171;
207 let got = hex_u32("0xab").expect("failed to parse prefixed hex");
208 assert_eq!(got, want);
209 }
210
211 #[test]
212 fn parse_u32_from_hex_no_prefix() {
213 let want = 171;
214 let got = hex_u32("ab").expect("failed to parse non-prefixed hex");
215 assert_eq!(got, want);
216 }
217
218 #[test]
219 fn parse_u128_from_hex_prefixed() {
220 let want = 3735928559;
221 let got = hex_u128("0xdeadbeef").expect("failed to parse prefixed hex");
222 assert_eq!(got, want);
223 }
224
225 #[test]
226 fn parse_u128_from_hex_no_prefix() {
227 let want = 3735928559;
228 let got = hex_u128("deadbeef").expect("failed to parse non-prefixed hex");
229 assert_eq!(got, want);
230 }
231}