arbi/
from_string.rs

1/*
2Copyright 2024-2025 Owain Davies
3SPDX-License-Identifier: Apache-2.0 OR MIT
4*/
5
6use crate::Base;
7use crate::{Arbi, Digit};
8use core::fmt;
9use core::str::FromStr;
10
11/// Errors that occur when parsing a string into an [`Arbi`].
12///
13/// Valid strings may optionally consist of a single leading + or - sign (no
14/// sign suggests nonnegative), followed by a mandatory nonempty string of
15/// base-`base` digits, where `base` must represent an integer in
16/// \\( [2, 36] \\).
17///
18/// The requirements are designed to be consistent with the behavior of
19/// `from_str_radix()` for primitive integer types. For example, see
20/// [`i32::from_str_radix()`].
21///
22/// # Examples
23/// ```
24/// use arbi::{Arbi, ParseError};
25/// let a = Arbi::from_str_radix("-123456789", 10).unwrap();
26/// assert_eq!(a, -123456789);
27/// let b = Arbi::from_str_radix("-12345a6789", 10);
28/// assert!(matches!(b, Err(ParseError::InvalidDigit)));
29/// let c = Arbi::from_str_radix("  -   ", 10);
30/// assert!(matches!(c, Err(ParseError::InvalidDigit)));
31/// let d = Arbi::from_str_radix("", 10);
32/// assert!(matches!(d, Err(ParseError::Empty)));
33/// ```
34#[derive(Debug, PartialEq, Eq)]
35pub enum ParseError {
36    /// Invalid digit found for the provided base.
37    InvalidDigit,
38    /// The provided string is empty.
39    Empty,
40}
41
42impl fmt::Display for ParseError {
43    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44        match self {
45            Self::InvalidDigit => {
46                write!(f, "Invalid digit found for the provided base")
47            }
48            Self::Empty => {
49                write!(f, "The provided string is empty")
50            }
51        }
52    }
53}
54
55#[cfg(feature = "std")]
56impl std::error::Error for ParseError {}
57
58#[cfg(feature = "std")]
59#[cfg(test)]
60mod std_tests {
61    use super::ParseError;
62    use std::error::Error;
63
64    #[test]
65    fn test_error_trait() {
66        let err: ParseError = ParseError::InvalidDigit;
67        assert!(err.source().is_none());
68    }
69}
70
71/// Calculate base ** exp
72const fn pow(base: Digit, exp: usize) -> Digit {
73    // (0..exp).fold(1, |acc, _| acc * base)
74    let mut result = 1;
75    let mut i = 0;
76    while i < exp {
77        result *= base;
78        i += 1;
79    }
80    result
81}
82
83#[derive(Copy, Clone)]
84pub(crate) struct BaseMbs {
85    // max batch size
86    pub(crate) mbs: usize,
87    // base ** mbs
88    pub(crate) base_pow_mbs: Digit,
89}
90
91/// Configuration for mappings and batch sizes
92pub(crate) mod configs {
93    use super::{pow, BaseMbs, Digit};
94    use crate::DDigit;
95
96    pub(crate) const BASE_MBS: [BaseMbs; 37] = compute_base_mbs();
97
98    // For example, if digit <==> uint32_t (uint64_t), 10^{9} (10^{19}) is the
99    // highest power of 10 that fits in it.
100    const fn compute_base_mbs() -> [BaseMbs; 37] {
101        let mut base_mbs = [BaseMbs {
102            mbs: 0,
103            base_pow_mbs: 0,
104        }; 37];
105        let mut base = 2;
106        while base <= 36 {
107            // Calculate max batch size
108            let mut max_batch_size = 0;
109            let mut b = base as DDigit;
110            while b < Digit::MAX as DDigit {
111                b *= base as DDigit;
112                max_batch_size += 1;
113            }
114
115            base_mbs[base] = BaseMbs {
116                mbs: max_batch_size,
117                base_pow_mbs: pow(base as Digit, max_batch_size),
118            };
119            base += 1;
120        }
121        base_mbs
122    }
123}
124
125impl Arbi {
126    /// Equivalent to [`Arbi::from_str_base()`], but panics if the base is
127    /// invalid (i.e. not in \\( [2, 36] \\)).
128    ///
129    /// # Examples
130    /// ```
131    /// use arbi::{Arbi, Base};
132    /// let x = Arbi::from_str_radix("987654321", 10).unwrap();
133    /// assert_eq!(x, 987654321);
134    pub fn from_str_radix(src: &str, radix: u32) -> Result<Self, ParseError> {
135        let base: Base = match radix.try_into() {
136            Err(_) => panic!("`radix` is not an integer in [2, 36]"),
137            Ok(b) => b,
138        };
139
140        Arbi::from_str_base(src, base)
141    }
142
143    /// Construct an integer from a string representing a base-`base` integer.
144    ///
145    /// # Examples
146    /// ```
147    /// use arbi::{base::DEC, Arbi};
148    /// let x = Arbi::from_str_base("987654321", DEC).unwrap();
149    /// assert_eq!(x, 987654321);
150    /// ```
151    pub fn from_str_base(s: &str, base: Base) -> Result<Self, ParseError> {
152        let mut res = Self::new();
153        res.assign_str_base(s, base)?;
154        Ok(res)
155    }
156}
157
158/* TODO: clean up */
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163    use crate::BaseError;
164    use alloc::string::{String, ToString};
165
166    #[test]
167    fn test_from_str_base_invalid_base() {
168        for (_, base) in &[("0", 0), ("0", 1), ("0", 37)] {
169            assert!(matches!(
170                Base::try_from(*base),
171                Err(BaseError::InvalidBase)
172            ));
173        }
174    }
175
176    #[test]
177    fn test_parse_error() {
178        assert!(matches!(
179            Arbi::from_str_base("", 10.try_into().unwrap()),
180            Err(ParseError::Empty)
181        ));
182        assert!(matches!(
183            Arbi::from_str_base("        ", 10.try_into().unwrap()),
184            Err(ParseError::InvalidDigit)
185        ));
186        for s in &["  -", "      -"] {
187            assert!(matches!(
188                Arbi::from_str_base(s, 10.try_into().unwrap()),
189                Err(ParseError::InvalidDigit)
190            ));
191        }
192    }
193
194    #[test]
195    fn test_parse_error_invalid_digit() {
196        assert!(matches!(
197            Arbi::from_str_radix("-00A1", 2),
198            Err(ParseError::InvalidDigit)
199        ));
200    }
201
202    #[test]
203    fn test_from_str_base_small_strings() {
204        for (s, expected) in &[
205            ("0", 0),
206            ("-0", 0),
207            ("+0", 0),
208            ("987", 987),
209            ("-987", -987),
210            ("+987", 987),
211            ("+00100", 100),
212            ("+000000", 0),
213            ("18446744073709551615", 18446744073709551615_i128),
214        ] {
215            let arbi = Arbi::from_str_base(s, 10.try_into().unwrap()).unwrap();
216            assert_eq!(arbi, *expected);
217        }
218    }
219
220    fn test_construct_from_string(base: Base) {
221        use crate::to_string::tests::ToStringBase;
222        use crate::util::test::{
223            get_seedable_rng, get_uniform_die, Distribution,
224        };
225        use crate::{DDigit, QDigit, SDDigit, SDigit, SQDigit};
226
227        for x in [
228            0 as SQDigit,
229            Digit::MAX as SQDigit,
230            DDigit::MAX as SQDigit,
231            SDigit::MIN as SQDigit,
232            SDDigit::MIN as SQDigit,
233            SDigit::MAX as SQDigit,
234            SDDigit::MAX as SQDigit,
235            SQDigit::MIN,
236            SQDigit::MAX,
237        ] {
238            assert_eq!(
239                Arbi::from_str_base(&x.to_string_base(base), base).unwrap(),
240                x,
241            );
242        }
243
244        assert_eq!(
245            Arbi::from_str_base(&QDigit::MAX.to_string_base(base), base)
246                .unwrap(),
247            QDigit::MAX
248        );
249
250        let (mut rng, _) = get_seedable_rng();
251
252        let die = get_uniform_die(SQDigit::MIN, SQDigit::MAX);
253        let die_16 = get_uniform_die(i16::MIN, i16::MAX);
254        for _ in 0..i16::MAX {
255            let rv = die.sample(&mut rng);
256            let s = rv.to_string_base(base);
257            assert_eq!(Arbi::from_str_base(&s, base).unwrap(), rv);
258
259            let rv_16 = die_16.sample(&mut rng);
260            let s = rv_16.to_string_base(base);
261            assert_eq!(Arbi::from_str_base(&s, base).unwrap(), rv_16);
262        }
263    }
264
265    #[test]
266    fn test_construct_from_string_() {
267        for i in 2..=36 {
268            test_construct_from_string(i.try_into().unwrap());
269        }
270    }
271
272    #[test]
273    fn test_from_str_base_large_string() {
274        use crate::base::DEC;
275
276        let mut s: String = "\
277            9999090900932323293029323092309309232309920940294242043232099290329\
278            3029320312904092384092384903840928490320948239048903284920139402030\
279            2309402934092304904902394029340932049302942313094874981748920028034\
280            3298980495840298502480239483390099999029100293809809809809099923420\
281        "
282        .into();
283
284        assert_eq!(Arbi::from_str_base(&s, DEC).unwrap().to_string(), s);
285
286        s.insert(0, '-');
287        let a = Arbi::from_str_base(&s, DEC).unwrap();
288        assert!(a.is_negative());
289        assert_eq!(a.to_string(), s);
290    }
291
292    #[test]
293    fn test_from_str_radix() {
294        let a = Arbi::from_str_radix("-987654321", 10).unwrap();
295        assert_eq!(a, -987654321);
296
297        let b =
298            Arbi::from_str_radix("10001101001111000000111000010101011011", 2)
299                .unwrap();
300        assert_eq!(b, 151649486171_i64);
301    }
302
303    #[test]
304    fn test_from_str() {
305        let a = Arbi::from_str("-987654321").unwrap();
306        assert_eq!(a, -987654321);
307    }
308
309    #[test]
310    fn test_str_parse() {
311        let a: Arbi = "-987654321".parse().unwrap();
312        assert_eq!(a, -987654321);
313
314        let a = "123456789".parse::<Arbi>().unwrap();
315        assert_eq!(a, 123456789);
316
317        assert!(matches!(
318            "12A456789".parse::<Arbi>(),
319            Err(ParseError::InvalidDigit)
320        ));
321        assert!(matches!(
322            "   -".parse::<Arbi>(),
323            Err(ParseError::InvalidDigit)
324        ));
325
326        let a = "\
327            123456789012345678901234567890123456789043909809801329009092930\
328        ";
329        assert_eq!(a.parse::<Arbi>().unwrap().to_string(), a);
330
331        let a = "\
332            -12345678901234567890123456789012345678909098909809802340982349\
333        ";
334        assert_eq!(a.parse::<Arbi>().unwrap().to_string(), a);
335
336        assert_eq!("0".parse::<Arbi>().unwrap(), 0);
337    }
338}
339
340impl FromStr for Arbi {
341    type Err = ParseError;
342
343    /// Equivalent to [`Arbi::from_str_radix()`] with base 10.
344    ///
345    /// # Examples
346    /// ```
347    /// use arbi::Arbi;
348    /// use core::str::FromStr;
349    /// let a = Arbi::from_str("-987654321").unwrap();
350    /// assert_eq!(a, -987654321);
351    /// let b = "123456789".parse::<Arbi>().unwrap();
352    /// assert_eq!(b, 123456789);
353    /// ```
354    fn from_str(src: &str) -> Result<Self, Self::Err> {
355        Arbi::from_str_radix(src, 10)
356    }
357}