use alloy_primitives::{utils::ParseUnits, Sign, I256, U256};
use eyre::Result;
use std::{
convert::Infallible,
fmt::{Binary, Debug, Display, Formatter, LowerHex, Octal, Result as FmtResult, UpperHex},
num::IntErrorKind,
str::FromStr,
};
#[repr(u32)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub enum Base {
Binary = 2,
Octal = 8,
#[default]
Decimal = 10,
Hexadecimal = 16,
}
impl Display for Base {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
Display::fmt(&(*self as u32), f)
}
}
impl FromStr for Base {
type Err = eyre::Report;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"2" | "b" | "bin" | "binary" => Ok(Self::Binary),
"8" | "o" | "oct" | "octal" => Ok(Self::Octal),
"10" | "d" | "dec" | "decimal" => Ok(Self::Decimal),
"16" | "h" | "hex" | "hexadecimal" => Ok(Self::Hexadecimal),
s => Err(eyre::eyre!(
"\
Invalid base \"{s}\". Possible values:
2, b, bin, binary
8, o, oct, octal
10, d, dec, decimal
16, h, hex, hexadecimal"
)),
}
}
}
impl TryFrom<String> for Base {
type Error = eyre::Report;
fn try_from(s: String) -> Result<Self, Self::Error> {
Self::from_str(&s)
}
}
impl TryFrom<u32> for Base {
type Error = eyre::Report;
fn try_from(n: u32) -> Result<Self, Self::Error> {
match n {
2 => Ok(Self::Binary),
8 => Ok(Self::Octal),
10 => Ok(Self::Decimal),
16 => Ok(Self::Hexadecimal),
n => Err(eyre::eyre!("Invalid base \"{}\". Possible values: 2, 8, 10, 16", n)),
}
}
}
impl TryFrom<I256> for Base {
type Error = eyre::Report;
fn try_from(n: I256) -> Result<Self, Self::Error> {
Self::try_from(n.low_u32())
}
}
impl TryFrom<U256> for Base {
type Error = eyre::Report;
fn try_from(n: U256) -> Result<Self, Self::Error> {
Self::try_from(n.saturating_to::<u32>())
}
}
impl From<Base> for u32 {
fn from(b: Base) -> Self {
b as Self
}
}
impl From<Base> for String {
fn from(b: Base) -> Self {
b.to_string()
}
}
impl Base {
pub fn unwrap_or_detect(base: Option<&str>, s: impl AsRef<str>) -> Result<Self> {
match base {
Some(base) => base.parse(),
None => Self::detect(s),
}
}
pub fn detect(s: impl AsRef<str>) -> Result<Self> {
let s = s.as_ref();
match s {
_ if s.starts_with(['+', '-']) => Self::detect(&s[1..]),
_ if s.starts_with("0b") => match u64::from_str_radix(&s[2..], 2) {
Ok(_) => Ok(Self::Binary),
Err(e) => match e.kind() {
IntErrorKind::PosOverflow => Ok(Self::Binary),
_ => Err(eyre::eyre!("could not parse binary value: {}", e)),
},
},
_ if s.starts_with("0o") => match u64::from_str_radix(&s[2..], 8) {
Ok(_) => Ok(Self::Octal),
Err(e) => match e.kind() {
IntErrorKind::PosOverflow => Ok(Self::Octal),
_ => Err(eyre::eyre!("could not parse octal value: {e}")),
},
},
_ if s.starts_with("0x") => match u64::from_str_radix(&s[2..], 16) {
Ok(_) => Ok(Self::Hexadecimal),
Err(e) => match e.kind() {
IntErrorKind::PosOverflow => Ok(Self::Hexadecimal),
_ => Err(eyre::eyre!("could not parse hexadecimal value: {e}")),
},
},
_ => match U256::from_str_radix(s, 10) {
Ok(_) => Ok(Self::Decimal),
Err(_) => match U256::from_str_radix(s, 16) {
Ok(_) => Ok(Self::Hexadecimal),
Err(e) => Err(eyre::eyre!(
"could not autodetect base as neither decimal or hexadecimal: {e}"
)),
},
},
}
}
pub const fn prefix(&self) -> &str {
match self {
Self::Binary => "0b",
Self::Octal => "0o",
Self::Decimal => "",
Self::Hexadecimal => "0x",
}
}
}
#[derive(Clone, Copy)]
pub struct NumberWithBase {
number: U256,
is_nonnegative: bool,
base: Base,
}
impl std::ops::Deref for NumberWithBase {
type Target = U256;
fn deref(&self) -> &Self::Target {
&self.number
}
}
impl Debug for NumberWithBase {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let prefix = self.base.prefix();
if self.number.is_zero() {
f.pad_integral(true, prefix, "0")
} else {
let is_nonnegative = match self.base {
Base::Decimal => self.is_nonnegative,
_ => true,
};
f.pad_integral(is_nonnegative, prefix, &self.format())
}
}
}
impl Binary for NumberWithBase {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
Debug::fmt(&self.with_base(Base::Binary), f)
}
}
impl Octal for NumberWithBase {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
Debug::fmt(&self.with_base(Base::Octal), f)
}
}
impl Display for NumberWithBase {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
Debug::fmt(&self.with_base(Base::Decimal), f)
}
}
impl LowerHex for NumberWithBase {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
Debug::fmt(&self.with_base(Base::Hexadecimal), f)
}
}
impl UpperHex for NumberWithBase {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let n = format!("{self:x}").to_uppercase();
f.pad_integral(true, Base::Hexadecimal.prefix(), &n)
}
}
impl FromStr for NumberWithBase {
type Err = eyre::Report;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse_int(s, None)
}
}
impl From<I256> for NumberWithBase {
fn from(number: I256) -> Self {
Self::new(number.into_raw(), !number.is_negative(), Base::default())
}
}
impl From<ParseUnits> for NumberWithBase {
fn from(value: ParseUnits) -> Self {
match value {
ParseUnits::U256(val) => val.into(),
ParseUnits::I256(val) => val.into(),
}
}
}
impl From<U256> for NumberWithBase {
fn from(number: U256) -> Self {
Self::new(number, true, Base::default())
}
}
impl From<NumberWithBase> for I256 {
fn from(n: NumberWithBase) -> Self {
Self::from_raw(n.number)
}
}
impl From<NumberWithBase> for U256 {
fn from(n: NumberWithBase) -> Self {
n.number
}
}
impl From<NumberWithBase> for String {
fn from(n: NumberWithBase) -> Self {
n.format()
}
}
impl NumberWithBase {
pub fn new(number: impl Into<U256>, is_nonnegative: bool, base: Base) -> Self {
Self { number: number.into(), is_nonnegative, base }
}
pub fn with_base(&self, base: Base) -> Self {
Self { number: self.number, is_nonnegative: self.is_nonnegative, base }
}
pub fn parse_int(s: &str, base: Option<&str>) -> Result<Self> {
let base = Base::unwrap_or_detect(base, s)?;
let (number, is_nonnegative) = Self::_parse_int(s, base)?;
Ok(Self { number, is_nonnegative, base })
}
pub fn parse_uint(s: &str, base: Option<&str>) -> Result<Self> {
let base = Base::unwrap_or_detect(base, s)?;
let number = Self::_parse_uint(s, base)?;
Ok(Self { number, is_nonnegative: true, base })
}
pub fn number(&self) -> U256 {
self.number
}
pub fn is_nonnegative(&self) -> bool {
self.is_nonnegative
}
pub fn base(&self) -> Base {
self.base
}
pub const fn prefix(&self) -> &str {
self.base.prefix()
}
pub fn set_base(&mut self, base: Base) -> &mut Self {
self.base = base;
self
}
pub fn format(&self) -> String {
let s = match self.base {
Base::Binary => format!("{:b}", self.number),
Base::Octal => format!("{:o}", self.number),
Base::Decimal => {
if self.is_nonnegative {
self.number.to_string()
} else {
let s = I256::from_raw(self.number).to_string();
s.strip_prefix('-').unwrap_or(&s).to_string()
}
}
Base::Hexadecimal => format!("{:x}", self.number),
};
if s.starts_with('0') {
s.trim_start_matches('0').to_string()
} else {
s
}
}
fn _parse_int(s: &str, base: Base) -> Result<(U256, bool)> {
let (s, sign) = get_sign(s);
let mut n = Self::_parse_uint(s, base)?;
let is_neg = matches!(sign, Sign::Negative);
if is_neg {
n = (!n).overflowing_add(U256::from(1)).0;
}
Ok((n, !is_neg))
}
fn _parse_uint(s: &str, base: Base) -> Result<U256> {
let s = match s.get(0..2) {
Some("0x" | "0X" | "0o" | "0O" | "0b" | "0B") => &s[2..],
_ => s,
};
U256::from_str_radix(s, base as u64).map_err(Into::into)
}
}
pub trait ToBase {
type Err;
fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err>;
}
impl ToBase for NumberWithBase {
type Err = Infallible;
fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err> {
let n = self.with_base(base);
if add_prefix {
Ok(format!("{n:#?}"))
} else {
Ok(format!("{n:?}"))
}
}
}
impl ToBase for I256 {
type Err = Infallible;
fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err> {
let n = NumberWithBase::from(*self).with_base(base);
if add_prefix {
Ok(format!("{n:#?}"))
} else {
Ok(format!("{n:?}"))
}
}
}
impl ToBase for U256 {
type Err = Infallible;
fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err> {
let n = NumberWithBase::from(*self).with_base(base);
if add_prefix {
Ok(format!("{n:#?}"))
} else {
Ok(format!("{n:?}"))
}
}
}
impl ToBase for String {
type Err = eyre::Report;
fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err> {
str::to_base(self, base, add_prefix)
}
}
impl ToBase for str {
type Err = eyre::Report;
fn to_base(&self, base: Base, add_prefix: bool) -> Result<String, Self::Err> {
let n = NumberWithBase::from_str(self)?.with_base(base);
if add_prefix {
Ok(format!("{n:#?}"))
} else {
Ok(format!("{n:?}"))
}
}
}
fn get_sign(s: &str) -> (&str, Sign) {
match s.as_bytes().first() {
Some(b'+') => (&s[1..], Sign::Positive),
Some(b'-') => (&s[1..], Sign::Negative),
_ => (s, Sign::Positive),
}
}
#[cfg(test)]
mod tests {
use super::*;
use Base::*;
const POS_NUM: [i128; 44] = [
1,
2,
3,
5,
7,
8,
10,
11,
13,
16,
17,
19,
23,
29,
31,
32,
37,
41,
43,
47,
53,
59,
61,
64,
67,
71,
73,
79,
83,
89,
97,
100,
128,
200,
333,
500,
666,
1000,
6666,
10000,
i16::MAX as i128,
i32::MAX as i128,
i64::MAX as i128,
i128::MAX,
];
const NEG_NUM: [i128; 44] = [
-1,
-2,
-3,
-5,
-7,
-8,
-10,
-11,
-13,
-16,
-17,
-19,
-23,
-29,
-31,
-32,
-37,
-41,
-43,
-47,
-53,
-59,
-61,
-64,
-67,
-71,
-73,
-79,
-83,
-89,
-97,
-100,
-128,
-200,
-333,
-500,
-666,
-1000,
-6666,
-10000,
i16::MIN as i128,
i32::MIN as i128,
i64::MIN as i128,
i128::MIN,
];
#[test]
fn test_defaults() {
let def: Base = Default::default();
assert!(matches!(def, Decimal));
let n: NumberWithBase = U256::ZERO.into();
assert!(matches!(n.base, Decimal));
let n: NumberWithBase = I256::ZERO.into();
assert!(matches!(n.base, Decimal));
}
#[test]
fn can_parse_base() {
assert_eq!("2".parse::<Base>().unwrap(), Binary);
assert_eq!("b".parse::<Base>().unwrap(), Binary);
assert_eq!("bin".parse::<Base>().unwrap(), Binary);
assert_eq!("binary".parse::<Base>().unwrap(), Binary);
assert_eq!("8".parse::<Base>().unwrap(), Octal);
assert_eq!("o".parse::<Base>().unwrap(), Octal);
assert_eq!("oct".parse::<Base>().unwrap(), Octal);
assert_eq!("octal".parse::<Base>().unwrap(), Octal);
assert_eq!("10".parse::<Base>().unwrap(), Decimal);
assert_eq!("d".parse::<Base>().unwrap(), Decimal);
assert_eq!("dec".parse::<Base>().unwrap(), Decimal);
assert_eq!("decimal".parse::<Base>().unwrap(), Decimal);
assert_eq!("16".parse::<Base>().unwrap(), Hexadecimal);
assert_eq!("h".parse::<Base>().unwrap(), Hexadecimal);
assert_eq!("hex".parse::<Base>().unwrap(), Hexadecimal);
assert_eq!("hexadecimal".parse::<Base>().unwrap(), Hexadecimal);
}
#[test]
fn can_detect_base() {
assert_eq!(Base::detect("0b100").unwrap(), Binary);
assert_eq!(Base::detect("0o100").unwrap(), Octal);
assert_eq!(Base::detect("100").unwrap(), Decimal);
assert_eq!(Base::detect("0x100").unwrap(), Hexadecimal);
assert_eq!(Base::detect("0123456789abcdef").unwrap(), Hexadecimal);
let _ = Base::detect("0b234abc").unwrap_err();
let _ = Base::detect("0o89cba").unwrap_err();
let _ = Base::detect("0123456789abcdefg").unwrap_err();
let _ = Base::detect("0x123abclpmk").unwrap_err();
let _ = Base::detect("hello world").unwrap_err();
}
#[test]
fn test_format_pos() {
let expected_2: Vec<_> = POS_NUM.iter().map(|n| format!("{n:b}")).collect();
let expected_8: Vec<_> = POS_NUM.iter().map(|n| format!("{n:o}")).collect();
let expected_10: Vec<_> = POS_NUM.iter().map(|n| format!("{n:}")).collect();
let expected_l16: Vec<_> = POS_NUM.iter().map(|n| format!("{n:x}")).collect();
let expected_u16: Vec<_> = POS_NUM.iter().map(|n| format!("{n:X}")).collect();
for (i, n) in POS_NUM.into_iter().enumerate() {
let mut num: NumberWithBase = I256::try_from(n).unwrap().into();
assert_eq!(num.set_base(Binary).format(), expected_2[i]);
assert_eq!(num.set_base(Octal).format(), expected_8[i]);
assert_eq!(num.set_base(Decimal).format(), expected_10[i]);
assert_eq!(num.set_base(Hexadecimal).format(), expected_l16[i]);
assert_eq!(num.set_base(Hexadecimal).format().to_uppercase(), expected_u16[i]);
}
}
#[test]
fn test_format_neg() {
let expected_2: Vec<_> = NEG_NUM.iter().map(|n| format!("{n:1>256b}")).collect();
let expected_10: Vec<_> =
NEG_NUM.iter().map(|n| format!("{n:}").trim_matches('-').to_string()).collect();
let expected_l16: Vec<_> = NEG_NUM.iter().map(|n| format!("{n:f>64x}")).collect();
let expected_u16: Vec<_> = NEG_NUM.iter().map(|n| format!("{n:F>64X}")).collect();
for (i, n) in NEG_NUM.into_iter().enumerate() {
let mut num: NumberWithBase = I256::try_from(n).unwrap().into();
assert_eq!(num.set_base(Binary).format(), expected_2[i]);
assert_eq!(num.set_base(Decimal).format(), expected_10[i]);
assert_eq!(num.set_base(Hexadecimal).format(), expected_l16[i]);
assert_eq!(num.set_base(Hexadecimal).format().to_uppercase(), expected_u16[i]);
}
}
#[test]
fn test_fmt_macro() {
let nums: Vec<_> =
POS_NUM.into_iter().map(|n| NumberWithBase::from(I256::try_from(n).unwrap())).collect();
let actual_2: Vec<_> = nums.iter().map(|n| format!("{n:b}")).collect();
let actual_2_alt: Vec<_> = nums.iter().map(|n| format!("{n:#b}")).collect();
let actual_8: Vec<_> = nums.iter().map(|n| format!("{n:o}")).collect();
let actual_8_alt: Vec<_> = nums.iter().map(|n| format!("{n:#o}")).collect();
let actual_10: Vec<_> = nums.iter().map(|n| format!("{n:}")).collect();
let actual_10_alt: Vec<_> = nums.iter().map(|n| format!("{n:#}")).collect();
let actual_l16: Vec<_> = nums.iter().map(|n| format!("{n:x}")).collect();
let actual_l16_alt: Vec<_> = nums.iter().map(|n| format!("{n:#x}")).collect();
let actual_u16: Vec<_> = nums.iter().map(|n| format!("{n:X}")).collect();
let actual_u16_alt: Vec<_> = nums.iter().map(|n| format!("{n:#X}")).collect();
let expected_2: Vec<_> = POS_NUM.iter().map(|n| format!("{n:b}")).collect();
let expected_2_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#b}")).collect();
let expected_8: Vec<_> = POS_NUM.iter().map(|n| format!("{n:o}")).collect();
let expected_8_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#o}")).collect();
let expected_10: Vec<_> = POS_NUM.iter().map(|n| format!("{n:}")).collect();
let expected_10_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#}")).collect();
let expected_l16: Vec<_> = POS_NUM.iter().map(|n| format!("{n:x}")).collect();
let expected_l16_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#x}")).collect();
let expected_u16: Vec<_> = POS_NUM.iter().map(|n| format!("{n:X}")).collect();
let expected_u16_alt: Vec<_> = POS_NUM.iter().map(|n| format!("{n:#X}")).collect();
for (i, _) in POS_NUM.iter().enumerate() {
assert_eq!(actual_2[i], expected_2[i]);
assert_eq!(actual_2_alt[i], expected_2_alt[i]);
assert_eq!(actual_8[i], expected_8[i]);
assert_eq!(actual_8_alt[i], expected_8_alt[i]);
assert_eq!(actual_10[i], expected_10[i]);
assert_eq!(actual_10_alt[i], expected_10_alt[i]);
assert_eq!(actual_l16[i], expected_l16[i]);
assert_eq!(actual_l16_alt[i], expected_l16_alt[i]);
assert_eq!(actual_u16[i], expected_u16[i]);
assert_eq!(actual_u16_alt[i], expected_u16_alt[i]);
}
}
}