parse_int/
lib.rs

1#![deny(warnings)]
2#![doc = include_str!("../README.md")]
3
4#[cfg(feature = "ranges")]
5pub mod range;
6mod utils;
7
8use std::fmt::{Binary, Display, LowerHex, Octal};
9
10use num_traits::Num;
11use utils::{is_floaty, process_fractional, process_integer};
12
13/// True if the crate had the feature "implicit-octal" enabled at build time
14pub const IMPLICIT_OCTAL_ENABLED: bool = {
15    #[cfg(feature = "implicit-octal")]
16    {
17        true
18    }
19    #[cfg(not(feature = "implicit-octal"))]
20    {
21        false
22    }
23};
24
25/// Parse [&str] with common prefixes to integer values
26///
27/// ```rust
28/// # use std::error::Error;
29/// # fn main() -> Result<(), Box<dyn Error>> {
30/// use parse_int::parse;
31///
32/// // decimal
33/// let d = parse::<usize>("42")?;
34/// assert_eq!(42, d);
35///
36/// // hex
37/// let d = parse::<isize>("0x42")?;
38/// assert_eq!(66, d);
39///
40/// // you can use underscores for more readable inputs
41/// let d = parse::<isize>("0x42_424_242")?;
42/// assert_eq!(1_111_638_594, d);
43///
44/// // octal explicit
45/// let d = parse::<u8>("0o42")?;
46/// assert_eq!(34, d);
47///
48/// ##[cfg(feature = "implicit-octal")]
49/// {
50///     let d = parse::<i8>("042")?;
51///     assert_eq!(34, d);
52/// }
53///
54/// // binary
55/// let d = parse::<u16>("0b0110")?;
56/// assert_eq!(6, d);
57/// #
58/// #     Ok(())
59/// # }
60/// ```
61#[inline]
62pub fn parse<T: Num>(input: &str) -> Result<T, T::FromStrRadixErr> {
63    let input = input.trim();
64
65    let (is_negative, input) = if let Some(input) = input.strip_prefix('-') {
66        (true, input)
67    } else {
68        (false, input)
69    };
70
71    // invalid start
72    if input.starts_with("_") {
73        /* With rust 1.55 the return type is stable but we can not construct it yet
74
75        let kind = ::core::num::IntErrorKind::InvalidDigit;
76        //let pie = ::core::num::ParseIntError {
77        let pie = <<T as num_traits::Num>::FromStrRadixErr as Trait>::A {
78            kind
79        };
80        return Err(pie);
81        */
82        return T::from_str_radix("_", 2);
83    }
84
85    let num: T =
86    // hex
87    if input.starts_with("0x") || input.starts_with("0X") {
88        parse_with_base(&input[2..], 16)?
89    } else
90
91    // binary
92    if input.starts_with("0b") || input.starts_with("0B") {
93        parse_with_base(&input[2..], 2)?
94    } else
95
96    // octal
97    if input.starts_with("0o") || input.starts_with("0O") {
98        parse_with_base(&input[2..], 8)?
99    } else if IMPLICIT_OCTAL_ENABLED &&  input.starts_with("0") {
100        if input == "0" {
101            T::zero()
102        } else {
103            parse_with_base(&input[1..], 8)?
104        }
105    } else {
106        // decimal
107        parse_with_base(input, 10)?
108    };
109
110    Ok(if is_negative {
111        num * (T::zero() - T::one())
112    } else {
113        num
114    })
115}
116
117#[inline]
118fn parse_with_base<T: Num>(input: &str, base: u32) -> Result<T, T::FromStrRadixErr> {
119    let input = input.chars().filter(|&c| c != '_').collect::<String>();
120    T::from_str_radix(&input, base)
121}
122
123/// Pretty print integer and float numbers with base 10, separated by underscores grouping the digits in threes
124///
125/// ```rust
126/// use parse_int::format_pretty_dec;
127/// assert_eq!("12_345.678_9", format_pretty_dec(12345.6789));
128/// assert_eq!("12_345", format_pretty_dec(12345));
129/// ```
130pub fn format_pretty_dec<N: Num + Display + 'static>(num: N) -> String {
131    let is_floaty = is_floaty(&num);
132    let short = format!("{num}");
133
134    let (integer, fraction) = if is_floaty {
135        match short.split_once('.') {
136            Some((i, f)) => (i, Some(f)),
137            None => (short.as_ref(), Some("0")),
138        }
139    } else {
140        (short.as_ref(), None)
141    };
142
143    let processed_integer = process_integer(integer, 3);
144
145    if let Some(fraction) = fraction {
146        let processed_fractional = process_fractional(fraction);
147        format!("{processed_integer}.{processed_fractional}")
148    } else {
149        processed_integer
150    }
151}
152
153/// Outputs a human readable string like:
154///
155/// ```
156/// let pretty = parse_int::format_pretty_octal(1024);
157/// assert_eq!("0o2_000", pretty);
158/// ```
159///
160/// Floats do not implement Octal
161pub fn format_pretty_octal<N: Num + Display + Octal + PartialOrd + 'static>(num: N) -> String {
162    let is_negative = num < N::zero();
163
164    let num = if is_negative {
165        // TODO optimise in the future
166        num * (N::zero() - N::one())
167    } else {
168        num
169    };
170
171    let short = format!("{num:o}");
172
173    let sign = if is_negative { "-" } else { "" };
174
175    let processed = process_integer(&short, 3);
176
177    format!("{sign}0o{processed}")
178}
179
180/// Outputs a human readable string like:
181///
182/// ```
183/// let pretty = parse_int::format_pretty_hex(1024);
184/// assert_eq!("0x4_00", pretty);
185/// ```
186///
187/// Floats do not implement LowerHex
188pub fn format_pretty_hex<N: Num + Display + LowerHex + PartialOrd + 'static>(num: N) -> String {
189    let is_negative = num < N::zero();
190
191    let num = if is_negative {
192        // TODO optimise in the future
193        num * (N::zero() - N::one())
194    } else {
195        num
196    };
197
198    let short = format!("{num:x}");
199
200    let sign = if is_negative { "-" } else { "" };
201
202    let processed = process_integer(&short, 2);
203
204    format!("{sign}0x{processed}")
205}
206
207/// Outputs a human readable string like:
208///
209/// ```
210/// let pretty = parse_int::format_pretty_bin(1024);
211/// assert_eq!("0b100_0000_0000", pretty);
212/// ```
213///
214/// Floats do not implement LowerHex
215pub fn format_pretty_bin<N: Num + Display + Binary + PartialOrd + 'static>(num: N) -> String {
216    let is_negative = num < N::zero();
217
218    let num = if is_negative {
219        // TODO optimise in the future
220        num * (N::zero() - N::one())
221    } else {
222        num
223    };
224
225    let short = format!("{num:b}");
226
227    let sign = if is_negative { "-" } else { "" };
228
229    let processed = process_integer(&short, 4);
230
231    format!("{sign}0b{processed}")
232}
233
234/*
235/// The available bases for pretty printing
236#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
237pub enum Base {
238    #[default]
239    Decimal = 10,
240    Hex = 16,
241    Octal = 8,
242}
243
244impl Base {
245    pub fn format<N: Num + PartialOrd + Display + LowerHex + Octal + 'static>(
246        &self,
247        num: N,
248    ) -> String {
249        format_pretty_with(*self, num)
250    }
251}
252*/
253
254#[cfg(test)]
255mod test_parsing {
256    use super::*;
257
258    #[test]
259    fn turbofish_usize_dec() {
260        let s = "42";
261
262        let u = parse::<usize>(s).unwrap();
263
264        assert_eq!(42, u);
265    }
266
267    #[test]
268    fn deduct_usize_dec() {
269        let s = "42";
270
271        let u = parse(s).unwrap();
272
273        assert_eq!(42usize, u);
274    }
275
276    #[test]
277    fn deduct_isize_dec_neg() {
278        let s = "-42";
279
280        let u = parse(s).unwrap();
281
282        assert_eq!(-42_isize, u);
283    }
284
285    macro_rules! int_parse {
286        ($type:ident, $s:literal, $e:literal) => {
287            #[test]
288            fn $type() {
289                let l: Result<$type, _> = crate::parse($s);
290                assert_eq!(Ok($e), l, "lower case");
291
292                let u: Result<$type, _> = crate::parse(&$s.to_uppercase());
293                assert_eq!(Ok($e), u, "upper case");
294            }
295        };
296    }
297
298    macro_rules! int_parse_err {
299        ($type:ident, $s:literal) => {
300            int_parse_err!($type, $s, err);
301        };
302        ($type:ident, $s:literal, $opt:ident) => {
303            mod $opt {
304                #[test]
305                fn $type() {
306                    let u: Result<$type, _> = crate::parse($s);
307                    assert!(u.is_err(), "expected Err(_), got = {:?}", u);
308                }
309            }
310        };
311    }
312
313    mod decimal {
314        int_parse!(usize, "42", 42);
315        int_parse!(isize, "42", 42);
316        int_parse!(u8, "42", 42);
317        int_parse!(i8, "42", 42);
318        int_parse!(u16, "42", 42);
319        int_parse!(i16, "42", 42);
320        int_parse!(u32, "42", 42);
321        int_parse!(i32, "42", 42);
322        int_parse!(u64, "42", 42);
323        int_parse!(i64, "42", 42);
324        int_parse!(u128, "42", 42);
325        int_parse!(i128, "42", 42);
326    }
327
328    mod decimal_negative {
329        int_parse!(isize, "-42", -42);
330        int_parse!(i8, "-42", -42);
331        int_parse!(i128, "-42_000", -42_000);
332    }
333
334    mod hexadecimal {
335        int_parse!(usize, "0x42", 66);
336        int_parse!(isize, "0x42", 66);
337        int_parse!(u8, "0x42", 66);
338        int_parse!(i8, "0x42", 66);
339        int_parse!(u16, "0x42", 66);
340        int_parse!(i16, "0x42", 66);
341        int_parse!(u32, "0x42", 66);
342        int_parse!(i32, "0x42", 66);
343        int_parse!(u64, "0x42", 66);
344        int_parse!(i64, "0x42", 66);
345        int_parse!(u128, "0x42", 66);
346        int_parse!(i128, "0x42", 66);
347    }
348
349    mod hex_negative {
350        int_parse!(isize, "-0x42", -66);
351        int_parse!(i8, "-0x42", -66);
352        int_parse!(i16, "-0x42", -66);
353        int_parse!(i32, "-0x42", -66);
354        int_parse!(i64, "-0x42", -66);
355        int_parse!(i128, "-0x42", -66);
356    }
357
358    mod octal_explicit {
359        int_parse!(usize, "0o42", 34);
360        int_parse!(isize, "0o42", 34);
361        int_parse!(u8, "0o42", 34);
362        int_parse!(i8, "0o42", 34);
363        int_parse!(u16, "0o42", 34);
364        int_parse!(i16, "0o42", 34);
365        int_parse!(u32, "0o42", 34);
366        int_parse!(i32, "0o42", 34);
367        int_parse!(u64, "0o42", 34);
368        int_parse!(i64, "0o42", 34);
369        int_parse!(u128, "0o42", 34);
370        int_parse!(i128, "0o42", 34);
371    }
372
373    mod octal_explicit_negative {
374        int_parse!(isize, "-0o42", -34);
375        int_parse!(i8, "-0o42", -34);
376        int_parse!(i16, "-0o42", -34);
377        int_parse!(i32, "-0o42", -34);
378        int_parse!(i64, "-0o42", -34);
379        int_parse!(i128, "-0o42", -34);
380    }
381
382    #[cfg(feature = "implicit-octal")]
383    mod octal_implicit {
384        use super::*;
385        int_parse!(usize, "042", 34);
386        int_parse!(isize, "042", 34);
387        int_parse!(u8, "042", 34);
388        int_parse!(i8, "042", 34);
389        int_parse!(u16, "042", 34);
390        int_parse!(i16, "042", 34);
391        int_parse!(u32, "042", 34);
392        int_parse!(i32, "042", 34);
393        int_parse!(u64, "042", 34);
394        int_parse!(i64, "042", 34);
395        int_parse!(u128, "042", 34);
396        int_parse!(i128, "042", 34);
397
398        #[test]
399        fn issue_nr_0() {
400            let s = "0";
401
402            assert_eq!(0, parse::<usize>(s).unwrap());
403            assert_eq!(0, parse::<isize>(s).unwrap());
404            assert_eq!(0, parse::<i8>(s).unwrap());
405            assert_eq!(0, parse::<u8>(s).unwrap());
406            assert_eq!(0, parse::<i16>(s).unwrap());
407            assert_eq!(0, parse::<u16>(s).unwrap());
408            assert_eq!(0, parse::<i32>(s).unwrap());
409            assert_eq!(0, parse::<u32>(s).unwrap());
410            assert_eq!(0, parse::<i64>(s).unwrap());
411            assert_eq!(0, parse::<u64>(s).unwrap());
412            assert_eq!(0, parse::<i128>(s).unwrap());
413            assert_eq!(0, parse::<u128>(s).unwrap());
414        }
415    }
416    #[cfg(feature = "implicit-octal")]
417    mod octal_implicit_negative {
418        int_parse!(isize, "-042", -34);
419        int_parse!(i8, "-042", -34);
420        int_parse!(i16, "-042", -34);
421        int_parse!(i32, "-042", -34);
422        int_parse!(i64, "-042", -34);
423        int_parse!(i128, "-042", -34);
424    }
425    #[cfg(not(feature = "implicit-octal"))]
426    mod octal_implicit_disabled {
427        use super::*;
428        #[test]
429        /// maybe this will change in the future
430        fn no_implicit_is_int() {
431            let s = "042";
432
433            let u = parse::<usize>(s);
434            assert_eq!(Ok(42), u, "{:?}", u);
435        }
436        #[test]
437        fn no_implicit_is_int_neg() {
438            let s = "-042";
439
440            let u = parse::<isize>(s);
441            assert_eq!(Ok(-42), u, "{:?}", u);
442        }
443    }
444
445    mod binary {
446        int_parse!(usize, "0b0110", 6);
447        int_parse!(isize, "0b0110", 6);
448        int_parse!(u8, "0b0110", 6);
449        int_parse!(i8, "0b0110", 6);
450        int_parse!(u16, "0b0110", 6);
451        int_parse!(i16, "0b0110", 6);
452        int_parse!(u32, "0b0110", 6);
453        int_parse!(i32, "0b0110", 6);
454        int_parse!(u64, "0b0110", 6);
455        int_parse!(i64, "0b0110", 6);
456        int_parse!(u128, "0b0110", 6);
457        int_parse!(i128, "0b0110", 6);
458    }
459
460    mod binary_negative {
461        int_parse_err!(i8, "0b1000_0000");
462        int_parse!(i8, "0b-0111_1111", -127);
463    }
464
465    mod underscore {
466        int_parse!(usize, "0b0110_0110", 102);
467        int_parse!(isize, "0x0110_0110", 17_826_064);
468        int_parse!(u64, "0o0110_0110", 294_984);
469        int_parse!(u128, "1_100_110", 1_100_110);
470
471        #[cfg(feature = "implicit-octal")]
472        mod implicit_octal {
473            int_parse!(i128, "0110_0110", 294_984);
474        }
475        #[cfg(not(feature = "implicit-octal"))]
476        mod implicit_octal {
477            int_parse!(i128, "0110_0110", 1_100_110);
478        }
479    }
480
481    mod underscore_in_prefix {
482        #[test]
483        fn invalid_underscore_in_prefix() {
484            let r = crate::parse::<isize>("_4");
485            println!("{:?}", r);
486            assert!(r.is_err());
487        }
488        int_parse_err!(isize, "0_x_4", hex);
489        int_parse_err!(isize, "_4", decimal);
490        int_parse_err!(isize, "0_o_4", octal);
491        int_parse_err!(isize, "0_b_1", binary);
492    }
493}
494
495#[cfg(test)]
496mod test_parsing_floats {
497    use std::f64::consts::PI;
498
499    use super::*;
500
501    #[test]
502    fn dec() {
503        let f = parse("12.34");
504        assert_eq!(12.34, f.unwrap());
505    }
506
507    #[test]
508    fn dec_separators() {
509        assert_eq!(12.34, parse("1_2.3_4").unwrap());
510        assert_eq!(12.34_f32, parse("1_2.3_4").unwrap());
511        assert_eq!(12.34_f64, parse("1_2.3_4").unwrap());
512
513        assert_eq!(PI, parse("3.141_592_653_589_793").unwrap());
514    }
515}
516
517#[cfg(test)]
518mod pretty_print_test {
519    use super::*;
520
521    #[test]
522    fn intuitive_syntax() {
523        assert_eq!("42_230_123", format_pretty_dec(42_230_123));
524
525        assert_eq!("42.0", format_pretty_dec(42.0_f32));
526    }
527
528    #[test]
529    fn positive_ints_based() {
530        assert_eq!("42", format_pretty_dec(42));
531        assert_eq!("42_000", format_pretty_dec(42_000));
532
533        assert_eq!("-42_000", format_pretty_dec(-42_000));
534    }
535
536    #[test]
537    fn positive_ints_oct() {
538        let ft = 0o42;
539        assert_eq!("0o42", format_pretty_octal(ft));
540        assert_eq!("0o42_000", format_pretty_octal(0o42_000));
541
542        assert_eq!("0o42_000", format_pretty_octal(0o42_000_u64));
543        assert_eq!("0o42_000", format_pretty_octal(0o42_000_i64));
544    }
545
546    #[test]
547    fn positive_ints_oct_neg() {
548        let ft = -0o42;
549        assert_eq!("-0o42", format_pretty_octal(ft));
550        assert_eq!("-0o42_000", format_pretty_octal(-0o42_000));
551        assert_eq!("-0o4_200", format_pretty_octal(-0o4_200));
552
553        assert_eq!("-0o42_000", format_pretty_octal(-0o42_000_i64));
554    }
555
556    #[test]
557    fn positive_ints_hex() {
558        assert_eq!("0x42", format_pretty_hex(0x42));
559
560        assert_eq!("0xf_ff", format_pretty_hex(0xf_ff));
561        assert_eq!("0x42_00", format_pretty_hex(0x4_200));
562
563        assert_eq!("0xff_ff", format_pretty_hex(0xff_FF));
564        assert_eq!("0x4_20_00", format_pretty_hex(0x42_000));
565
566        assert_eq!("0x4_20_00", format_pretty_hex(0x42_000_u64));
567        assert_eq!("0x4_20_00", format_pretty_hex(0x42_000_i64));
568    }
569
570    #[test]
571    fn negative_ints_hex() {
572        assert_eq!("-0x42", format_pretty_hex(-0x42_i8));
573        assert_eq!("-0x42", format_pretty_hex(-0x42_i16));
574
575        assert_eq!("-0x42_00", format_pretty_hex(-0x42_00_i32));
576        assert_eq!("-0x4_20_00", format_pretty_hex(-0x4_20_00_i64));
577    }
578
579    #[test]
580    fn dec_ints() {
581        assert_eq!("0", format_pretty_dec(0));
582        assert_eq!("42", format_pretty_dec(42));
583        assert_eq!("42_000", format_pretty_dec(42_000));
584
585        assert_eq!("-4_200", format_pretty_dec(-4200));
586        assert_eq!("-42_000", format_pretty_dec(-42_000));
587    }
588
589    #[test]
590    fn dec_floats() {
591        assert_eq!("0.0", format_pretty_dec(0.0_f32));
592        assert_eq!("0.0", format_pretty_dec(0.0_f64));
593
594        assert_eq!("42.0", format_pretty_dec(42.0_f32));
595        assert_eq!("42_000.0", format_pretty_dec(42_000.0_f64));
596
597        assert_eq!("-42_000.0", format_pretty_dec(-42_000.0));
598        assert_eq!("-42_000.001_002", format_pretty_dec(-42_000.001_002));
599    }
600
601    #[test]
602    fn bin_ints() {
603        assert_eq!("0b0", format_pretty_bin(0));
604        assert_eq!("0b10_1010", format_pretty_bin(42));
605        assert_eq!("0b1010_0100_0001_0000", format_pretty_bin(42_000));
606
607        assert_eq!("-0b1_0000_0110_1000", format_pretty_bin(-4200));
608        assert_eq!("-0b1010_0100_0001_0000", format_pretty_bin(-42_000));
609    }
610}