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;