litrs/float/mod.rs
1use std::fmt;
2
3use 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)]
23pub struct FloatLit<B: Buffer> {
24 /// Basically the whole literal, but without the type suffix. Other `usize`
25 /// fields in this struct partition this string. `end_integer_part` is
26 /// always <= `end_fractional_part`.
27 ///
28 /// ```text
29 /// 12_3.4_56e789
30 /// ╷ ╷
31 /// | └ end_fractional_part = 9
32 /// └ end_integer_part = 4
33 ///
34 /// 246.
35 /// ╷╷
36 /// |└ end_fractional_part = 4
37 /// └ end_integer_part = 3
38 ///
39 /// 1234e89
40 /// ╷
41 /// |
42 /// └ end_integer_part = end_fractional_part = 4
43 /// ```
44 number_part: 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 /// Optional type suffix.
54 type_suffix: Option<FloatType>,
55}
56
57/// All possible float type suffixes.
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum FloatType {
60 F32,
61 F64,
62}
63
64impl<B: Buffer> FloatLit<B> {
65 /// Parses the input as a floating point literal. Returns an error if the
66 /// input is invalid or represents a different kind of literal.
67 pub fn parse(s: B) -> Result<Self, ParseError> {
68 match first_byte_or_empty(&s)? {
69 b'0'..=b'9' => Self::parse_impl(s),
70 _ => Err(perr(0, DoesNotStartWithDigit)),
71 }
72 }
73
74 /// Returns the whole number part (including integer part, fractional part
75 /// and exponent), but without the type suffix. If you want an actual
76 /// floating point value, you need to parse this string, e.g. with
77 /// `f32::from_str` or an external crate.
78 pub fn number_part(&self) -> &str {
79 &self.number_part
80 }
81
82 /// Returns the non-empty integer part of this literal.
83 pub fn integer_part(&self) -> &str {
84 &(*self.number_part)[..self.end_integer_part]
85 }
86
87 /// Returns the optional fractional part of this literal. Does not include
88 /// the period. If a period exists in the input, `Some` is returned, `None`
89 /// otherwise. Note that `Some("")` might be returned, e.g. for `3.`.
90 pub fn fractional_part(&self) -> Option<&str> {
91 if self.end_integer_part == self.end_fractional_part {
92 None
93 } else {
94 Some(&(*self.number_part)[self.end_integer_part + 1..self.end_fractional_part])
95 }
96 }
97
98 /// Optional exponent part. Might be empty if there was no exponent part in
99 /// the input. Includes the `e` or `E` at the beginning.
100 pub fn exponent_part(&self) -> &str {
101 &(*self.number_part)[self.end_fractional_part..]
102 }
103
104 /// The optional type suffix.
105 pub fn type_suffix(&self) -> Option<FloatType> {
106 self.type_suffix
107 }
108
109 /// Precondition: first byte of string has to be in `b'0'..=b'9'`.
110 pub(crate) fn parse_impl(input: B) -> Result<Self, ParseError> {
111 // Integer part.
112 let end_integer_part = end_dec_digits(&input);
113 let rest = &input[end_integer_part..];
114
115
116 // Fractional part.
117 let end_fractional_part = if rest.as_bytes().get(0) == Some(&b'.') {
118 // The fractional part must not start with `_`.
119 if rest.as_bytes().get(1) == Some(&b'_') {
120 return Err(perr(end_integer_part + 1, UnexpectedChar));
121 }
122
123 end_dec_digits(&rest[1..]) + 1 + end_integer_part
124 } else {
125 end_integer_part
126 };
127 let rest = &input[end_fractional_part..];
128
129 // If we have a period that is not followed by decimal digits, the
130 // literal must end now.
131 if end_integer_part + 1 == end_fractional_part && !rest.is_empty() {
132 return Err(perr(end_integer_part + 1, UnexpectedChar));
133 }
134
135
136 // Optional exponent.
137 let end_number_part = if rest.starts_with('e') || rest.starts_with('E') {
138 // Strip single - or + sign at the beginning.
139 let exp_number_start = match rest.as_bytes().get(1) {
140 Some(b'-') | Some(b'+') => 2,
141 _ => 1,
142 };
143
144 // Find end of exponent and make sure there is at least one digit.
145 let end_exponent = end_dec_digits(&rest[exp_number_start..]) + exp_number_start;
146 if !rest[exp_number_start..end_exponent].bytes().any(|b| matches!(b, b'0'..=b'9')) {
147 return Err(perr(
148 end_fractional_part..end_fractional_part + end_exponent,
149 NoExponentDigits,
150 ));
151 }
152
153 end_exponent + end_fractional_part
154 } else {
155 end_fractional_part
156 };
157
158
159 // Type suffix
160 let type_suffix = match &input[end_number_part..] {
161 "" => None,
162 "f32" => Some(FloatType::F32),
163 "f64" => Some(FloatType::F64),
164 _ => return Err(perr(end_number_part..input.len(), InvalidFloatTypeSuffix)),
165 };
166
167 Ok(Self {
168 number_part: input.cut(0..end_number_part),
169 end_integer_part,
170 end_fractional_part,
171 type_suffix,
172 })
173 }
174}
175
176impl FloatLit<&str> {
177 /// Makes a copy of the underlying buffer and returns the owned version of
178 /// `Self`.
179 pub fn to_owned(&self) -> FloatLit<String> {
180 FloatLit {
181 number_part: self.number_part.to_owned(),
182 end_integer_part: self.end_integer_part,
183 end_fractional_part: self.end_fractional_part,
184 type_suffix: self.type_suffix,
185 }
186 }
187}
188
189impl<B: Buffer> fmt::Display for FloatLit<B> {
190 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191 let suffix = match self.type_suffix {
192 None => "",
193 Some(FloatType::F32) => "f32",
194 Some(FloatType::F64) => "f64",
195 };
196 write!(f, "{}{}", self.number_part(), suffix)
197 }
198}
199
200
201#[cfg(test)]
202mod tests;