1#![allow(clippy::missing_inline_in_public_items)] use crate::{base_convert::BaseConvertError, Uint};
4use core::{fmt, str::FromStr};
5
6#[derive(Debug, Copy, Clone, PartialEq, Eq)]
8pub enum ParseError {
9 InvalidDigit(char),
11
12 InvalidRadix(u64),
14
15 BaseConvertError(BaseConvertError),
17}
18
19#[cfg(feature = "std")]
20impl std::error::Error for ParseError {
21 #[inline]
22 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
23 match self {
24 Self::BaseConvertError(e) => Some(e),
25 _ => None,
26 }
27 }
28}
29
30impl From<BaseConvertError> for ParseError {
31 #[inline]
32 fn from(value: BaseConvertError) -> Self {
33 Self::BaseConvertError(value)
34 }
35}
36
37impl fmt::Display for ParseError {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 match self {
40 Self::BaseConvertError(e) => e.fmt(f),
41 Self::InvalidDigit(c) => write!(f, "invalid digit: {c}"),
42 Self::InvalidRadix(r) => write!(f, "invalid radix {r}, up to 64 is supported"),
43 }
44 }
45}
46
47impl<const BITS: usize, const LIMBS: usize> Uint<BITS, LIMBS> {
48 pub fn from_str_radix(src: &str, radix: u64) -> Result<Self, ParseError> {
63 if radix > 64 {
64 return Err(ParseError::InvalidRadix(radix));
65 }
66 let mut err = None;
67 let digits = src.chars().filter_map(|c| {
68 if err.is_some() {
69 return None;
70 }
71 let digit = if radix <= 36 {
72 match c {
74 '0'..='9' => u64::from(c) - u64::from('0'),
75 'a'..='z' => u64::from(c) - u64::from('a') + 10,
76 'A'..='Z' => u64::from(c) - u64::from('A') + 10,
77 '_' => return None, _ => {
79 err = Some(ParseError::InvalidDigit(c));
80 return None;
81 }
82 }
83 } else {
84 match c {
86 'A'..='Z' => u64::from(c) - u64::from('A'),
87 'a'..='f' => u64::from(c) - u64::from('a') + 26,
88 '0'..='9' => u64::from(c) - u64::from('0') + 52,
89 '+' | '-' => 62,
90 '/' | ',' | '_' => 63,
91 '=' | '\r' | '\n' => return None, _ => {
93 err = Some(ParseError::InvalidDigit(c));
94 return None;
95 }
96 }
97 };
98 Some(digit)
99 });
100 let value = Self::from_base_be(radix, digits)?;
101 err.map_or(Ok(value), Err)
102 }
103}
104
105impl<const BITS: usize, const LIMBS: usize> FromStr for Uint<BITS, LIMBS> {
106 type Err = ParseError;
107
108 fn from_str(src: &str) -> Result<Self, Self::Err> {
109 let (src, radix) = if src.is_char_boundary(2) {
110 let (prefix, rest) = src.split_at(2);
111 match prefix {
112 "0x" | "0X" => (rest, 16),
113 "0o" | "0O" => (rest, 8),
114 "0b" | "0B" => (rest, 2),
115 _ => (src, 10),
116 }
117 } else {
118 (src, 10)
119 };
120 Self::from_str_radix(src, radix)
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use proptest::{prop_assert_eq, proptest};
128
129 #[test]
130 fn test_parse() {
131 proptest!(|(value: u128)| {
132 type U = Uint<128, 2>;
133 prop_assert_eq!(U::from_str(&format!("{value:#b}")), Ok(U::from(value)));
134 prop_assert_eq!(U::from_str(&format!("{value:#o}")), Ok(U::from(value)));
135 prop_assert_eq!(U::from_str(&format!("{value:}")), Ok(U::from(value)));
136 prop_assert_eq!(U::from_str(&format!("{value:#x}")), Ok(U::from(value)));
137 prop_assert_eq!(U::from_str(&format!("{value:#X}")), Ok(U::from(value)));
138 });
139 }
140}