1use alloy_primitives::{I256, U256};
4use eyre::Result;
5use std::{
6 fmt::{Debug, Display, Formatter, LowerHex, Result as FmtResult},
7 num::IntErrorKind,
8 str::FromStr,
9};
10
11#[repr(u32)]
15#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
16pub enum Base {
17 Binary = 2,
18 Octal = 8,
19 #[default]
20 Decimal = 10,
21 Hexadecimal = 16,
22}
23
24impl FromStr for Base {
25 type Err = eyre::Report;
26
27 fn from_str(s: &str) -> Result<Self, Self::Err> {
28 match s.to_lowercase().as_str() {
29 "2" | "b" | "bin" | "binary" => Ok(Self::Binary),
30 "8" | "o" | "oct" | "octal" => Ok(Self::Octal),
31 "10" | "d" | "dec" | "decimal" => Ok(Self::Decimal),
32 "16" | "h" | "hex" | "hexadecimal" => Ok(Self::Hexadecimal),
33 s => Err(eyre::eyre!(
34 "\
35Invalid base \"{s}\". Possible values:
36 2, b, bin, binary
37 8, o, oct, octal
3810, d, dec, decimal
3916, h, hex, hexadecimal"
40 )),
41 }
42 }
43}
44
45impl Base {
46 pub fn unwrap_or_detect(base: Option<&str>, s: &str) -> Result<Self> {
48 match base {
49 Some(base) => base.parse(),
50 None => Self::detect(s),
51 }
52 }
53
54 pub fn detect(s: &str) -> Result<Self> {
57 let s = s.strip_prefix(['+', '-']).unwrap_or(s);
58 let prefix = s.get(..2).map(str::to_ascii_lowercase);
59 match prefix.as_deref() {
60 Some("0b") => Self::detect_prefixed(s, Self::Binary, "binary"),
61 Some("0o") => Self::detect_prefixed(s, Self::Octal, "octal"),
62 Some("0x") => Self::detect_prefixed(s, Self::Hexadecimal, "hexadecimal"),
63 _ if U256::from_str_radix(s, 10).is_ok() => Ok(Self::Decimal),
65 _ => U256::from_str_radix(s, 16).map(|_| Self::Hexadecimal).map_err(|e| {
66 eyre::eyre!("could not autodetect base as neither decimal or hexadecimal: {e}")
67 }),
68 }
69 }
70
71 fn detect_prefixed(s: &str, base: Self, label: &str) -> Result<Self> {
74 match u64::from_str_radix(&s[2..], base as u32) {
75 Ok(_) => Ok(base),
76 Err(e) if *e.kind() == IntErrorKind::PosOverflow => Ok(base),
77 Err(e) => Err(eyre::eyre!("could not parse {label} value: {e}")),
78 }
79 }
80
81 pub const fn prefix(self) -> &'static str {
83 match self {
84 Self::Binary => "0b",
85 Self::Octal => "0o",
86 Self::Decimal => "",
87 Self::Hexadecimal => "0x",
88 }
89 }
90}
91
92#[derive(Clone, Copy)]
111pub struct NumberWithBase {
112 number: U256,
114 is_nonnegative: bool,
116 base: Base,
118}
119
120impl Debug for NumberWithBase {
121 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
122 let prefix = self.base.prefix();
123 if self.number.is_zero() {
124 return f.pad_integral(true, prefix, "0");
125 }
126 let is_nonnegative = self.base != Base::Decimal || self.is_nonnegative;
128 f.pad_integral(is_nonnegative, prefix, &self.format())
129 }
130}
131
132impl Display for NumberWithBase {
133 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
134 Debug::fmt(&self.with_base(Base::Decimal), f)
135 }
136}
137
138impl LowerHex for NumberWithBase {
139 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
140 Debug::fmt(&self.with_base(Base::Hexadecimal), f)
141 }
142}
143
144impl From<I256> for NumberWithBase {
145 fn from(number: I256) -> Self {
146 Self {
147 number: number.into_raw(),
148 is_nonnegative: !number.is_negative(),
149 base: Base::default(),
150 }
151 }
152}
153
154impl From<U256> for NumberWithBase {
155 fn from(number: U256) -> Self {
156 Self { number, is_nonnegative: true, base: Base::default() }
157 }
158}
159
160impl NumberWithBase {
161 pub fn parse_int(s: &str, base: Option<&str>) -> Result<Self> {
163 Self::parse_int_in(s, Base::unwrap_or_detect(base, s)?)
164 }
165
166 pub fn parse_int_in(s: &str, base: Base) -> Result<Self> {
168 let (s, is_nonnegative) = match s.strip_prefix('-') {
169 Some(s) => (s, false),
170 None => (s.strip_prefix('+').unwrap_or(s), true),
171 };
172 let mut number = Self::parse_digits(s, base)?;
173 if !is_nonnegative {
174 eyre::ensure!(
175 number <= I256::MIN.unsigned_abs(),
176 "value out of range for a signed 256-bit integer"
177 );
178 number = number.wrapping_neg();
179 }
180 Ok(Self { number, is_nonnegative, base })
181 }
182
183 pub fn parse_uint(s: &str, base: Option<&str>) -> Result<Self> {
185 let base = Base::unwrap_or_detect(base, s)?;
186 Ok(Self { number: Self::parse_digits(s, base)?, is_nonnegative: true, base })
187 }
188
189 fn parse_digits(s: &str, base: Base) -> Result<U256> {
192 let s = match s.get(..2) {
193 Some(p) if p.eq_ignore_ascii_case(base.prefix()) => &s[2..],
194 _ => s,
195 };
196 U256::from_str_radix(s, base as u64).map_err(Into::into)
197 }
198
199 pub const fn number(&self) -> U256 {
201 self.number
202 }
203
204 pub const fn is_nonnegative(&self) -> bool {
206 self.is_nonnegative
207 }
208
209 pub const fn with_base(self, base: Base) -> Self {
211 Self { base, ..self }
212 }
213
214 fn format(&self) -> String {
216 match self.base {
217 Base::Binary => format!("{:b}", self.number),
218 Base::Octal => format!("{:o}", self.number),
219 Base::Decimal if self.is_nonnegative => self.number.to_string(),
220 Base::Decimal => I256::from_raw(self.number).to_string().trim_start_matches('-').into(),
221 Base::Hexadecimal => format!("{:x}", self.number),
222 }
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229 use Base::*;
230
231 const NUMS: [i128; 44] = [
232 1,
233 2,
234 3,
235 5,
236 7,
237 8,
238 10,
239 11,
240 13,
241 16,
242 17,
243 19,
244 23,
245 29,
246 31,
247 32,
248 37,
249 41,
250 43,
251 47,
252 53,
253 59,
254 61,
255 64,
256 67,
257 71,
258 73,
259 79,
260 83,
261 89,
262 97,
263 100,
264 128,
265 200,
266 333,
267 500,
268 666,
269 1000,
270 6666,
271 10000,
272 i16::MAX as i128,
273 i32::MAX as i128,
274 i64::MAX as i128,
275 i128::MAX,
276 ];
277
278 fn number(n: i128) -> NumberWithBase {
279 NumberWithBase::from(I256::try_from(n).unwrap())
280 }
281
282 #[test]
283 fn can_parse_base() {
284 for (aliases, base) in [
285 (["2", "b", "bin", "binary"], Binary),
286 (["8", "o", "oct", "octal"], Octal),
287 (["10", "d", "dec", "decimal"], Decimal),
288 (["16", "h", "hex", "hexadecimal"], Hexadecimal),
289 ] {
290 for alias in aliases {
291 assert_eq!(alias.parse::<Base>().unwrap(), base, "{alias}");
292 assert_eq!(alias.to_uppercase().parse::<Base>().unwrap(), base, "{alias}");
293 }
294 }
295 assert!("3".parse::<Base>().is_err());
296 }
297
298 #[test]
299 fn can_detect_base() {
300 assert_eq!(Base::detect("0b100").unwrap(), Binary);
301 assert_eq!(Base::detect("0o100").unwrap(), Octal);
302 assert_eq!(Base::detect("100").unwrap(), Decimal);
303 assert_eq!(Base::detect("0x100").unwrap(), Hexadecimal);
304
305 assert_eq!(Base::detect("0B100").unwrap(), Binary);
306 assert_eq!(Base::detect("0O100").unwrap(), Octal);
307 assert_eq!(Base::detect("0X100").unwrap(), Hexadecimal);
308
309 assert_eq!(Base::detect("-0B100").unwrap(), Binary);
310 assert_eq!(Base::detect("-0O100").unwrap(), Octal);
311 assert_eq!(Base::detect("-0X100").unwrap(), Hexadecimal);
312
313 assert_eq!(Base::detect("0123456789abcdef").unwrap(), Hexadecimal);
314
315 let _ = Base::detect("0b234abc").unwrap_err();
316 let _ = Base::detect("0o89cba").unwrap_err();
317 let _ = Base::detect("0123456789abcdefg").unwrap_err();
318 let _ = Base::detect("0x123abclpmk").unwrap_err();
319 let _ = Base::detect("hello world").unwrap_err();
320 }
321
322 #[test]
323 fn strips_only_the_prefix_of_the_requested_base() {
324 for s in ["0b1010", "0B1010"] {
326 let number = NumberWithBase::parse_uint(s, Some("16")).unwrap();
327 assert_eq!(number.number(), U256::from(0xb1010));
328 assert_eq!(number.base, Hexadecimal);
329 }
330 let number = NumberWithBase::parse_int("-0b11", Some("hex")).unwrap();
331 assert_eq!(number.number(), U256::from(0xb11).wrapping_neg());
332 assert!(!number.is_nonnegative());
333
334 assert_eq!(
336 NumberWithBase::parse_uint("0x1f", Some("16")).unwrap().number(),
337 U256::from(31)
338 );
339 assert_eq!(
340 NumberWithBase::parse_uint("0X1F", Some("16")).unwrap().number(),
341 U256::from(31)
342 );
343 assert_eq!(NumberWithBase::parse_uint("0b11", Some("2")).unwrap().number(), U256::from(3));
344 assert_eq!(NumberWithBase::parse_uint("0o17", Some("8")).unwrap().number(), U256::from(15));
345 assert_eq!(NumberWithBase::parse_uint("42", Some("10")).unwrap().number(), U256::from(42));
346
347 assert!(NumberWithBase::parse_uint("0x10", Some("2")).is_err());
349 assert!(NumberWithBase::parse_uint("0b10", Some("10")).is_err());
350
351 let number = NumberWithBase::parse_uint("0b1010", None).unwrap();
353 assert_eq!(number.number(), U256::from(10));
354 assert_eq!(number.base, Binary);
355 }
356
357 #[test]
358 fn formats_positive_numbers() {
359 for n in NUMS {
360 let num = number(n);
361 assert_eq!(num.with_base(Binary).format(), format!("{n:b}"));
362 assert_eq!(num.with_base(Octal).format(), format!("{n:o}"));
363 assert_eq!(num.with_base(Decimal).format(), n.to_string());
364 assert_eq!(num.with_base(Hexadecimal).format(), format!("{n:x}"));
365
366 assert_eq!(format!("{num}"), n.to_string());
367 assert_eq!(format!("{num:x}"), format!("{n:x}"));
368 assert_eq!(format!("{num:#x}"), format!("{n:#x}"));
369 assert_eq!(format!("{:#?}", num.with_base(Binary)), format!("{n:#b}"));
370 assert_eq!(format!("{:#?}", num.with_base(Octal)), format!("{n:#o}"));
371 }
372 }
373
374 #[test]
375 fn formats_negative_numbers() {
376 for n in NUMS.into_iter().map(|n| -n).chain([i128::MIN]) {
377 let num = number(n);
378 assert_eq!(num.with_base(Binary).format(), format!("{n:1>256b}"));
380 assert_eq!(num.with_base(Hexadecimal).format(), format!("{n:f>64x}"));
381 assert_eq!(num.with_base(Decimal).format(), n.to_string().trim_start_matches('-'));
383 assert_eq!(format!("{num}"), n.to_string());
384 }
385 }
386}