#![allow(clippy::missing_inline_in_public_items)] use crate::{base_convert::BaseConvertError, Uint};
use core::{fmt, str::FromStr};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ParseError {
InvalidDigit(char),
InvalidRadix(u64),
BaseConvertError(BaseConvertError),
}
#[cfg(feature = "std")]
impl std::error::Error for ParseError {
#[inline]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::BaseConvertError(e) => Some(e),
_ => None,
}
}
}
impl From<BaseConvertError> for ParseError {
#[inline]
fn from(value: BaseConvertError) -> Self {
Self::BaseConvertError(value)
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BaseConvertError(e) => e.fmt(f),
Self::InvalidDigit(c) => write!(f, "invalid digit: {c}"),
Self::InvalidRadix(r) => write!(f, "invalid radix {r}, up to 64 is supported"),
}
}
}
impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
pub fn from_str_radix(src: &str, radix: u64) -> Result<Self, ParseError> {
if radix > 64 {
return Err(ParseError::InvalidRadix(radix));
}
let mut err = None;
let digits = src.chars().filter_map(|c| {
if err.is_some() {
return None;
}
let digit = if radix <= 36 {
match c {
'0'..='9' => u64::from(c) - u64::from('0'),
'a'..='z' => u64::from(c) - u64::from('a') + 10,
'A'..='Z' => u64::from(c) - u64::from('A') + 10,
'_' => return None, _ => {
err = Some(ParseError::InvalidDigit(c));
return None;
}
}
} else {
match c {
'A'..='Z' => u64::from(c) - u64::from('A'),
'a'..='f' => u64::from(c) - u64::from('a') + 26,
'0'..='9' => u64::from(c) - u64::from('0') + 52,
'+' | '-' => 62,
'/' | ',' | '_' => 63,
'=' | '\r' | '\n' => return None, _ => {
err = Some(ParseError::InvalidDigit(c));
return None;
}
}
};
Some(digit)
});
let value = Self::from_base_be(radix, digits)?;
err.map_or(Ok(value), Err)
}
}
impl<const BITS: usize, const LIMBS: usize> FromStr for Uint<BITS, LIMBS> {
type Err = ParseError;
fn from_str(src: &str) -> Result<Self, Self::Err> {
let (src, radix) = if src.is_char_boundary(2) {
let (prefix, rest) = src.split_at(2);
match prefix {
"0x" | "0X" => (rest, 16),
"0o" | "0O" => (rest, 8),
"0b" | "0B" => (rest, 2),
_ => (src, 10),
}
} else {
(src, 10)
};
Self::from_str_radix(src, radix)
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::{prop_assert_eq, proptest};
#[test]
fn test_parse() {
proptest!(|(value: u128)| {
type U = Uint<128, 2>;
prop_assert_eq!(U::from_str(&format!("{value:#b}")), Ok(U::from(value)));
prop_assert_eq!(U::from_str(&format!("{value:#o}")), Ok(U::from(value)));
prop_assert_eq!(U::from_str(&format!("{value:}")), Ok(U::from(value)));
prop_assert_eq!(U::from_str(&format!("{value:#x}")), Ok(U::from(value)));
prop_assert_eq!(U::from_str(&format!("{value:#X}")), Ok(U::from(value)));
});
}
}