1use alloy_primitives::{I256, Sign, U256, utils::ParseUnits};
2use eyre::Result;
3use std::{
4 convert::Infallible,
5 fmt::{Binary, Debug, Display, Formatter, LowerHex, Octal, Result as FmtResult, UpperHex},
6 num::IntErrorKind,
7 str::FromStr,
8};
9
10#[repr(u32)]
17#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
18pub enum Base {
19 Binary = 2,
20 Octal = 8,
21 #[default]
22 Decimal = 10,
23 Hexadecimal = 16,
24}
25
26impl Display for Base {
27 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
28 Display::fmt(&(*self as u32), f)
29 }
30}
31
32impl FromStr for Base {
33 type Err = eyre::Report;
34
35 fn from_str(s: &str) -> Result<Self, Self::Err> {
36 match s.to_lowercase().as_str() {
37 "2" | "b" | "bin" | "binary" => Ok(Self::Binary),
38 "8" | "o" | "oct" | "octal" => Ok(Self::Octal),
39 "10" | "d" | "dec" | "decimal" => Ok(Self::Decimal),
40 "16" | "h" | "hex" | "hexadecimal" => Ok(Self::Hexadecimal),
41 s => Err(eyre::eyre!(
42 "\
43Invalid base \"{s}\". Possible values:
44 2, b, bin, binary
45 8, o, oct, octal
4610, d, dec, decimal
4716, h, hex, hexadecimal"
48 )),
49 }
50 }
51}
52
53impl TryFrom<String> for Base {
54 type Error = eyre::Report;
55
56 fn try_from(s: String) -> Result<Self, Self::Error> {
57 Self::from_str(&s)
58 }
59}
60
61impl TryFrom<u32> for Base {
62 type Error = eyre::Report;
63
64 fn try_from(n: u32) -> Result<Self, Self::Error> {
65 match n {
66 2 => Ok(Self::Binary),
67 8 => Ok(Self::Octal),
68 10 => Ok(Self::Decimal),
69 16 => Ok(Self::Hexadecimal),
70 n => Err(eyre::eyre!("Invalid base \"{}\". Possible values: 2, 8, 10, 16", n)),
71 }
72 }
73}
74
75impl TryFrom<I256> for Base {
76 type Error = eyre::Report;
77
78 fn try_from(n: I256) -> Result<Self, Self::Error> {
79 Self::try_from(n.low_u32())
80 }
81}
82
83impl TryFrom<U256> for Base {
84 type Error = eyre::Report;
85
86 fn try_from(n: U256) -> Result<Self, Self::Error> {
87 Self::try_from(n.saturating_to::<u32>())
88 }
89}
90
91impl From<Base> for u32 {
92 fn from(b: Base) -> Self {
93 b as Self
94 }
95}
96
97impl From<Base> for String {
98 fn from(b: Base) -> Self {
99 b.to_string()
100 }
101}
102
103impl Base {
104 pub fn unwrap_or_detect(base: Option<&str>, s: impl AsRef<str>) -> Result<Self> {
105 match base {
106 Some(base) => base.parse(),
107 None => Self::detect(s),
108 }
109 }
110
111 pub fn detect(s: impl AsRef<str>) -> Result<Self> {
113 let s = s.as_ref();
114 match s {
115 _ if s.starts_with(['+', '-']) => Self::detect(&s[1..]),
117 _ if s.starts_with("0b") => Self::detect_prefixed(s, 2, Self::Binary, "binary"),
124 _ if s.starts_with("0o") => Self::detect_prefixed(s, 8, Self::Octal, "octal"),
125 _ if s.starts_with("0x") => {
126 Self::detect_prefixed(s, 16, Self::Hexadecimal, "hexadecimal")
127 }
128 _ => match U256::from_str_radix(s, 10) {
130 Ok(_) => Ok(Self::Decimal),
132 Err(_) => match U256::from_str_radix(s, 16) {
133 Ok(_) => Ok(Self::Hexadecimal),
134 Err(e) => Err(eyre::eyre!(
135 "could not autodetect base as neither decimal or hexadecimal: {e}"
136 )),
137 },
138 },
139 }
140 }
141
142 fn detect_prefixed(s: &str, radix: u32, base: Self, label: &str) -> Result<Self> {
145 match u64::from_str_radix(&s[2..], radix) {
146 Ok(_) => Ok(base),
147 Err(e) => match e.kind() {
148 IntErrorKind::PosOverflow => Ok(base),
149 _ => Err(eyre::eyre!("could not parse {label} value: {e}")),
150 },
151 }
152 }
153
154 pub const fn prefix(&self) -> &str {
156 match self {
157 Self::Binary => "0b",
158 Self::Octal => "0o",
159 Self::Decimal => "",
160 Self::Hexadecimal => "0x",
161 }
162 }
163}
164
165#[derive(Clone, Copy)]
195pub struct NumberWithBase {
196 number: U256,
198 is_nonnegative: bool,
200 base: Base,
202}
203
204impl std::ops::Deref for NumberWithBase {
205 type Target = U256;
206
207 fn deref(&self) -> &Self::Target {
208 &self.number
209 }
210}
211
212impl Debug for NumberWithBase {
214 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
215 let prefix = self.base.prefix();
216 if self.number.is_zero() {
217 f.pad_integral(true, prefix, "0")
218 } else {
219 let is_nonnegative = match self.base {
221 Base::Decimal => self.is_nonnegative,
222 _ => true,
223 };
224 f.pad_integral(is_nonnegative, prefix, &self.format())
225 }
226 }
227}
228
229impl Binary for NumberWithBase {
230 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
231 Debug::fmt(&self.with_base(Base::Binary), f)
232 }
233}
234
235impl Octal for NumberWithBase {
236 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
237 Debug::fmt(&self.with_base(Base::Octal), f)
238 }
239}
240
241impl Display for NumberWithBase {
242 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
243 Debug::fmt(&self.with_base(Base::Decimal), f)
244 }
245}
246
247impl LowerHex for NumberWithBase {
248 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
249 Debug::fmt(&self.with_base(Base::Hexadecimal), f)
250 }
251}
252
253impl UpperHex for NumberWithBase {
254 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
255 let n = format!("{self:x}").to_uppercase();
256 f.pad_integral(true, Base::Hexadecimal.prefix(), &n)
257 }
258}
259
260impl FromStr for NumberWithBase {
261 type Err = eyre::Report;
262
263 fn from_str(s: &str) -> Result<Self, Self::Err> {
264 Self::parse_int(s, None)
265 }
266}
267
268impl From<I256> for NumberWithBase {
269 fn from(number: I256) -> Self {
270 Self::new(number.into_raw(), !number.is_negative(), Base::default())
272 }
273}
274
275impl From<ParseUnits> for NumberWithBase {
276 fn from(value: ParseUnits) -> Self {
277 match value {
278 ParseUnits::U256(val) => val.into(),
279 ParseUnits::I256(val) => val.into(),
280 }
281 }
282}
283
284impl From<U256> for NumberWithBase {
285 fn from(number: U256) -> Self {
286 Self::new(number, true, Base::default())
287 }
288}
289
290impl From<NumberWithBase> for I256 {
291 fn from(n: NumberWithBase) -> Self {
292 Self::from_raw(n.number)
293 }
294}
295
296impl From<NumberWithBase> for U256 {
297 fn from(n: NumberWithBase) -> Self {
298 n.number
299 }
300}
301
302impl From<NumberWithBase> for String {
303 fn from(n: NumberWithBase) -> Self {
307 n.format()
308 }
309}
310
311impl NumberWithBase {
312 pub fn new(number: impl Into<U256>, is_nonnegative: bool, base: Base) -> Self {
313 Self { number: number.into(), is_nonnegative, base }
314 }
315
316 pub fn with_base(&self, base: Base) -> Self {
318 Self { number: self.number, is_nonnegative: self.is_nonnegative, base }
319 }
320
321 pub fn parse_int(s: &str, base: Option<&str>) -> Result<Self> {
324 let base = Base::unwrap_or_detect(base, s)?;
325 let (number, is_nonnegative) = Self::_parse_int(s, base)?;
326 Ok(Self { number, is_nonnegative, base })
327 }
328
329 pub fn parse_uint(s: &str, base: Option<&str>) -> Result<Self> {
332 let base = Base::unwrap_or_detect(base, s)?;
333 let number = Self::_parse_uint(s, base)?;
334 Ok(Self { number, is_nonnegative: true, base })
335 }
336
337 pub fn number(&self) -> U256 {
340 self.number
341 }
342
343 pub fn is_nonnegative(&self) -> bool {
345 self.is_nonnegative
346 }
347
348 pub fn base(&self) -> Base {
350 self.base
351 }
352
353 pub const fn prefix(&self) -> &str {
355 self.base.prefix()
356 }
357
358 pub fn set_base(&mut self, base: Base) -> &mut Self {
360 self.base = base;
361 self
362 }
363
364 pub fn format(&self) -> String {
370 let s = match self.base {
371 Base::Binary => format!("{:b}", self.number),
372 Base::Octal => format!("{:o}", self.number),
373 Base::Decimal => {
374 if self.is_nonnegative {
375 self.number.to_string()
376 } else {
377 let s = I256::from_raw(self.number).to_string();
378 s.strip_prefix('-').unwrap_or(&s).to_string()
379 }
380 }
381 Base::Hexadecimal => format!("{:x}", self.number),
382 };
383 if s.starts_with('0') { s.trim_start_matches('0').to_string() } else { s }
384 }
385
386 fn _parse_int(s: &str, base: Base) -> Result<(U256, bool)> {
387 let (s, sign) = get_sign(s);
388 let mut n = Self::_parse_uint(s, base)?;
389
390 let is_neg = matches!(sign, Sign::Negative);
391 if is_neg {
392 n = (!n).overflowing_add(U256::from(1)).0;
393 }
394
395 Ok((n, !is_neg))
396 }
397
398 fn _parse_uint(s: &str, base: Base) -> Result<U256> {
399 let s = match s.get(0..2) {
400 Some("0x" | "0X" | "0o" | "0O" | "0b" | "0B") => &s[2..],
401 _ => s,
402 };
403 U256::from_str_radix(s, base as u64).map_err(Into::into)
404 }
405}
406
407pub trait ToBase {
411 type Err;
412
413 fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err>;
433}
434
435impl ToBase for NumberWithBase {
436 type Err = Infallible;
437
438 fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err> {
439 let n = self.with_base(base);
440 if add_prefix { Ok(format!("{n:#?}")) } else { Ok(format!("{n:?}")) }
441 }
442}
443
444impl ToBase for I256 {
445 type Err = Infallible;
446
447 fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err> {
448 let n = NumberWithBase::from(*self).with_base(base);
449 if add_prefix { Ok(format!("{n:#?}")) } else { Ok(format!("{n:?}")) }
450 }
451}
452
453impl ToBase for U256 {
454 type Err = Infallible;
455
456 fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err> {
457 let n = NumberWithBase::from(*self).with_base(base);
458 if add_prefix { Ok(format!("{n:#?}")) } else { Ok(format!("{n:?}")) }
459 }
460}
461
462impl ToBase for String {
463 type Err = eyre::Report;
464
465 fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err> {
466 str::to_base(self, base, add_prefix)
467 }
468}
469
470impl ToBase for str {
471 type Err = eyre::Report;
472
473 fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err> {
474 let n = NumberWithBase::from_str(self)?.with_base(base);
475 if add_prefix { Ok(format!("{n:#?}")) } else { Ok(format!("{n:?}")) }
476 }
477}
478
479fn get_sign(s: &str) -> (&str, Sign) {
480 match s.as_bytes().first() {
481 Some(b'+') => (&s[1..], Sign::Positive),
482 Some(b'-') => (&s[1..], Sign::Negative),
483 _ => (s, Sign::Positive),
484 }
485}
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490 use Base::*;
491
492 const POS_NUM: [i128; 44] = [
493 1,
494 2,
495 3,
496 5,
497 7,
498 8,
499 10,
500 11,
501 13,
502 16,
503 17,
504 19,
505 23,
506 29,
507 31,
508 32,
509 37,
510 41,
511 43,
512 47,
513 53,
514 59,
515 61,
516 64,
517 67,
518 71,
519 73,
520 79,
521 83,
522 89,
523 97,
524 100,
525 128,
526 200,
527 333,
528 500,
529 666,
530 1000,
531 6666,
532 10000,
533 i16::MAX as i128,
534 i32::MAX as i128,
535 i64::MAX as i128,
536 i128::MAX,
537 ];
538
539 const NEG_NUM: [i128; 44] = [
540 -1,
541 -2,
542 -3,
543 -5,
544 -7,
545 -8,
546 -10,
547 -11,
548 -13,
549 -16,
550 -17,
551 -19,
552 -23,
553 -29,
554 -31,
555 -32,
556 -37,
557 -41,
558 -43,
559 -47,
560 -53,
561 -59,
562 -61,
563 -64,
564 -67,
565 -71,
566 -73,
567 -79,
568 -83,
569 -89,
570 -97,
571 -100,
572 -128,
573 -200,
574 -333,
575 -500,
576 -666,
577 -1000,
578 -6666,
579 -10000,
580 i16::MIN as i128,
581 i32::MIN as i128,
582 i64::MIN as i128,
583 i128::MIN,
584 ];
585
586 #[test]
587 fn test_defaults() {
588 let def: Base = Default::default();
589 assert!(matches!(def, Decimal));
590
591 let n: NumberWithBase = U256::ZERO.into();
592 assert!(matches!(n.base, Decimal));
593 let n: NumberWithBase = I256::ZERO.into();
594 assert!(matches!(n.base, Decimal));
595 }
596
597 #[test]
598 fn can_parse_base() {
599 assert_eq!("2".parse::<Base>().unwrap(), Binary);
600 assert_eq!("b".parse::<Base>().unwrap(), Binary);
601 assert_eq!("bin".parse::<Base>().unwrap(), Binary);
602 assert_eq!("binary".parse::<Base>().unwrap(), Binary);
603
604 assert_eq!("8".parse::<Base>().unwrap(), Octal);
605 assert_eq!("o".parse::<Base>().unwrap(), Octal);
606 assert_eq!("oct".parse::<Base>().unwrap(), Octal);
607 assert_eq!("octal".parse::<Base>().unwrap(), Octal);
608
609 assert_eq!("10".parse::<Base>().unwrap(), Decimal);
610 assert_eq!("d".parse::<Base>().unwrap(), Decimal);
611 assert_eq!("dec".parse::<Base>().unwrap(), Decimal);
612 assert_eq!("decimal".parse::<Base>().unwrap(), Decimal);
613
614 assert_eq!("16".parse::<Base>().unwrap(), Hexadecimal);
615 assert_eq!("h".parse::<Base>().unwrap(), Hexadecimal);
616 assert_eq!("hex".parse::<Base>().unwrap(), Hexadecimal);
617 assert_eq!("hexadecimal".parse::<Base>().unwrap(), Hexadecimal);
618 }
619
620 #[test]
621 fn can_detect_base() {
622 assert_eq!(Base::detect("0b100").unwrap(), Binary);
623 assert_eq!(Base::detect("0o100").unwrap(), Octal);
624 assert_eq!(Base::detect("100").unwrap(), Decimal);
625 assert_eq!(Base::detect("0x100").unwrap(), Hexadecimal);
626
627 assert_eq!(Base::detect("0123456789abcdef").unwrap(), Hexadecimal);
628
629 let _ = Base::detect("0b234abc").unwrap_err();
630 let _ = Base::detect("0o89cba").unwrap_err();
631 let _ = Base::detect("0123456789abcdefg").unwrap_err();
632 let _ = Base::detect("0x123abclpmk").unwrap_err();
633 let _ = Base::detect("hello world").unwrap_err();
634 }
635
636 #[test]
637 fn test_format_pos() {
638 let expected_2: Vec<_> = POS_NUM.iter().map(|n| format!("{n:b}")).collect();
639 let expected_8: Vec<_> = POS_NUM.iter().map(|n| format!("{n:o}")).collect();
640 let expected_10: Vec<_> = POS_NUM.iter().map(|n| format!("{n:}")).collect();
641 let expected_l16: Vec<_> = POS_NUM.iter().map(|n| format!("{n:x}")).collect();
642 let expected_u16: Vec<_> = POS_NUM.iter().map(|n| format!("{n:X}")).collect();
643
644 for (i, n) in POS_NUM.into_iter().enumerate() {
645 let mut num: NumberWithBase = I256::try_from(n).unwrap().into();
646
647 assert_eq!(num.set_base(Binary).format(), expected_2[i]);
648 assert_eq!(num.set_base(Octal).format(), expected_8[i]);
649 assert_eq!(num.set_base(Decimal).format(), expected_10[i]);
650 assert_eq!(num.set_base(Hexadecimal).format(), expected_l16[i]);
651 assert_eq!(num.set_base(Hexadecimal).format().to_uppercase(), expected_u16[i]);
652 }
653 }
654
655 #[test]
656 fn test_format_neg() {
657 let expected_2: Vec<_> = NEG_NUM.iter().map(|n| format!("{n:1>256b}")).collect();
660 let expected_8: Vec<_> = NEG_NUM
661 .iter()
662 .map(|n| {
663 let i = I256::try_from(*n).unwrap();
664 let mut u = NumberWithBase::from(i);
665 u.set_base(Octal);
666 u.format()
667 })
668 .collect();
669 let expected_10: Vec<_> =
671 NEG_NUM.iter().map(|n| format!("{n:}").trim_matches('-').to_string()).collect();
672 let expected_l16: Vec<_> = NEG_NUM.iter().map(|n| format!("{n:f>64x}")).collect();
673 let expected_u16: Vec<_> = NEG_NUM.iter().map(|n| format!("{n:F>64X}")).collect();
674
675 for (i, n) in NEG_NUM.into_iter().enumerate() {
676 let mut num: NumberWithBase = I256::try_from(n).unwrap().into();
677
678 assert_eq!(num.set_base(Binary).format(), expected_2[i]);
679 assert_eq!(num.set_base(Octal).format(), expected_8[i]);
680 assert_eq!(num.set_base(Decimal).format(), expected_10[i]);
681 assert_eq!(num.set_base(Hexadecimal).format(), expected_l16[i]);
682 assert_eq!(num.set_base(Hexadecimal).format().to_uppercase(), expected_u16[i]);
683 }
684 }
685
686 #[test]
687 fn test_fmt_macro() {
688 let nums: Vec<_> =
689 POS_NUM.into_iter().map(|n| NumberWithBase::from(I256::try_from(n).unwrap())).collect();
690
691 let actual_2: Vec<_> = nums.iter().map(|n| format!("{n:b}")).collect();
692 let actual_2_alt: Vec<_> = nums.iter().map(|n| format!("{n:#b}")).collect();
693 let actual_8: Vec<_> = nums.iter().map(|n| format!("{n:o}")).collect();
694 let actual_8_alt: Vec<_> = nums.iter().map(|n| format!("{n:#o}")).collect();
695 let actual_10: Vec<_> = nums.iter().map(|n| format!("{n:}")).collect();
696 let actual_10_alt: Vec<_> = nums.iter().map(|n| format!("{n:#}")).collect();
697 let actual_l16: Vec<_> = nums.iter().map(|n| format!("{n:x}")).collect();
698 let actual_l16_alt: Vec<_> = nums.iter().map(|n| format!("{n:#x}")).collect();
699 let actual_u16: Vec<_> = nums.iter().map(|n| format!("{n:X}")).collect();
700 let actual_u16_alt: Vec<_> = nums.iter().map(|n| format!("{n:#X}")).collect();
701
702 let expected_2: Vec<_> = POS_NUM.iter().map(|n| format!("{n:b}")).collect();
703 let expected_2_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#b}")).collect();
704 let expected_8: Vec<_> = POS_NUM.iter().map(|n| format!("{n:o}")).collect();
705 let expected_8_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#o}")).collect();
706 let expected_10: Vec<_> = POS_NUM.iter().map(|n| format!("{n:}")).collect();
707 let expected_10_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#}")).collect();
708 let expected_l16: Vec<_> = POS_NUM.iter().map(|n| format!("{n:x}")).collect();
709 let expected_l16_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#x}")).collect();
710 let expected_u16: Vec<_> = POS_NUM.iter().map(|n| format!("{n:X}")).collect();
711 let expected_u16_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#X}")).collect();
712
713 for (i, _) in POS_NUM.iter().enumerate() {
714 assert_eq!(actual_2[i], expected_2[i]);
715 assert_eq!(actual_2_alt[i], expected_2_alt[i]);
716
717 assert_eq!(actual_8[i], expected_8[i]);
718 assert_eq!(actual_8_alt[i], expected_8_alt[i]);
719
720 assert_eq!(actual_10[i], expected_10[i]);
721 assert_eq!(actual_10_alt[i], expected_10_alt[i]);
722
723 assert_eq!(actual_l16[i], expected_l16[i]);
724 assert_eq!(actual_l16_alt[i], expected_l16_alt[i]);
725
726 assert_eq!(actual_u16[i], expected_u16[i]);
727 assert_eq!(actual_u16_alt[i], expected_u16_alt[i]);
728 }
729 }
730}