kalosm_sample/structured_parser/
float.rs

1use crate::{CreateParserState, ParseStatus, Parser};
2use std::ops::RangeInclusive;
3
4#[derive(Debug, PartialEq, Eq, Default, Copy, Clone)]
5enum FloatParserProgress {
6    #[default]
7    Initial,
8    AfterSign,
9    AfterDigit,
10    AfterDecimalPoint {
11        digits_after_decimal_point: u32,
12    },
13}
14
15impl FloatParserProgress {
16    fn is_after_digit(&self) -> bool {
17        matches!(
18            self,
19            FloatParserProgress::AfterDigit | FloatParserProgress::AfterDecimalPoint { .. }
20        )
21    }
22}
23
24/// The state of an integer parser.
25#[derive(Debug, PartialEq, Copy, Clone)]
26pub struct FloatParserState {
27    state: FloatParserProgress,
28    value: f64,
29    positive: bool,
30}
31
32impl Default for FloatParserState {
33    fn default() -> Self {
34        Self {
35            state: FloatParserProgress::Initial,
36            value: 0.0,
37            positive: true,
38        }
39    }
40}
41
42/// A parser for a float.
43#[derive(Debug, PartialEq, Clone)]
44pub struct FloatParser {
45    range: RangeInclusive<f64>,
46}
47
48impl FloatParser {
49    /// Create a new float parser.
50    pub fn new(range: RangeInclusive<f64>) -> Self {
51        if range.start() > range.end() {
52            Self {
53                range: *range.end()..=*range.start(),
54            }
55        } else {
56            Self { range }
57        }
58    }
59}
60
61impl CreateParserState for FloatParser {
62    fn create_parser_state(&self) -> <Self as Parser>::PartialState {
63        FloatParserState::default()
64    }
65}
66
67impl FloatParser {
68    fn sign_valid(&self, positive: bool) -> bool {
69        if positive {
70            *self.range.start() >= 0.0
71        } else {
72            *self.range.end() <= 0.0
73        }
74    }
75
76    fn is_number_valid(&self, value: f64) -> bool {
77        self.range.contains(&value)
78    }
79
80    fn could_number_become_valid_before_decimal(
81        &self,
82        value: f64,
83        state: FloatParserProgress,
84    ) -> bool {
85        if self.is_number_valid(value) {
86            true
87        } else {
88            let num_with_extra_digit = value * 10.;
89            if value < 0. {
90                if *self.range.start() > num_with_extra_digit {
91                    return false;
92                }
93            } else if *self.range.end() < num_with_extra_digit {
94                return false;
95            }
96            let value_string = value.abs().to_string();
97            let start_value_string = self.range.start().abs().to_string();
98            let end_value_string = self.range.end().abs().to_string();
99            match state {
100                FloatParserProgress::AfterDigit | FloatParserProgress::AfterSign => {
101                    // Check if the digits are within the range so far
102                    let digits = value_string.chars();
103                    let start_digits = start_value_string.chars();
104                    let end_digits = end_value_string.chars();
105                    for (digit, (start_digit, end_digit)) in
106                        digits.zip(start_digits.zip(end_digits))
107                    {
108                        if digit < start_digit || digit > end_digit {
109                            return false;
110                        }
111                    }
112                }
113                _ => {}
114            }
115            true
116        }
117    }
118
119    fn could_number_become_valid_after_decimal(
120        &self,
121        value: f64,
122        digits_after_decimal_point: u32,
123    ) -> bool {
124        let distance = if value < 0.0 {
125            *self.range.start() - value
126        } else {
127            *self.range.end() - value
128        };
129        println!("Distance: {}", distance);
130
131        distance < 10.0_f64.powi(-(digits_after_decimal_point as i32))
132    }
133}
134
135/// An error that can occur while parsing a float literal when the number starts with a leading zero.
136#[derive(Debug)]
137pub struct LeadingZeroError;
138
139impl std::fmt::Display for LeadingZeroError {
140    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141        write!(
142            f,
143            "Found leading zero. Leading zeros are not allowed when parsing a number"
144        )
145    }
146}
147
148impl std::error::Error for LeadingZeroError {}
149
150/// An error that can occur while parsing a float literal when the number is out of range.
151#[derive(Debug)]
152pub struct OutOfRangeError;
153
154impl std::fmt::Display for OutOfRangeError {
155    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156        write!(f, "Attempted to parse a number that was out of range")
157    }
158}
159
160impl std::error::Error for OutOfRangeError {}
161
162/// An error that can occur while parsing a float literal when the number contains a decimal point in the wrong place.
163#[derive(Debug)]
164pub struct InvalidDecimalLocation;
165
166impl std::fmt::Display for InvalidDecimalLocation {
167    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168        write!(
169            f,
170            "Failed to parse a number with a decimal before the first digit or multiple decimals"
171        )
172    }
173}
174
175impl std::error::Error for InvalidDecimalLocation {}
176
177/// An error that can occur while parsing a float literal when the number contains a sign in the wrong place.
178#[derive(Debug)]
179pub struct InvalidSignLocation;
180
181impl std::fmt::Display for InvalidSignLocation {
182    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183        write!(
184            f,
185            "Failed to parse a number with a sign after the first character"
186        )
187    }
188}
189
190impl std::error::Error for InvalidSignLocation {}
191
192/// An error that can occur while parsing a float literal when trying to parse a number with no characters.
193#[derive(Debug)]
194pub struct EmptyNumber;
195
196impl std::fmt::Display for EmptyNumber {
197    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198        write!(f, "Failed to parse a number with no digits")
199    }
200}
201
202impl std::error::Error for EmptyNumber {}
203
204impl Parser for FloatParser {
205    type Output = f64;
206    type PartialState = FloatParserState;
207
208    fn parse<'a>(
209        &self,
210        state: &FloatParserState,
211        input: &'a [u8],
212    ) -> crate::ParseResult<ParseStatus<'a, Self::PartialState, Self::Output>> {
213        let mut value = state.value;
214        let mut positive = state.positive;
215        let mut state = state.state;
216
217        for index in 0..input.len() {
218            let input_byte = input[index];
219            let digit = match input_byte {
220                b'0'..=b'9' => {
221                    if (state == FloatParserProgress::Initial
222                        || state == FloatParserProgress::AfterSign)
223                        && input_byte == b'0'
224                    {
225                        crate::bail!(LeadingZeroError);
226                    }
227                    input_byte - b'0'
228                }
229                b'.' => {
230                    let value_digits = value.abs().log10() + 1.;
231                    let start_digits = self.range.start().abs().log10() + 1.;
232                    let end_digits = self.range.end().abs().log10() + 1.;
233                    if positive {
234                        if value_digits > end_digits {
235                            crate::bail!(OutOfRangeError);
236                        }
237                    } else if value_digits > start_digits {
238                        crate::bail!(OutOfRangeError);
239                    }
240                    if state == FloatParserProgress::AfterDigit {
241                        state = FloatParserProgress::AfterDecimalPoint {
242                            digits_after_decimal_point: 0,
243                        };
244                        continue;
245                    } else {
246                        crate::bail!(InvalidDecimalLocation);
247                    }
248                }
249                b'+' | b'-' => {
250                    if state == FloatParserProgress::Initial {
251                        state = FloatParserProgress::AfterSign;
252                        positive = input_byte == b'+';
253
254                        if !self.sign_valid(positive) {
255                            crate::bail!(InvalidSignLocation);
256                        }
257                        continue;
258                    } else {
259                        crate::bail!(InvalidSignLocation);
260                    }
261                }
262                _ => {
263                    if state.is_after_digit() {
264                        let result = value * if positive { 1.0 } else { -1.0 };
265                        if self.is_number_valid(result) {
266                            return Ok(ParseStatus::Finished {
267                                result,
268                                remaining: &input[index..],
269                            });
270                        }
271                        return Ok(ParseStatus::Finished {
272                            result,
273                            remaining: &input[index..],
274                        });
275                    } else {
276                        crate::bail!(EmptyNumber)
277                    }
278                }
279            };
280
281            match &mut state {
282                FloatParserProgress::Initial => {
283                    state = FloatParserProgress::AfterDigit;
284                    value = f64::from(digit);
285                }
286                FloatParserProgress::AfterSign => {
287                    state = FloatParserProgress::AfterDigit;
288                    value = f64::from(digit);
289                }
290                FloatParserProgress::AfterDigit => {
291                    value = value * 10.0 + f64::from(digit);
292
293                    if !self.could_number_become_valid_before_decimal(
294                        value * if positive { 1.0 } else { -1.0 },
295                        FloatParserProgress::AfterDigit,
296                    ) {
297                        crate::bail!(OutOfRangeError);
298                    }
299                }
300                FloatParserProgress::AfterDecimalPoint {
301                    digits_after_decimal_point,
302                } => {
303                    value +=
304                        f64::from(digit) / 10.0_f64.powi(*digits_after_decimal_point as i32 + 1);
305                    *digits_after_decimal_point += 1;
306
307                    let signed_value = value * if positive { 1.0 } else { -1.0 };
308                    if !self.range.contains(&signed_value)
309                        && !self.could_number_become_valid_after_decimal(
310                            signed_value,
311                            *digits_after_decimal_point,
312                        )
313                    {
314                        crate::bail!(OutOfRangeError);
315                    }
316                }
317            }
318        }
319
320        Ok(ParseStatus::Incomplete {
321            new_state: FloatParserState {
322                state,
323                value,
324                positive,
325            },
326            required_next: Default::default(),
327        })
328    }
329}
330
331#[test]
332fn float_parser() {
333    let parser = FloatParser {
334        range: -100.0..=200.0,
335    };
336    let state = FloatParserState::default();
337    assert_eq!(
338        parser.parse(&state, b"123").unwrap(),
339        ParseStatus::Incomplete {
340            new_state: FloatParserState {
341                state: FloatParserProgress::AfterDigit,
342                value: 123.0,
343                positive: true
344            },
345            required_next: Default::default()
346        }
347    );
348    assert_eq!(
349        parser.parse(&state, b"123.456").unwrap(),
350        ParseStatus::Incomplete {
351            new_state: FloatParserState {
352                state: FloatParserProgress::AfterDecimalPoint {
353                    digits_after_decimal_point: 3
354                },
355                value: 123.456,
356                positive: true
357            },
358            required_next: Default::default()
359        }
360    );
361    assert_eq!(
362        parser
363            .parse(
364                &parser
365                    .parse(&state, b"123.456")
366                    .unwrap()
367                    .unwrap_incomplete()
368                    .0,
369                b"789x"
370            )
371            .unwrap(),
372        ParseStatus::Finished {
373            result: 123.456789,
374            remaining: b"x"
375        }
376    );
377    assert_eq!(
378        parser.parse(&state, b"123.456x").unwrap(),
379        ParseStatus::Finished {
380            result: 123.456,
381            remaining: b"x"
382        }
383    );
384    assert!(parser.parse(&state, b"abc").is_err());
385}