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