num_format/
parsing.rs

1//! Module with traits for parsing a formatted string into a number.
2//!
3//! # Examples
4//! ```
5//! use num_format::Locale;
6//! use num_format::parsing::ParseFormatted;
7//!
8//! fn main() {
9//!     let s = "1,000,000";
10//!     let n = s.parse_formatted::<_, u32>(&Locale::en).unwrap();
11//!     assert_eq!(n, 1_000_000);
12//! }
13//! ```
14
15use 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
23/// Trait that provides string-like types with a [`parse_formatted`]
24/// method, allowing conversion from a formatted string into a number.
25///
26/// # Examples
27/// ```
28/// use num_format::Locale;
29/// use num_format::parsing::ParseFormatted;
30///
31/// fn main() {
32///     let s = "1,000,000";
33///     let n = s.parse_formatted::<_, u32>(&Locale::en).unwrap();
34///     assert_eq!(n, 1_000_000);
35/// }
36/// ```
37///
38/// [`parse_formatted`]: trait.ParseFormatted.html#method.parse_formatted
39pub trait ParseFormatted {
40    /// Converts `self` (typically a formatted string) into a number (see [Examples] above).
41    ///
42    /// [Examples]: trait.ParseFormatted.html#examples
43    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
62/// Marker trait for number types (e.g. `u32`) that string-like types can be parsed
63/// into via the [`ParseFormatted`] trait.
64///
65/// This trait is sealed; so you may not implement it on your own types.
66///
67/// [`ParseFormatted`]: trait.ParseFormatted.html
68pub 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}