use std::iter::Peekable;
use std::str::Chars;
use traiter::numbers::{One, Zeroable};
use crate::traits::HasSignBit;
use super::constants::{MAX_REPRESENTABLE_BASE, MIN_REPRESENTABLE_BASE};
use super::contracts::is_valid_digit_bitness;
use super::digits::{
to_digits_sign, trim_leading_zeros, BinaryBaseFromDigits,
};
use super::types::{BigInt, Sign, TryFromStringError};
pub trait TryFromString: Sized {
fn try_from_string(
string: &str,
base: u8,
) -> Result<Self, TryFromStringError>;
}
const ASCII_CODES_DIGIT_VALUES: [u8; 256] = [
37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 0, 1, 2, 3, 4, 5, 6, 7, 8,
9, 37, 37, 37, 37, 37, 37, 37, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 37, 37, 37,
37, 37, 37, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37,
];
impl<
Digit: BinaryBaseFromDigits<u8> + HasSignBit,
const DIGIT_BITNESS: usize,
> TryFromString for BigInt<Digit, DIGIT_BITNESS>
where
for<'a> &'a Digit: Zeroable,
{
fn try_from_string(
string: &str,
mut base: u8,
) -> Result<Self, TryFromStringError> {
debug_assert!(is_valid_digit_bitness::<Digit, DIGIT_BITNESS>());
debug_assert!(
base == 0
|| (MIN_REPRESENTABLE_BASE..=MAX_REPRESENTABLE_BASE)
.contains(&base)
);
let mut characters = string.trim().chars().peekable();
let sign = parse_sign(&mut characters);
if base == 0 {
base = guess_base(&mut characters);
};
skip_prefix(&mut characters, base);
parse_digits(characters, base).map(|digits| {
let digits = Digit::binary_base_from_digits::<DIGIT_BITNESS>(
&digits,
base as usize,
);
Self {
sign: sign * to_digits_sign(&digits),
digits,
}
})
}
}
#[inline]
fn guess_base(characters: &mut Peekable<Chars<'_>>) -> u8 {
if characters.peek() != Some(&'0') {
10
} else {
match characters.clone().nth(1) {
Some('b' | 'B') => 2,
Some('o' | 'O') => 8,
Some('x' | 'X') => 16,
_ => 10,
}
}
}
#[inline]
fn parse_digits(
characters: Peekable<Chars<'_>>,
base: u8,
) -> Result<Vec<u8>, TryFromStringError> {
let mut result = {
let (_, maybe_characters_count) = characters.size_hint();
debug_assert!(maybe_characters_count.is_some());
let characters_count =
unsafe { maybe_characters_count.unwrap_unchecked() };
Vec::<u8>::with_capacity(characters_count)
};
for character in characters {
let digit = ASCII_CODES_DIGIT_VALUES[character as usize];
if digit >= base {
return Err(TryFromStringError::InvalidDigit(character, base));
}
result.push(digit);
}
if result.is_empty() {
Err(TryFromStringError::NoDigits)
} else {
result.reverse();
trim_leading_zeros(&mut result);
Ok(result)
}
}
#[inline]
fn parse_sign(characters: &mut Peekable<Chars<'_>>) -> i8 {
if characters.peek() == Some(&'-') {
characters.next();
-Sign::one()
} else if characters.peek() == Some(&'+') {
characters.next();
Sign::one()
} else {
Sign::one()
}
}
fn skip_prefix(characters: &mut Peekable<Chars<'_>>, base: u8) {
if characters.peek() == Some(&'0') {
match characters.clone().nth(1) {
Some('b' | 'B') => {
if base == 2 {
characters.nth(1);
}
}
Some('o' | 'O') => {
if base == 8 {
characters.nth(1);
}
}
Some('x' | 'X') => {
if base == 16 {
characters.nth(1);
}
}
_ => {}
};
};
}