]> git.lizzy.rs Git - rust.git/blob - src/libcore/num/dec2flt/parse.rs
Auto merge of #58406 - Disasm:rv64-support, r=nagisa
[rust.git] / src / libcore / num / dec2flt / parse.rs
1 //! Validating and decomposing a decimal string of the form:
2 //!
3 //! `(digits | digits? '.'? digits?) (('e' | 'E') ('+' | '-')? digits)?`
4 //!
5 //! In other words, standard floating-point syntax, with two exceptions: No sign, and no
6 //! handling of "inf" and "NaN". These are handled by the driver function (super::dec2flt).
7 //!
8 //! Although recognizing valid inputs is relatively easy, this module also has to reject the
9 //! countless invalid variations, never panic, and perform numerous checks that the other
10 //! modules rely on to not panic (or overflow) in turn.
11 //! To make matters worse, all that happens in a single pass over the input.
12 //! So, be careful when modifying anything, and double-check with the other modules.
13 use super::num;
14 use self::ParseResult::{Valid, ShortcutToInf, ShortcutToZero, Invalid};
15
16 #[derive(Debug)]
17 pub enum Sign {
18     Positive,
19     Negative,
20 }
21
22 #[derive(Debug, PartialEq, Eq)]
23 /// The interesting parts of a decimal string.
24 pub struct Decimal<'a> {
25     pub integral: &'a [u8],
26     pub fractional: &'a [u8],
27     /// The decimal exponent, guaranteed to have fewer than 18 decimal digits.
28     pub exp: i64,
29 }
30
31 impl<'a> Decimal<'a> {
32     pub fn new(integral: &'a [u8], fractional: &'a [u8], exp: i64) -> Decimal<'a> {
33         Decimal { integral, fractional, exp }
34     }
35 }
36
37 #[derive(Debug, PartialEq, Eq)]
38 pub enum ParseResult<'a> {
39     Valid(Decimal<'a>),
40     ShortcutToInf,
41     ShortcutToZero,
42     Invalid,
43 }
44
45 /// Checks if the input string is a valid floating point number and if so, locate the integral
46 /// part, the fractional part, and the exponent in it. Does not handle signs.
47 pub fn parse_decimal(s: &str) -> ParseResult {
48     if s.is_empty() {
49         return Invalid;
50     }
51
52     let s = s.as_bytes();
53     let (integral, s) = eat_digits(s);
54
55     match s.first() {
56         None => Valid(Decimal::new(integral, b"", 0)),
57         Some(&b'e') | Some(&b'E') => {
58             if integral.is_empty() {
59                 return Invalid; // No digits before 'e'
60             }
61
62             parse_exp(integral, b"", &s[1..])
63         }
64         Some(&b'.') => {
65             let (fractional, s) = eat_digits(&s[1..]);
66             if integral.is_empty() && fractional.is_empty() {
67                 // We require at least a single digit before or after the point.
68                 return Invalid;
69             }
70
71             match s.first() {
72                 None => Valid(Decimal::new(integral, fractional, 0)),
73                 Some(&b'e') | Some(&b'E') => parse_exp(integral, fractional, &s[1..]),
74                 _ => Invalid, // Trailing junk after fractional part
75             }
76         }
77         _ => Invalid, // Trailing junk after first digit string
78     }
79 }
80
81 /// Carve off decimal digits up to the first non-digit character.
82 fn eat_digits(s: &[u8]) -> (&[u8], &[u8]) {
83     let mut i = 0;
84     while i < s.len() && b'0' <= s[i] && s[i] <= b'9' {
85         i += 1;
86     }
87     (&s[..i], &s[i..])
88 }
89
90 /// Exponent extraction and error checking.
91 fn parse_exp<'a>(integral: &'a [u8], fractional: &'a [u8], rest: &'a [u8]) -> ParseResult<'a> {
92     let (sign, rest) = match rest.first() {
93         Some(&b'-') => (Sign::Negative, &rest[1..]),
94         Some(&b'+') => (Sign::Positive, &rest[1..]),
95         _ => (Sign::Positive, rest),
96     };
97     let (mut number, trailing) = eat_digits(rest);
98     if !trailing.is_empty() {
99         return Invalid; // Trailing junk after exponent
100     }
101     if number.is_empty() {
102         return Invalid; // Empty exponent
103     }
104     // At this point, we certainly have a valid string of digits. It may be too long to put into
105     // an `i64`, but if it's that huge, the input is certainly zero or infinity. Since each zero
106     // in the decimal digits only adjusts the exponent by +/- 1, at exp = 10^18 the input would
107     // have to be 17 exabyte (!) of zeros to get even remotely close to being finite.
108     // This is not exactly a use case we need to cater to.
109     while number.first() == Some(&b'0') {
110         number = &number[1..];
111     }
112     if number.len() >= 18 {
113         return match sign {
114             Sign::Positive => ShortcutToInf,
115             Sign::Negative => ShortcutToZero,
116         };
117     }
118     let abs_exp = num::from_str_unchecked(number);
119     let e = match sign {
120         Sign::Positive => abs_exp as i64,
121         Sign::Negative => -(abs_exp as i64),
122     };
123     Valid(Decimal::new(integral, fractional, e))
124 }