ion_rs/text/parsers/
float.rs1use crate::text::parse_result::{IonParseResult, OrFatalParseError, UpgradeIResult};
2use crate::text::parsers::numeric_support::{
3 digits_before_dot, exponent_digits, floating_point_number,
4};
5use crate::text::parsers::stop_character;
6use crate::text::text_value::TextValue;
7use nom::branch::alt;
8use nom::bytes::streaming::tag;
9use nom::character::streaming::one_of;
10use nom::combinator::{map, opt, recognize};
11use nom::sequence::{pair, preceded, terminated, tuple};
12use nom::Parser;
13use std::str::FromStr;
14
15pub(crate) fn parse_float(input: &str) -> IonParseResult<TextValue> {
18 terminated(
19 alt((float_special_value, float_numeric_value)),
20 stop_character,
21 )(input)
22}
23
24fn float_special_value(input: &str) -> IonParseResult<TextValue> {
26 map(tag("nan"), |_| TextValue::Float(f64::NAN))
27 .or(map(tag("+inf"), |_| TextValue::Float(f64::INFINITY)))
28 .or(map(tag("-inf"), |_| TextValue::Float(f64::NEG_INFINITY)))
29 .parse(input)
30 .upgrade()
31}
32
33fn float_numeric_value(input: &str) -> IonParseResult<TextValue> {
35 let (remaining, text) = recognize(tuple((
36 alt((
37 floating_point_number,
38 recognize(pair(opt(tag("-")), digits_before_dot)),
39 )),
40 recognize(float_exponent_marker_followed_by_digits),
41 )))(input)?;
42 let mut sanitized = text.replace('_', "");
44 if sanitized.ends_with('e') || sanitized.ends_with('E') {
45 sanitized.push('0');
46 }
47 let float = f64::from_str(&sanitized)
48 .or_fatal_parse_error(input, "could not parse float as f64")?
49 .1;
50 Ok((remaining, TextValue::Float(float)))
51}
52
53fn float_exponent_marker_followed_by_digits(input: &str) -> IonParseResult<&str> {
54 preceded(one_of("eE"), exponent_digits)(input)
55}
56
57#[cfg(test)]
58mod float_parsing_tests {
59 use crate::text::parsers::float::parse_float;
60 use crate::text::parsers::unit_test_support::{parse_test_err, parse_test_ok, parse_unwrap};
61 use crate::text::text_value::TextValue;
62 use std::str::FromStr;
63
64 fn parse_equals(text: &str, expected: f64) {
65 parse_test_ok(parse_float, text, TextValue::Float(expected))
66 }
67
68 fn parse_fails(text: &str) {
69 parse_test_err(parse_float, text)
70 }
71
72 #[test]
73 fn test_parse_float_special_values() {
74 parse_equals("+inf ", f64::INFINITY);
75 parse_equals("-inf ", f64::NEG_INFINITY);
76
77 let value = parse_unwrap(parse_float, "nan ");
79 if let TextValue::Float(f) = value {
80 assert!(f.is_nan());
81 } else {
82 panic!("Expected NaN, but got: {value:?}");
83 }
84
85 let value = parse_unwrap(parse_float, "-0e0 ");
87 if let TextValue::Float(f) = value {
88 assert!(f == 0.0f64);
89 assert!(f.is_sign_negative())
90 } else {
91 panic!("Expected -0e0, but got: {value:?}");
92 }
93 }
94
95 #[test]
96 fn test_parse_float_numeric_values() {
97 parse_equals("0.0e0 ", 0.0);
98 parse_equals("0E0 ", 0.0);
99 parse_equals("0e0 ", 0e0);
100 parse_equals("305e1 ", 3050.0);
101 parse_equals("305.0e1 ", 3050.0);
102 parse_equals("-0.279e3 ", -279.0);
103 parse_equals("-279e0 ", -279.0);
104 parse_equals("-279.5e0 ", -279.5);
105
106 parse_fails("305 ");
108 parse_fails("305e ");
110 parse_fails(".305e ");
112 parse_fails("305e0.5");
114 parse_fails("305e-0.5");
116 parse_fails(" 305e1 ");
118 parse_fails("0305e1 ");
120 parse_fails("+305e1 ");
122 parse_fails("--305e1 ");
124 parse_fails("305e1");
126 }
127
128 #[test]
129 fn test_parse_float_numeric_values_with_underscores() {
130 parse_equals("111_111e222 ", 111111.0 * 10f64.powf(222f64));
131 parse_equals("111_111.667e222 ", 111111.667 * 10f64.powf(222f64));
132 parse_equals("111_111e222_222 ", 111111.0 * 10f64.powf(222222f64));
133 parse_equals("-999_9e9_9 ", f64::from_str("-9999e99").unwrap());
134
135 parse_fails("_305e1 ");
137 parse_fails("305e1_ ");
139 parse_fails("30__5e1 ");
141 }
142}