295 lines
6.9 KiB
Rust
295 lines
6.9 KiB
Rust
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<Self, StrIntParseError> {
|
|
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<Self, StrIntParseError> {
|
|
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));
|
|
}
|
|
} |