use crate::text::parse_result::{IonParseResult, OrFatalParseError, UpgradeIResult};
use crate::text::parsers::numeric_support::{
digits_before_dot, exponent_digits, floating_point_number,
};
use crate::text::parsers::stop_character;
use crate::text::text_value::TextValue;
use nom::branch::alt;
use nom::bytes::streaming::tag;
use nom::character::streaming::one_of;
use nom::combinator::{map, opt, recognize};
use nom::sequence::{pair, preceded, terminated, tuple};
use nom::Parser;
use std::str::FromStr;
pub(crate) fn parse_float(input: &str) -> IonParseResult<TextValue> {
terminated(
alt((float_special_value, float_numeric_value)),
stop_character,
)(input)
}
fn float_special_value(input: &str) -> IonParseResult<TextValue> {
map(tag("nan"), |_| TextValue::Float(f64::NAN))
.or(map(tag("+inf"), |_| TextValue::Float(f64::INFINITY)))
.or(map(tag("-inf"), |_| TextValue::Float(f64::NEG_INFINITY)))
.parse(input)
.upgrade()
}
fn float_numeric_value(input: &str) -> IonParseResult<TextValue> {
let (remaining, text) = recognize(tuple((
alt((
floating_point_number,
recognize(pair(opt(tag("-")), digits_before_dot)),
)),
recognize(float_exponent_marker_followed_by_digits),
)))(input)?;
let mut sanitized = text.replace('_', "");
if sanitized.ends_with('e') || sanitized.ends_with('E') {
sanitized.push('0');
}
let float = f64::from_str(&sanitized)
.or_fatal_parse_error(input, "could not parse float as f64")?
.1;
Ok((remaining, TextValue::Float(float)))
}
fn float_exponent_marker_followed_by_digits(input: &str) -> IonParseResult<&str> {
preceded(one_of("eE"), exponent_digits)(input)
}
#[cfg(test)]
mod float_parsing_tests {
use crate::text::parsers::float::parse_float;
use crate::text::parsers::unit_test_support::{parse_test_err, parse_test_ok, parse_unwrap};
use crate::text::text_value::TextValue;
use std::str::FromStr;
fn parse_equals(text: &str, expected: f64) {
parse_test_ok(parse_float, text, TextValue::Float(expected))
}
fn parse_fails(text: &str) {
parse_test_err(parse_float, text)
}
#[test]
fn test_parse_float_special_values() {
parse_equals("+inf ", f64::INFINITY);
parse_equals("-inf ", f64::NEG_INFINITY);
let value = parse_unwrap(parse_float, "nan ");
if let TextValue::Float(f) = value {
assert!(f.is_nan());
} else {
panic!("Expected NaN, but got: {value:?}");
}
let value = parse_unwrap(parse_float, "-0e0 ");
if let TextValue::Float(f) = value {
assert!(f == 0.0f64);
assert!(f.is_sign_negative())
} else {
panic!("Expected -0e0, but got: {value:?}");
}
}
#[test]
fn test_parse_float_numeric_values() {
parse_equals("0.0e0 ", 0.0);
parse_equals("0E0 ", 0.0);
parse_equals("0e0 ", 0e0);
parse_equals("305e1 ", 3050.0);
parse_equals("305.0e1 ", 3050.0);
parse_equals("-0.279e3 ", -279.0);
parse_equals("-279e0 ", -279.0);
parse_equals("-279.5e0 ", -279.5);
parse_fails("305 ");
parse_fails("305e ");
parse_fails(".305e ");
parse_fails("305e0.5");
parse_fails("305e-0.5");
parse_fails(" 305e1 ");
parse_fails("0305e1 ");
parse_fails("+305e1 ");
parse_fails("--305e1 ");
parse_fails("305e1");
}
#[test]
fn test_parse_float_numeric_values_with_underscores() {
parse_equals("111_111e222 ", 111111.0 * 10f64.powf(222f64));
parse_equals("111_111.667e222 ", 111111.667 * 10f64.powf(222f64));
parse_equals("111_111e222_222 ", 111111.0 * 10f64.powf(222222f64));
parse_equals("-999_9e9_9 ", f64::from_str("-9999e99").unwrap());
parse_fails("_305e1 ");
parse_fails("305e1_ ");
parse_fails("30__5e1 ");
}
}