#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseIntegerError {
kind: ErrorKind,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum ErrorKind {
Empty,
InvalidDigit,
Overflow,
Underflow,
}
impl ParseIntegerError {
fn desc(&self) -> &str {
match self.kind {
ErrorKind::Empty => "cannot parse integer without digits",
ErrorKind::InvalidDigit => "invalid digit found in slice",
ErrorKind::Overflow => "number too large to fit in target type",
ErrorKind::Underflow => "number too small to fit in target type",
}
}
}
impl std::fmt::Display for ParseIntegerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.desc().fmt(f)
}
}
impl std::error::Error for ParseIntegerError {
fn description(&self) -> &str {
self.desc()
}
}
#[track_caller]
pub fn to_unsigned<I: MinNumTraits>(bytes: &[u8]) -> Result<I, ParseIntegerError> {
to_unsigned_with_radix(bytes, 10)
}
pub fn to_unsigned_with_radix<I: MinNumTraits>(bytes: &[u8], radix: u32) -> Result<I, ParseIntegerError> {
assert!(
(2..=36).contains(&radix),
"radix must lie in the range 2..=36, found {radix}"
);
let base = I::from_u32(radix).expect("radix can be represented as integer");
if bytes.is_empty() {
return Err(ParseIntegerError { kind: ErrorKind::Empty });
}
let mut result = I::ZERO;
for &digit in bytes {
let x = match char::from(digit).to_digit(radix).and_then(I::from_u32) {
Some(x) => x,
None => {
return Err(ParseIntegerError {
kind: ErrorKind::InvalidDigit,
})
}
};
result = match result.checked_mul(base) {
Some(result) => result,
None => {
return Err(ParseIntegerError {
kind: ErrorKind::Overflow,
})
}
};
result = match result.checked_add(x) {
Some(result) => result,
None => {
return Err(ParseIntegerError {
kind: ErrorKind::Overflow,
})
}
};
}
Ok(result)
}
pub fn to_signed<I: MinNumTraits>(bytes: &[u8]) -> Result<I, ParseIntegerError> {
to_signed_with_radix(bytes, 10)
}
pub fn to_signed_with_radix<I: MinNumTraits>(bytes: &[u8], radix: u32) -> Result<I, ParseIntegerError> {
assert!(
(2..=36).contains(&radix),
"radix must lie in the range 2..=36, found {radix}"
);
let base = I::from_u32(radix).expect("radix can be represented as integer");
if bytes.is_empty() {
return Err(ParseIntegerError { kind: ErrorKind::Empty });
}
let digits = match bytes[0] {
b'+' => return to_unsigned_with_radix(&bytes[1..], radix),
b'-' => &bytes[1..],
_ => return to_unsigned_with_radix(bytes, radix),
};
if digits.is_empty() {
return Err(ParseIntegerError { kind: ErrorKind::Empty });
}
let mut result = I::ZERO;
for &digit in digits {
let x = match char::from(digit).to_digit(radix).and_then(I::from_u32) {
Some(x) => x,
None => {
return Err(ParseIntegerError {
kind: ErrorKind::InvalidDigit,
})
}
};
result = match result.checked_mul(base) {
Some(result) => result,
None => {
return Err(ParseIntegerError {
kind: ErrorKind::Underflow,
})
}
};
result = match result.checked_sub(x) {
Some(result) => result,
None => {
return Err(ParseIntegerError {
kind: ErrorKind::Underflow,
})
}
};
}
Ok(result)
}
pub trait MinNumTraits: Sized + Copy + TryFrom<u32> {
const ZERO: Self;
fn from_u32(n: u32) -> Option<Self> {
Self::try_from(n).ok()
}
fn checked_mul(self, rhs: Self) -> Option<Self>;
fn checked_add(self, rhs: Self) -> Option<Self>;
fn checked_sub(self, v: Self) -> Option<Self>;
}
macro_rules! impl_checked {
($f:ident) => {
fn $f(self, rhs: Self) -> Option<Self> {
Self::$f(self, rhs)
}
};
}
macro_rules! min_num_traits {
($t:ty) => {
impl MinNumTraits for $t {
const ZERO: Self = 0;
impl_checked!(checked_add);
impl_checked!(checked_mul);
impl_checked!(checked_sub);
}
};
}
min_num_traits!(i32);
min_num_traits!(i64);
min_num_traits!(u64);
min_num_traits!(u8);
min_num_traits!(usize);