num_format/
parsing.rs
1use core::num::{NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize};
16use core::str;
17
18use crate::constants::*;
19use crate::error::Error;
20use crate::format::Format;
21use crate::sealed::Sealed;
22
23pub trait ParseFormatted {
40 fn parse_formatted<F, N>(&self, format: &F) -> Result<N, Error>
44 where
45 F: Format,
46 N: FromFormattedStr;
47}
48
49impl<S> ParseFormatted for S
50where
51 S: AsRef<str>,
52{
53 fn parse_formatted<F, N>(&self, format: &F) -> Result<N, Error>
54 where
55 F: Format,
56 N: FromFormattedStr,
57 {
58 FromFormattedStr::from_formatted_str(self.as_ref(), format)
59 }
60}
61
62pub trait FromFormattedStr: Sealed + Sized {
69 #[allow(missing_docs)]
70 fn from_formatted_str<F>(s: &str, format: &F) -> Result<Self, Error>
71 where
72 F: Format;
73}
74
75macro_rules! impl_from_formatted_str {
76 ($type:ty, $max_len:expr) => {
77 impl FromFormattedStr for $type {
78 fn from_formatted_str<F>(s: &str, format: &F) -> Result<Self, Error>
79 where
80 F: Format,
81 {
82 const BUF_LEN: usize = $max_len;
83 let mut buf: [u8; BUF_LEN] = [0; BUF_LEN];
84
85 let minus_sign = format.minus_sign().into_str();
86 let is_negative = s.starts_with(minus_sign);
87
88 let mut index = 0;
89 if is_negative {
90 buf[index] = '-' as u8;
91 index += 1;
92 }
93 for c in s.chars() {
94 if c.is_numeric() {
95 if index > BUF_LEN {
96 return Err(Error::parse_number(&s));
97 }
98 buf[index] = c as u8;
99 index += 1;
100 }
101 }
102
103 if index == 0 {
104 return Err(Error::parse_number(&s));
105 }
106
107 let s2 = unsafe { str::from_utf8_unchecked(&buf[..index]) };
108 let n = s2.parse::<$type>().map_err(|_| Error::parse_locale(&s))?;
109
110 Ok(n)
111 }
112 }
113 };
114}
115
116impl_from_formatted_str!(u8, U8_MAX_LEN);
117impl_from_formatted_str!(u16, U16_MAX_LEN);
118impl_from_formatted_str!(u32, U32_MAX_LEN);
119impl_from_formatted_str!(usize, USIZE_MAX_LEN);
120impl_from_formatted_str!(u64, U64_MAX_LEN);
121impl_from_formatted_str!(u128, U128_MAX_LEN);
122
123impl_from_formatted_str!(i8, I8_MAX_LEN);
124impl_from_formatted_str!(i16, I16_MAX_LEN);
125impl_from_formatted_str!(i32, I32_MAX_LEN);
126impl_from_formatted_str!(isize, ISIZE_MAX_LEN);
127impl_from_formatted_str!(i64, I64_MAX_LEN);
128impl_from_formatted_str!(i128, I128_MAX_LEN);
129
130macro_rules! impl_from_formatted_str_non_zero {
131 ($type:ty, $related_type:ty, $max_len:expr) => {
132 impl FromFormattedStr for $type {
133 fn from_formatted_str<F>(s: &str, format: &F) -> Result<Self, Error>
134 where
135 F: Format,
136 {
137 let n = s.parse_formatted::<_, $related_type>(format)?;
138 let n = Self::new(n).ok_or_else(|| Error::parse_number(s))?;
139 Ok(n)
140 }
141 }
142 };
143}
144
145impl_from_formatted_str_non_zero!(NonZeroU8, u8, U8_MAX_LEN);
146impl_from_formatted_str_non_zero!(NonZeroU16, u16, U16_MAX_LEN);
147impl_from_formatted_str_non_zero!(NonZeroU32, u32, U32_MAX_LEN);
148impl_from_formatted_str_non_zero!(NonZeroUsize, usize, USIZE_MAX_LEN);
149impl_from_formatted_str_non_zero!(NonZeroU64, u64, U64_MAX_LEN);
150impl_from_formatted_str_non_zero!(NonZeroU128, u128, U128_MAX_LEN);
151
152#[cfg(feature = "with-num-bigint")]
153mod num {
154 use num_bigint::{BigInt, BigUint};
155
156 use super::*;
157
158 macro_rules! impl_from_formatted_str_num_bigint {
159 ($type:ty) => {
160 impl FromFormattedStr for $type {
161 fn from_formatted_str<F>(s: &str, format: &F) -> Result<Self, Error>
162 where
163 F: Format,
164 {
165 let mut buf = Vec::new();
166
167 let minus_sign = format.minus_sign().into_str();
168 let is_negative = s.starts_with(minus_sign);
169
170 if is_negative {
171 buf.push('-' as u8);
172 }
173 for c in s.chars() {
174 if c.is_numeric() {
175 buf.push(c as u8);
176 }
177 }
178
179 if buf.is_empty() {
180 return Err(Error::parse_number(&s));
181 }
182
183 let s2 = unsafe { str::from_utf8_unchecked(&buf[..]) };
184 let n = s2.parse::<$type>().map_err(|_| Error::parse_locale(&s))?;
185
186 Ok(n)
187 }
188 }
189 };
190 }
191
192 impl_from_formatted_str_num_bigint!(BigInt);
193 impl_from_formatted_str_num_bigint!(BigUint);
194
195 #[cfg(test)]
196 mod tests {
197 use num_bigint::{ToBigInt, ToBigUint};
198
199 use super::*;
200 use crate::locale::Locale;
201
202 #[test]
203 fn test_parsing_num_bigint() {
204 assert_eq!(
205 "1,000,000"
206 .parse_formatted::<_, BigUint>(&Locale::en)
207 .unwrap(),
208 1_000_000.to_biguint().unwrap()
209 );
210 assert_eq!(
211 "-1,000,000"
212 .parse_formatted::<_, BigInt>(&Locale::en)
213 .unwrap(),
214 (-1_000_000).to_bigint().unwrap()
215 );
216 }
217 }
218}