1use core::fmt;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum AsciiErrorKind {
21 Empty,
23 InvalidDigit,
26 PosOverflow,
28 NegOverflow,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub struct AsciiParseError {
38 pub kind: AsciiErrorKind,
40}
41
42impl fmt::Display for AsciiParseError {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 let description = match self.kind {
45 AsciiErrorKind::Empty => "cannot parse integer from empty byte string",
46 AsciiErrorKind::InvalidDigit => "invalid digit found in byte string",
47 AsciiErrorKind::PosOverflow => "number too large to fit in target type",
48 AsciiErrorKind::NegOverflow => "number too small to fit in target type",
49 };
50 description.fmt(f)
51 }
52}
53
54c0nst::c0nst! {
55pub c0nst trait FromAscii: Sized {
58 fn from_ascii(src: &[u8]) -> Result<Self, AsciiParseError>;
69
70 fn from_ascii_radix(src: &[u8], radix: u32) -> Result<Self, AsciiParseError>;
85}
86}
87
88const fn ascii_digit(b: u8) -> u32 {
90 match b {
91 b'0'..=b'9' => (b - b'0') as u32,
92 b'a'..=b'z' => (b - b'a') as u32 + 10,
93 b'A'..=b'Z' => (b - b'A') as u32 + 10,
94 _ => u32::MAX,
95 }
96}
97
98macro_rules! from_ascii_impl {
99 ($signed:literal, $($t:ty)*) => {$(
101 c0nst::c0nst! {
102 c0nst impl FromAscii for $t {
103 #[inline]
104 fn from_ascii(src: &[u8]) -> Result<Self, AsciiParseError> {
105 <Self as FromAscii>::from_ascii_radix(src, 10)
106 }
107
108 fn from_ascii_radix(src: &[u8], radix: u32) -> Result<Self, AsciiParseError> {
109 assert!(
110 radix >= 2 && radix <= 36,
111 "from_ascii_radix: radix must lie in the range `[2, 36]`"
112 );
113 if src.is_empty() {
114 return Err(AsciiParseError { kind: AsciiErrorKind::Empty });
115 }
116 let (positive, start) = match src[0] {
117 b'+' => (true, 1),
118 b'-' if $signed => (false, 1),
119 _ => (true, 0),
120 };
121 if src.len() == start {
122 return Err(AsciiParseError { kind: AsciiErrorKind::InvalidDigit });
124 }
125 let overflow_kind = if positive {
126 AsciiErrorKind::PosOverflow
127 } else {
128 AsciiErrorKind::NegOverflow
129 };
130 let mut result: $t = 0;
131 let mut i = start;
132 while i < src.len() {
133 let d = ascii_digit(src[i]);
134 if d >= radix {
135 return Err(AsciiParseError { kind: AsciiErrorKind::InvalidDigit });
136 }
137 result = match <$t>::checked_mul(result, radix as $t) {
138 Some(v) => v,
139 None => return Err(AsciiParseError { kind: overflow_kind }),
140 };
141 let step = if positive {
142 <$t>::checked_add(result, d as $t)
143 } else {
144 <$t>::checked_sub(result, d as $t)
145 };
146 result = match step {
147 Some(v) => v,
148 None => return Err(AsciiParseError { kind: overflow_kind }),
149 };
150 i += 1;
151 }
152 Ok(result)
153 }
154 }
155 }
156 )*};
157}
158
159from_ascii_impl!(false, usize u8 u16 u32 u64 u128);
160from_ascii_impl!(true, isize i8 i16 i32 i64 i128);
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn basic() {
168 assert_eq!(<u32 as FromAscii>::from_ascii(b"0"), Ok(0));
169 assert_eq!(<u32 as FromAscii>::from_ascii(b"+42"), Ok(42));
170 assert_eq!(<i32 as FromAscii>::from_ascii(b"-42"), Ok(-42));
171 assert_eq!(<u32 as FromAscii>::from_ascii_radix(b"ff", 16), Ok(255));
172 assert_eq!(<u32 as FromAscii>::from_ascii_radix(b"FF", 16), Ok(255));
173 assert_eq!(<u32 as FromAscii>::from_ascii_radix(b"101", 2), Ok(5));
174 assert_eq!(<u32 as FromAscii>::from_ascii_radix(b"z", 36), Ok(35));
175 assert_eq!(<i8 as FromAscii>::from_ascii(b"-128"), Ok(i8::MIN));
176 assert_eq!(<i8 as FromAscii>::from_ascii(b"127"), Ok(i8::MAX));
177 }
178
179 #[test]
180 fn errors() {
181 use AsciiErrorKind::*;
182 let kind = |r: Result<u8, AsciiParseError>| r.unwrap_err().kind;
183 assert_eq!(kind(<u8 as FromAscii>::from_ascii(b"")), Empty);
184 assert_eq!(kind(<u8 as FromAscii>::from_ascii(b"+")), InvalidDigit);
185 assert_eq!(kind(<u8 as FromAscii>::from_ascii(b"-1")), InvalidDigit); assert_eq!(kind(<u8 as FromAscii>::from_ascii(b"12x")), InvalidDigit);
187 assert_eq!(kind(<u8 as FromAscii>::from_ascii(b"1 2")), InvalidDigit);
188 assert_eq!(kind(<u8 as FromAscii>::from_ascii(b"256")), PosOverflow);
189 assert_eq!(
190 kind(<u8 as FromAscii>::from_ascii_radix(b"9", 8)),
191 InvalidDigit
192 );
193 assert_eq!(
194 <i8 as FromAscii>::from_ascii(b"-129").unwrap_err().kind,
195 NegOverflow
196 );
197 assert_eq!(
198 <i8 as FromAscii>::from_ascii(b"--1").unwrap_err().kind,
199 InvalidDigit
200 );
201 }
202
203 #[test]
204 #[should_panic(expected = "radix must lie in the range")]
205 fn bad_radix() {
206 let _ = <u8 as FromAscii>::from_ascii_radix(b"1", 37);
207 }
208
209 #[test]
210 fn agrees_with_from_str_radix() {
211 let inputs: &[&str] = &[
214 "0", "1", "127", "128", "255", "256", "-1", "-128", "-129", "+5", "ff", "FF", "7f",
215 "deadbeef", "", "+", "-", "12_3", "0x10",
216 ];
217 for radix in [2u32, 8, 10, 16, 36] {
218 for s in inputs {
219 let ours = <i32 as FromAscii>::from_ascii_radix(s.as_bytes(), radix);
220 let std = i32::from_str_radix(s, radix);
221 assert_eq!(ours.is_ok(), std.is_ok(), "{s:?} radix {radix}");
222 if let (Ok(a), Ok(b)) = (ours, std) {
223 assert_eq!(a, b, "{s:?} radix {radix}");
224 }
225 }
226 }
227 }
228}