1 use std::fmt;
2
3 use crate::{
4 Buffer, ParseError,
5 err::{perr, ParseErrorKind::*},
6 parse::{end_dec_digits, first_byte_or_empty},
7 };
8
9
10
11 /// A floating point literal, e.g. `3.14`, `8.`, `135e12`, `27f32` or `1.956e2f64`.
12 ///
13 /// This kind of literal has several forms, but generally consists of a main
14 /// number part, an optional exponent and an optional type suffix. See
15 /// [the reference][ref] for more information.
16 ///
17 /// A leading minus sign `-` is not part of the literal grammar! `-3.14` are two
18 /// tokens in the Rust grammar.
19 ///
20 ///
21 /// [ref]: https://doc.rust-lang.org/reference/tokens.html#floating-point-literals
22 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
23 pub struct FloatLit<B: Buffer> {
24 /// The whole raw input. The `usize` fields in this struct partition this
25 /// string. Always true: `end_integer_part <= end_fractional_part`.
26 ///
27 /// ```text
28 /// 12_3.4_56e789f32
29 /// ╷ ╷ ╷
30 /// | | └ end_number_part = 13
31 /// | └ end_fractional_part = 9
32 /// └ end_integer_part = 4
33 ///
34 /// 246.
35 /// ╷╷
36 /// |└ end_fractional_part = end_number_part = 4
37 /// └ end_integer_part = 3
38 ///
39 /// 1234e89
40 /// ╷ ╷
41 /// | └ end_number_part = 7
42 /// └ end_integer_part = end_fractional_part = 4
43 /// ```
44 raw: B,
45
46 /// The first index not part of the integer part anymore. Since the integer
47 /// part is at the start, this is also the length of that part.
48 end_integer_part: usize,
49
50 /// The first index after the fractional part.
51 end_fractional_part: usize,
52
53 /// The first index after the whole number part (everything except type suffix).
54 end_number_part: usize,
55
56 /// Optional type suffix.
57 type_suffix: Option<FloatType>,
58 }
59
60 /// All possible float type suffixes.
61 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
62 pub enum FloatType {
63 F32,
64 F64,
65 }
66
67 impl<B: Buffer> FloatLit<B> {
68 /// Parses the input as a floating point literal. Returns an error if the
69 /// input is invalid or represents a different kind of literal.
parse(s: B) -> Result<Self, ParseError>70 pub fn parse(s: B) -> Result<Self, ParseError> {
71 match first_byte_or_empty(&s)? {
72 b'0'..=b'9' => {
73 // TODO: simplify once RFC 2528 is stabilized
74 let FloatLit {
75 end_integer_part,
76 end_fractional_part,
77 end_number_part,
78 type_suffix,
79 ..
80 } = parse_impl(&s)?;
81
82 Ok(Self {
83 raw: s,
84 end_integer_part,
85 end_fractional_part,
86 end_number_part,
87 type_suffix,
88 })
89 },
90 _ => Err(perr(0, DoesNotStartWithDigit)),
91 }
92 }
93
94 /// Returns the whole number part (including integer part, fractional part
95 /// and exponent), but without the type suffix. If you want an actual
96 /// floating point value, you need to parse this string, e.g. with
97 /// `f32::from_str` or an external crate.
number_part(&self) -> &str98 pub fn number_part(&self) -> &str {
99 &(*self.raw)[..self.end_number_part]
100 }
101
102 /// Returns the non-empty integer part of this literal.
integer_part(&self) -> &str103 pub fn integer_part(&self) -> &str {
104 &(*self.raw)[..self.end_integer_part]
105 }
106
107 /// Returns the optional fractional part of this literal. Does not include
108 /// the period. If a period exists in the input, `Some` is returned, `None`
109 /// otherwise. Note that `Some("")` might be returned, e.g. for `3.`.
fractional_part(&self) -> Option<&str>110 pub fn fractional_part(&self) -> Option<&str> {
111 if self.end_integer_part == self.end_fractional_part {
112 None
113 } else {
114 Some(&(*self.raw)[self.end_integer_part + 1..self.end_fractional_part])
115 }
116 }
117
118 /// Optional exponent part. Might be empty if there was no exponent part in
119 /// the input. Includes the `e` or `E` at the beginning.
exponent_part(&self) -> &str120 pub fn exponent_part(&self) -> &str {
121 &(*self.raw)[self.end_fractional_part..self.end_number_part]
122 }
123
124 /// The optional type suffix.
type_suffix(&self) -> Option<FloatType>125 pub fn type_suffix(&self) -> Option<FloatType> {
126 self.type_suffix
127 }
128
129 /// Returns the raw input that was passed to `parse`.
raw_input(&self) -> &str130 pub fn raw_input(&self) -> &str {
131 &self.raw
132 }
133
134 /// Returns the raw input that was passed to `parse`, potentially owned.
into_raw_input(self) -> B135 pub fn into_raw_input(self) -> B {
136 self.raw
137 }
138 }
139
140 impl FloatLit<&str> {
141 /// Makes a copy of the underlying buffer and returns the owned version of
142 /// `Self`.
to_owned(&self) -> FloatLit<String>143 pub fn to_owned(&self) -> FloatLit<String> {
144 FloatLit {
145 raw: self.raw.to_owned(),
146 end_integer_part: self.end_integer_part,
147 end_fractional_part: self.end_fractional_part,
148 end_number_part: self.end_number_part,
149 type_suffix: self.type_suffix,
150 }
151 }
152 }
153
154 impl<B: Buffer> fmt::Display for FloatLit<B> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result155 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156 write!(f, "{}", &*self.raw)
157 }
158 }
159
160 /// Precondition: first byte of string has to be in `b'0'..=b'9'`.
161 #[inline(never)]
parse_impl(input: &str) -> Result<FloatLit<&str>, ParseError>162 pub(crate) fn parse_impl(input: &str) -> Result<FloatLit<&str>, ParseError> {
163 // Integer part.
164 let end_integer_part = end_dec_digits(input.as_bytes());
165 let rest = &input[end_integer_part..];
166
167
168 // Fractional part.
169 let end_fractional_part = if rest.as_bytes().get(0) == Some(&b'.') {
170 // The fractional part must not start with `_`.
171 if rest.as_bytes().get(1) == Some(&b'_') {
172 return Err(perr(end_integer_part + 1, UnexpectedChar));
173 }
174
175 end_dec_digits(rest[1..].as_bytes()) + 1 + end_integer_part
176 } else {
177 end_integer_part
178 };
179 let rest = &input[end_fractional_part..];
180
181 // If we have a period that is not followed by decimal digits, the
182 // literal must end now.
183 if end_integer_part + 1 == end_fractional_part && !rest.is_empty() {
184 return Err(perr(end_integer_part + 1, UnexpectedChar));
185 }
186
187
188 // Optional exponent.
189 let end_number_part = if rest.starts_with('e') || rest.starts_with('E') {
190 // Strip single - or + sign at the beginning.
191 let exp_number_start = match rest.as_bytes().get(1) {
192 Some(b'-') | Some(b'+') => 2,
193 _ => 1,
194 };
195
196 // Find end of exponent and make sure there is at least one digit.
197 let end_exponent = end_dec_digits(rest[exp_number_start..].as_bytes()) + exp_number_start;
198 if !rest[exp_number_start..end_exponent].bytes().any(|b| matches!(b, b'0'..=b'9')) {
199 return Err(perr(
200 end_fractional_part..end_fractional_part + end_exponent,
201 NoExponentDigits,
202 ));
203 }
204
205 end_exponent + end_fractional_part
206 } else {
207 end_fractional_part
208 };
209
210
211 // Type suffix
212 let type_suffix = match &input[end_number_part..] {
213 "" => None,
214 "f32" => Some(FloatType::F32),
215 "f64" => Some(FloatType::F64),
216 _ => return Err(perr(end_number_part..input.len(), InvalidFloatTypeSuffix)),
217 };
218
219 Ok(FloatLit {
220 raw: input,
221 end_integer_part,
222 end_fractional_part,
223 end_number_part,
224 type_suffix,
225 })
226 }
227
228 #[cfg(test)]
229 mod tests;
230