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 number = number.wrapping_neg();
175 }
176 Ok(Self { number, is_nonnegative, base })
177 }
178
179 pub fn parse_uint(s: &str, base: Option<&str>) -> Result<Self> {
181 let base = Base::unwrap_or_detect(base, s)?;
182 Ok(Self { number: Self::parse_digits(s, base)?, is_nonnegative: true, base })
183 }
184
185 fn parse_digits(s: &str, base: Base) -> Result<U256> {
188 let s = match s.get(..2) {
189 Some(p) if p.eq_ignore_ascii_case(base.prefix()) => &s[2..],
190 _ => s,
191 };
192 U256::from_str_radix(s, base as u64).map_err(Into::into)
193 }
194
195 pub const fn number(&self) -> U256 {
197 self.number
198 }
199
200 pub const fn is_nonnegative(&self) -> bool {
202 self.is_nonnegative
203 }
204
205 pub const fn with_base(self, base: Base) -> Self {
207 Self { base, ..self }
208 }
209
210 fn format(&self) -> String {
212 match self.base {
213 Base::Binary => format!("{:b}", self.number),
214 Base::Octal => format!("{:o}", self.number),
215 Base::Decimal if self.is_nonnegative => self.number.to_string(),
216 Base::Decimal => I256::from_raw(self.number).to_string().trim_start_matches('-').into(),
217 Base::Hexadecimal => format!("{:x}", self.number),
218 }
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use Base::*;
226
227 const NUMS: [i128; 44] = [
228 1,
229 2,
230 3,
231 5,
232 7,
233 8,
234 10,
235 11,
236 13,
237 16,
238 17,
239 19,
240 23,
241 29,
242 31,
243 32,
244 37,
245 41,
246 43,
247 47,
248 53,
249 59,
250 61,
251 64,
252 67,
253 71,
254 73,
255 79,
256 83,
257 89,
258 97,
259 100,
260 128,
261 200,
262 333,
263 500,
264 666,
265 1000,
266 6666,
267 10000,
268 i16::MAX as i128,
269 i32::MAX as i128,
270 i64::MAX as i128,
271 i128::MAX,
272 ];
273
274 fn number(n: i128) -> NumberWithBase {
275 NumberWithBase::from(I256::try_from(n).unwrap())
276 }
277
278 #[test]
279 fn can_parse_base() {
280 for (aliases, base) in [
281 (["2", "b", "bin", "binary"], Binary),
282 (["8", "o", "oct", "octal"], Octal),
283 (["10", "d", "dec", "decimal"], Decimal),
284 (["16", "h", "hex", "hexadecimal"], Hexadecimal),
285 ] {
286 for alias in aliases {
287 assert_eq!(alias.parse::<Base>().unwrap(), base, "{alias}");
288 assert_eq!(alias.to_uppercase().parse::<Base>().unwrap(), base, "{alias}");
289 }
290 }
291 assert!("3".parse::<Base>().is_err());
292 }
293
294 #[test]
295 fn can_detect_base() {
296 assert_eq!(Base::detect("0b100").unwrap(), Binary);
297 assert_eq!(Base::detect("0o100").unwrap(), Octal);
298 assert_eq!(Base::detect("100").unwrap(), Decimal);
299 assert_eq!(Base::detect("0x100").unwrap(), Hexadecimal);
300
301 assert_eq!(Base::detect("0B100").unwrap(), Binary);
302 assert_eq!(Base::detect("0O100").unwrap(), Octal);
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("0123456789abcdef").unwrap(), Hexadecimal);
310
311 let _ = Base::detect("0b234abc").unwrap_err();
312 let _ = Base::detect("0o89cba").unwrap_err();
313 let _ = Base::detect("0123456789abcdefg").unwrap_err();
314 let _ = Base::detect("0x123abclpmk").unwrap_err();
315 let _ = Base::detect("hello world").unwrap_err();
316 }
317
318 #[test]
319 fn strips_only_the_prefix_of_the_requested_base() {
320 for s in ["0b1010", "0B1010"] {
322 let number = NumberWithBase::parse_uint(s, Some("16")).unwrap();
323 assert_eq!(number.number(), U256::from(0xb1010));
324 assert_eq!(number.base, Hexadecimal);
325 }
326 let number = NumberWithBase::parse_int("-0b11", Some("hex")).unwrap();
327 assert_eq!(number.number(), U256::from(0xb11).wrapping_neg());
328 assert!(!number.is_nonnegative());
329
330 assert_eq!(
332 NumberWithBase::parse_uint("0x1f", Some("16")).unwrap().number(),
333 U256::from(31)
334 );
335 assert_eq!(
336 NumberWithBase::parse_uint("0X1F", Some("16")).unwrap().number(),
337 U256::from(31)
338 );
339 assert_eq!(NumberWithBase::parse_uint("0b11", Some("2")).unwrap().number(), U256::from(3));
340 assert_eq!(NumberWithBase::parse_uint("0o17", Some("8")).unwrap().number(), U256::from(15));
341 assert_eq!(NumberWithBase::parse_uint("42", Some("10")).unwrap().number(), U256::from(42));
342
343 assert!(NumberWithBase::parse_uint("0x10", Some("2")).is_err());
345 assert!(NumberWithBase::parse_uint("0b10", Some("10")).is_err());
346
347 let number = NumberWithBase::parse_uint("0b1010", None).unwrap();
349 assert_eq!(number.number(), U256::from(10));
350 assert_eq!(number.base, Binary);
351 }
352
353 #[test]
354 fn formats_positive_numbers() {
355 for n in NUMS {
356 let num = number(n);
357 assert_eq!(num.with_base(Binary).format(), format!("{n:b}"));
358 assert_eq!(num.with_base(Octal).format(), format!("{n:o}"));
359 assert_eq!(num.with_base(Decimal).format(), n.to_string());
360 assert_eq!(num.with_base(Hexadecimal).format(), format!("{n:x}"));
361
362 assert_eq!(format!("{num}"), n.to_string());
363 assert_eq!(format!("{num:x}"), format!("{n:x}"));
364 assert_eq!(format!("{num:#x}"), format!("{n:#x}"));
365 assert_eq!(format!("{:#?}", num.with_base(Binary)), format!("{n:#b}"));
366 assert_eq!(format!("{:#?}", num.with_base(Octal)), format!("{n:#o}"));
367 }
368 }
369
370 #[test]
371 fn formats_negative_numbers() {
372 for n in NUMS.into_iter().map(|n| -n).chain([i128::MIN]) {
373 let num = number(n);
374 assert_eq!(num.with_base(Binary).format(), format!("{n:1>256b}"));
376 assert_eq!(num.with_base(Hexadecimal).format(), format!("{n:f>64x}"));
377 assert_eq!(num.with_base(Decimal).format(), n.to_string().trim_start_matches('-'));
379 assert_eq!(format!("{num}"), n.to_string());
380 }
381 }
382}