use std::ops::Mul;
use crate::{constants::*, error::ParseIntErr};
pub trait FromAscii: Sized {
#[inline]
fn atoi(s: impl AsRef<[u8]>) -> Result<Self, ParseIntErr> {
Self::bytes_to_int(s.as_ref())
}
fn bytes_to_int(s: &[u8]) -> Result<Self, ParseIntErr>;
}
#[inline(always)]
fn parse_byte<N>(byte: u8, pow10: N) -> Result<N, ParseIntErr>
where
N: From<u8> + Mul<Output = N>,
{
let d = byte.wrapping_sub(ASCII_TO_INT_FACTOR);
if d > 9 {
return Err(ParseIntErr::with_byte(byte));
}
Ok(N::from(d) * pow10)
}
macro_rules! unsigned_from_ascii {
($int:ty, $const_table:ident) => {
impl FromAscii for $int {
#[inline]
fn bytes_to_int(mut bytes: &[u8]) -> Result<Self, ParseIntErr> {
if bytes.len() > $const_table.len() {
return Err(ParseIntErr::Overflow);
}
let mut result: Self = 0;
let mut len = bytes.len();
let mut idx = $const_table.len().wrapping_sub(len);
unsafe {
while len >= 4 {
match (
bytes.get_unchecked(..4),
$const_table.get_unchecked(idx..idx + 4),
) {
([a, b, c, d], [p1, p2, p3, p4]) => {
let r1 = parse_byte(*a, *p1)?;
let r2 = parse_byte(*b, *p2)?;
let r3 = parse_byte(*c, *p3)?;
let r4 = parse_byte(*d, *p4)?;
result = result.wrapping_add(r1 + r2 + r3 + r4);
}
_ => unreachable!(),
}
len -= 4;
idx += 4;
bytes = bytes.get_unchecked(4..);
}
for offset in 0..len {
let a = bytes.get_unchecked(offset);
let p = $const_table.get_unchecked(idx + offset);
let r = parse_byte(*a, *p)?;
result = result.wrapping_add(r);
}
}
Ok(result)
}
}
};
(@u8, $const_table:ident) => {
impl FromAscii for u8 {
#[inline]
fn bytes_to_int(bytes: &[u8]) -> Result<Self, ParseIntErr> {
if bytes.len() > $const_table.len() {
return Err(ParseIntErr::Overflow);
}
let mut result: Self = 0;
let len = bytes.len();
let idx = $const_table.len().wrapping_sub(len);
unsafe {
for offset in 0..len {
let a = bytes.get_unchecked(offset);
let p = $const_table.get_unchecked(idx + offset);
let r = parse_byte(*a, *p)?;
result = result.wrapping_add(r);
}
}
Ok(result)
}
}
};
}
macro_rules! signed_from_ascii {
($int:ty, $unsigned_version:ty) => {
impl FromAscii for $int {
fn bytes_to_int(bytes: &[u8]) -> Result<Self, ParseIntErr> {
if bytes.starts_with(b"-") {
Ok((<$unsigned_version>::bytes_to_int(&bytes[1..])? as Self).wrapping_neg())
} else {
Ok(<$unsigned_version>::bytes_to_int(bytes)? as Self)
}
}
}
};
}
unsigned_from_ascii!(@u8, POW10_U8);
unsigned_from_ascii!(u16, POW10_U16);
unsigned_from_ascii!(u32, POW10_U32);
unsigned_from_ascii!(u64, POW10_U64);
unsigned_from_ascii!(usize, POW10_USIZE);
signed_from_ascii!(i8, u8);
signed_from_ascii!(i16, u16);
signed_from_ascii!(i32, u32);
signed_from_ascii!(i64, u64);
signed_from_ascii!(isize, usize);
#[cfg(test)]
mod tests {
use super::{FromAscii, ParseIntErr};
#[test]
fn to_u8() {
assert_eq!(u8::atoi("123"), Ok(123));
assert_eq!(u8::atoi("256"), Ok(0));
assert_eq!(u8::atoi("257"), Ok(1));
assert_eq!(u8::atoi("!23"), Err(ParseIntErr::with_byte(b'!')));
assert_eq!(u8::atoi("1000"), Err(ParseIntErr::Overflow));
}
#[test]
fn overflow_isize() {
assert_eq!(isize::atoi("-9223372036854775809"), Ok(9223372036854775807));
assert_eq!(isize::atoi("9223372036854775809"), Ok(-9223372036854775807));
}
}