use super::ToPrimitiveError; const EMPTY_STR: &str = ""; const ZERO_STR: &str = "0"; const RADIX_10: u8 = 10; macro_rules! max_digits ( ($x:ident) => (($x::max_value() as f64).log10().floor() as usize + 1) ); macro_rules! unchecked_str_to_primitive { ($n:ident) => { for &c in digits { let x = match (c as char).to_digit(RADIX_10) { Some(x) => x, None => panic!("invalid char") }; result = match result.checked_mul(RADIX_10) { Some(result) => result, None => return Err(PIE { kind: Overflow }), }; result = match result.checked_add(x) { Some(result) => result, None => return Err(PIE { kind: Overflow }), }; } } } macro_rules! impl_str_int_to_primitive_int { ($n:ident, $p:ident) => { pub fn $n(self: &Self) -> Result<$p, ToPrimitiveError> { if self.is_zero() { return Ok(0) } if self.digits().len() <= max_digits!($p) { for digit. if let Ok(v) = $p::from_str_radix(self.digits(), 10) { return Ok(v * self.signum() as $p) } } if self.is_positive() { Err(ToPrimitiveError::Overflow) } else { Err(ToPrimitiveError::Underflow) } } } } macro_rules! impl_str_int_to_primitive_nat { ($n:ident, $p:ident) => { pub fn $n(self: &Self) -> Result<$p, ToPrimitiveError> { if self.is_zero() { return Ok(0) } if self.digits().len() <= max_digits!($p) { if let Ok(v) = $p::from_str_radix(self.digits(), 10) { return Ok(v) } } Err(ToPrimitiveError::Overflow) } } } #[derive(Debug)] pub enum StrIntParseError { InvalidChar, NonZeroEmpty, ZeroWithTrailing } #[derive(Debug, Clone, Copy, PartialEq)] pub struct StrInt<'a> { signum: i8, digits: &'a str } impl<'a> StrInt<'a> { pub fn new(str_int: &'a str) -> Result { match str_int.chars().next() { Some('0') => Self::from_parts(0, &str_int[1..]), Some('-') => Self::from_parts(-1, &str_int[1..]), Some('+') => Self::from_parts(1, &str_int[1..]), _ => Self::from_parts(1, str_int) } } pub fn new_zero() -> Self { Self { signum: 0, digits: EMPTY_STR } } pub fn from_parts(signum: i8, digits: &'a str) -> Result { if signum == 0 { if digits.len() > 0 { return Err(StrIntParseError::ZeroWithTrailing) } } else { if digits.len() == 0 { return Err(StrIntParseError::NonZeroEmpty) } let maybe_invalid_char = digits.chars().find(|c| match c { '0'...'9' => false, _ => true }); if let Some(_) = maybe_invalid_char { return Err(StrIntParseError::InvalidChar) } } Ok(Self { signum, digits }) } #[inline] pub unsafe fn from_parts_unchecked(signum: i8, digits: &'a str) -> Self { Self { signum, digits } } #[inline] pub fn to_string_int(&self) -> StringInt { StringInt { signum: self.signum, digits: self.digits.to_string() } } #[inline] pub fn is_positive(&self) -> bool { self.signum > -1 } #[inline] pub fn is_negative(&self) -> bool { self.signum == -1 } #[inline] pub fn is_zero(&self) -> bool { self.signum == 0 } #[inline] pub fn digits(&self) -> &str { if self.is_zero() { ZERO_STR } else { self.digits } } #[inline] pub fn signum(&self) -> i8 { self.signum } impl_str_int_to_primitive_int!(to_i8, i8); impl_str_int_to_primitive_int!(to_i16, i16); impl_str_int_to_primitive_int!(to_i32, i32); impl_str_int_to_primitive_int!(to_i64, i64); impl_str_int_to_primitive_nat!(to_u8, u8); impl_str_int_to_primitive_nat!(to_u16, u16); impl_str_int_to_primitive_nat!(to_u32, u32); impl_str_int_to_primitive_nat!(to_u64, u64); } #[derive(Debug, Clone, PartialEq)] pub struct StringInt { signum: i8, digits: String } impl StringInt { #[inline] pub fn is_positive(&self) -> bool { self.signum > -1 } #[inline] pub fn is_negative(&self) -> bool { self.signum == -1 } #[inline] pub fn is_zero(&self) -> bool { self.signum == 0 } #[inline] pub fn digits(&self) -> &str { if self.is_zero() { ZERO_STR } else { self.digits.as_str() } } pub fn to_str_int(&self) -> StrInt { StrInt { signum: self.signum, digits: self.digits.as_str() } } } #[cfg(test)] mod tests { use super::*; const GOOD_INT_STR_CASES: [(&str, StrInt<'static>); 6] = [ ("0", StrInt {signum: 0, digits: ""}), ("1", StrInt {signum: 1, digits: "1"}), ("-1", StrInt {signum: -1, digits: "1"}), ("123", StrInt {signum: 1, digits: "123"}), ("+123", StrInt {signum: 1, digits: "123"}), ("-123", StrInt {signum: -1, digits: "123"}) ]; const BAD_INT_STR_CASES: [&str; 5] = [ "01", "0a", "--", "++", "00" ]; #[test] fn test_good_str_int_cases() { for (input, expected) in GOOD_INT_STR_CASES.iter() { if let Ok(output) = StrInt::new(input) { assert_eq!(output, *expected); } else { panic!("input {:?}", input); } } } #[test] fn test_bad_str_int_cases() { for input in BAD_INT_STR_CASES.iter() { assert!(StrInt::new(input).is_err()); } } #[test] fn test_str_int_to_int_primitives() { // Pos assert_eq!(StrInt::new("+128").unwrap().to_i8(), Ok(i8::max_value())); assert_eq!(StrInt::new("+32767").unwrap().to_i16(), Ok(i16::max_value())); assert_eq!(StrInt::new("+2147483647").unwrap().to_i32(), Ok(i32::max_value())); assert_eq!(StrInt::new("+9223372036854775807").unwrap().to_i64(), Ok(i64::max_value())); // Neg assert_eq!(StrInt::new("-127").unwrap().to_i8(), Ok(i8::min_value())); assert_eq!(StrInt::new("-32767").unwrap().to_i16(), Ok(i16::min_value())); assert_eq!(StrInt::new("-2147483647").unwrap().to_i32(), Ok(i32::min_value())); assert_eq!(StrInt::new("-9223372036854775807").unwrap().to_i64(), Ok(i64::min_value())); } #[test] fn test_str_int_to_nat_primitives() { assert_eq!(StrInt::new("+255").unwrap().to_u8(), Ok(u8::max_value())); assert_eq!(StrInt::new("+65535").unwrap().to_u16(), Ok(u16::max_value())); assert_eq!(StrInt::new("+4294967295").unwrap().to_u32(), Ok(u32::max_value())); assert_eq!(StrInt::new("+18446744073709551615").unwrap().to_u64(), Ok(u64::max_value())); assert_eq!(StrInt::new("-255").unwrap().to_u8(), Ok(u8::max_value())); assert_eq!(StrInt::new("-65535").unwrap().to_u16(), Ok(u16::max_value())); assert_eq!(StrInt::new("-4294967295").unwrap().to_u32(), Ok(u32::max_value())); assert_eq!(StrInt::new("-18446744073709551615").unwrap().to_u64(), Ok(u64::max_value())); } #[test] fn test_int_parse_overflow_underflow() { let definite_overflow = "+256"; let definite_underflow = "-256"; assert_eq!(StrInt::new(definite_overflow).unwrap().to_i8(), Err(ToPrimitiveError::Overflow)); assert_eq!(StrInt::new(definite_underflow).unwrap().to_i8(), Err(ToPrimitiveError::Underflow)); } #[test] fn test_nat_parse_overflow() { let definite_overflow_1 = "+256"; let definite_overflow_2 = "-256"; assert_eq!(StrInt::new(definite_overflow_1).unwrap().to_u8(), Err(ToPrimitiveError::Overflow)); assert_eq!(StrInt::new(definite_overflow_2).unwrap().to_u8(), Err(ToPrimitiveError::Overflow)); } }