use crate::{
parse::{FromStrBack, FromStrFront},
util,
};
use std::fmt::Debug;
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
pub enum ParseIntPartialError {
#[error("the given integer representation would cause overflow")]
Overflow,
#[error("the given integer representation would cause underflow")]
Underflow,
#[error(
"invalid input, expected: `['+' | '-']? ['0' - '9']+` for signed or `['0' - '9']+` for unsigned"
)]
Invalid,
#[error(
"empty input, expected: `['+' | '-']? ['0' - '9']+` for signed or `['0' - '9']+` for unsigned"
)]
Empty,
}
pub trait FromStrPartialRadixExt: util::sealed::Sealed + FromStrFront + FromStrBack {
#[allow(clippy::missing_errors_doc)]
fn from_str_radix_front(
input: &str,
radix: u32,
) -> Result<(Self, &str), <Self as FromStrFront>::Error>;
#[allow(clippy::missing_errors_doc)]
fn from_str_radix_back(
input: &str,
radix: u32,
) -> Result<(Self, &str), <Self as FromStrBack>::Error>;
}
trait FromStrRadixHelper: Copy {
const IS_SIGNED: bool;
const ZERO: Self;
fn checked_neg(self) -> Option<Self>;
fn checked_mul(self, other: u32) -> Option<Self>;
fn checked_sub(self, other: u32) -> Option<Self>;
fn checked_add(self, other: u32) -> Option<Self>;
}
fn from_str_radix_front<T: FromStrRadixHelper>(
input: &str,
radix: u32,
) -> Result<(T, &str), ParseIntPartialError> {
assert!(
matches!(radix, 2..=36),
"radix must be in `[2, 36]` - found {}",
radix
);
let (is_neg, rest) = match input.as_bytes() {
[b'-', ..] => {
if T::IS_SIGNED {
(true, &input[1..])
} else {
return Err(ParseIntPartialError::Invalid);
}
}
[b'+', ..] => (false, &input[1..]),
_ => (false, input),
};
if rest.is_empty() {
return Err(ParseIntPartialError::Empty);
}
let iter = rest
.as_bytes()
.iter()
.enumerate()
.map(|(idx, &byte)| (idx, (byte as char).to_digit(radix)));
let mut num = false;
let mut buf = T::ZERO;
let mut rest_start = 0;
if is_neg {
for (idx, maybe_digit) in iter {
let sub = match maybe_digit {
Some(val) => {
rest_start = idx + 1;
val
}
None => {
rest_start = idx;
break;
}
};
num = true;
buf = buf
.checked_mul(radix)
.ok_or(ParseIntPartialError::Underflow)?;
buf = buf
.checked_sub(sub)
.ok_or(ParseIntPartialError::Underflow)?;
}
} else {
for (idx, maybe_digit) in iter {
let add = match maybe_digit {
Some(val) => {
rest_start = idx + 1;
val
}
None => {
rest_start = idx;
break;
}
};
num = true;
buf = buf
.checked_mul(radix)
.ok_or(ParseIntPartialError::Overflow)?;
buf = buf.checked_add(add).ok_or(ParseIntPartialError::Overflow)?;
}
}
if num {
Ok((buf, &rest[rest_start..]))
} else {
Err(ParseIntPartialError::Invalid)
}
}
fn from_str_radix_back<T: FromStrRadixHelper>(
input: &str,
radix: u32,
) -> Result<(T, &str), ParseIntPartialError> {
assert!(
matches!(radix, 2..=36),
"radix must be in `[2, 36]` - found {}",
radix
);
if input.is_empty() {
return Err(ParseIntPartialError::Empty);
}
let mut num = false;
let mut buf = T::ZERO;
let mut len = 0;
let mut factor = Some(1);
let iter = input.as_bytes().iter().rev();
if T::IS_SIGNED {
let mut is_neg = false;
for &byte in iter {
let sub = match (byte as char).to_digit(radix) {
Some(val) => val,
None => {
match byte {
b'-' => {
len += 1;
is_neg = true;
}
b'+' => len += 1,
_ => {}
}
break;
}
};
len += 1;
num = true;
let fac = factor.ok_or(ParseIntPartialError::Underflow)?;
buf = fac
.checked_mul(sub)
.and_then(|s| buf.checked_sub(s))
.ok_or(ParseIntPartialError::Underflow)?;
factor = fac.checked_mul(radix);
}
if !is_neg {
buf = buf.checked_neg().ok_or(ParseIntPartialError::Overflow)?;
}
} else {
for &byte in iter {
let add = match (byte as char).to_digit(radix) {
Some(val) => val,
None => {
if byte == b'+' {
len += 1;
}
break;
}
};
len += 1;
num = true;
let fac = factor.ok_or(ParseIntPartialError::Overflow)?;
buf = fac
.checked_mul(add)
.and_then(|a| buf.checked_add(a))
.ok_or(ParseIntPartialError::Overflow)?;
factor = fac.checked_mul(radix);
}
}
if num {
Ok((buf, &input[..input.len() - len]))
} else {
Err(ParseIntPartialError::Invalid)
}
}
macro_rules! int_impl {
(int $int:ty) => {
impl FromStrRadixHelper for $int {
const IS_SIGNED: bool = true;
const ZERO: Self = 0;
#[inline]
fn checked_neg(self) -> Option<Self> {
self.checked_neg()
}
#[inline]
fn checked_mul(self, other: u32) -> Option<Self> {
Self::checked_mul(self, other as Self)
}
#[inline]
fn checked_sub(self, other: u32) -> Option<Self> {
Self::checked_sub(self, other as Self)
}
#[inline]
fn checked_add(self, other: u32) -> Option<Self> {
Self::checked_add(self, other as Self)
}
}
int_impl!($int);
};
(uint $int:ty) => {
impl FromStrRadixHelper for $int {
const IS_SIGNED: bool = false;
const ZERO: Self = 0;
#[inline]
fn checked_neg(self) -> Option<Self> {
Some(self)
}
#[inline]
fn checked_mul(self, other: u32) -> Option<Self> {
Self::checked_mul(self, other as Self)
}
#[inline]
fn checked_sub(self, other: u32) -> Option<Self> {
Self::checked_sub(self, other as Self)
}
#[inline]
fn checked_add(self, other: u32) -> Option<Self> {
Self::checked_add(self, other as Self)
}
}
int_impl!($int);
};
($int:ty) => {
impl FromStrFront for $int {
type Error = ParseIntPartialError;
fn from_str_front(input: &str) -> Result<(Self, &str), Self::Error> {
Self::from_str_radix_front(input, 10)
}
}
impl FromStrBack for $int {
type Error = ParseIntPartialError;
fn from_str_back(input: &str) -> Result<(Self, &str), Self::Error> {
Self::from_str_radix_back(input, 10)
}
}
impl FromStrPartialRadixExt for $int {
fn from_str_radix_front(
input: &str,
radix: u32,
) -> Result<(Self, &str), <Self as FromStrFront>::Error> {
from_str_radix_front(input, radix)
}
fn from_str_radix_back(
input: &str,
radix: u32,
) -> Result<(Self, &str), <Self as FromStrBack>::Error> {
from_str_radix_back(input, radix)
}
}
};
}
int_impl!(int i8);
int_impl!(int i16);
int_impl!(int i32);
int_impl!(int i64);
int_impl!(int i128);
int_impl!(int isize);
int_impl!(uint u8);
int_impl!(uint u16);
int_impl!(uint u32);
int_impl!(uint u64);
int_impl!(uint u128);
int_impl!(uint usize);
#[cfg(test)]
mod tests {
use super::*;
mod front {
use super::*;
#[test]
fn invalid_prefix() {
assert_eq!(
u8::from_str_radix_front("!!!", 10),
Err(ParseIntPartialError::Invalid)
);
assert_eq!(
i8::from_str_radix_front("-!!!", 10),
Err(ParseIntPartialError::Invalid)
);
}
#[test]
fn valid() {
assert_eq!(u8::from_str_radix_front("255", 10), Ok((255, "")));
assert_eq!(u8::from_str_radix_front("255!!!", 10), Ok((255, "!!!")));
assert_eq!(i8::from_str_radix_front("-128", 10), Ok((-128, "")));
assert_eq!(i8::from_str_radix_front("127!!!", 10), Ok((127, "!!!")));
}
#[test]
fn over_under_flow() {
assert_eq!(
u8::from_str_radix_front("2550", 10),
Err(ParseIntPartialError::Overflow)
);
assert_eq!(
u8::from_str_radix_front("256", 10),
Err(ParseIntPartialError::Overflow)
);
assert_eq!(
i8::from_str_radix_front("-129", 10),
Err(ParseIntPartialError::Underflow)
);
assert_eq!(
i8::from_str_radix_front("128", 10),
Err(ParseIntPartialError::Overflow)
);
}
}
mod back {
use super::*;
#[test]
fn invalid_suffix() {
assert_eq!(
u8::from_str_radix_back("!!!", 10),
Err(ParseIntPartialError::Invalid)
);
assert_eq!(
i8::from_str_radix_back("!!!-", 10),
Err(ParseIntPartialError::Invalid)
);
}
#[test]
fn valid() {
assert_eq!(u8::from_str_radix_back("255", 10), Ok((255, "")));
assert_eq!(u8::from_str_radix_back("!!!255", 10), Ok((255, "!!!")));
assert_eq!(i8::from_str_radix_back("-128", 10), Ok((-128, "")));
assert_eq!(i8::from_str_radix_back("!!!127", 10), Ok((127, "!!!")));
}
#[test]
fn over_under_flow() {
assert_eq!(
u8::from_str_radix_back("2550", 10),
Err(ParseIntPartialError::Overflow)
);
assert_eq!(
u8::from_str_radix_back("256", 10),
Err(ParseIntPartialError::Overflow)
);
assert_eq!(
i8::from_str_radix_back("-129", 10),
Err(ParseIntPartialError::Underflow)
);
assert_eq!(
i8::from_str_radix_back("128", 10),
Err(ParseIntPartialError::Overflow)
);
}
}
}