aimx/literals/number.rs
1//! Number parsing for the AIM expression grammar.
2//!
3//! This module provides parsers for numeric literals in the AIM expression language,
4//! supporting integers, floating-point numbers, and scientific notation. It handles
5//! common number formats with flexible syntax to accommodate various user input styles.
6//!
7//! # Supported Number Formats
8//!
9//! - **Integers**: `123`, `-456`, `0`
10//! - **Floats**: `12.34`, `-56.78`, `.5`, `-.5`, `123.`
11//! - **Scientific notation**: `1e10`, `2.5e-3`, `-1.2E+4`, `5E2`
12//! - **Special values**: `NaN`, `Inf`, `+Inf`, `-Inf`
13//!
14//! # Examples
15//!
16//! ```text
17//! 42 // integer
18//! -3.14 // float
19//! .5 // float without leading zero
20//! 1e6 // scientific notation
21//! -2.5E-3 // scientific notation with negative exponent
22//! NaN // not a number
23//! Inf // positive infinity
24//! -Inf // negative infinity
25//! ```
26//!
27//! # Related Modules
28//!
29//! - [`crate::literals`] - Parent module for all literal parsers
30//! - [`crate::literal`] - Main literal parsing interface
31
32use std::f64::{NAN, INFINITY, NEG_INFINITY};
33
34use nom::{
35 IResult,
36 Parser,
37 branch::alt,
38 bytes::complete::tag,
39 character::complete::{char, digit1, one_of},
40 combinator::{map, map_res, opt, recognize},
41};
42
43/// Parse a number literal from a string into an f64 value.
44///
45/// This function parses numeric literals according to the AIM expression grammar,
46/// supporting integers, floating-point numbers, and scientific notation with
47/// flexible syntax. It also handles special floating-point values.
48///
49/// # Supported Formats
50/// - **Integers**: `123`, `-456`
51/// - **Floats**: `12.34`, `-56.78`, `.5`, `-.5`, `123.`
52/// - **Scientific notation**: `1e10`, `2.5e-3`, `-1.2E+4`, `5E2`
53/// - **Special values**: `NaN`, `Inf`, `+Inf`, `-Inf`
54///
55/// # Arguments
56///
57/// * `input` - A string slice containing the number to parse
58///
59/// # Returns
60///
61/// * `IResult<&str, f64>` - A nom result containing the remaining input and parsed f64 value
62///
63/// # Examples
64///
65/// ```rust
66/// use aimx::literals::number::parse_number;
67///
68/// assert_eq!(parse_number("123"), Ok(("", 123.0)));
69/// assert_eq!(parse_number("-45.67"), Ok(("", -45.67)));
70/// assert_eq!(parse_number("1e10"), Ok(("", 1e10)));
71/// assert_eq!(parse_number(".5"), Ok(("", 0.5)));
72/// assert!(parse_number("NaN").unwrap().1.is_nan());
73/// assert_eq!(parse_number("Inf").unwrap().1, f64::INFINITY);
74/// assert_eq!(parse_number("-Inf").unwrap().1, f64::NEG_INFINITY);
75/// ```
76pub fn parse_number(input: &str) -> IResult<&str, f64> {
77 // First try to parse special float values
78 if let Ok(result) = alt((
79 map(tag::<_, _, nom::error::Error<&str>>("NaN"), |_| NAN),
80 map(tag::<_, _, nom::error::Error<&str>>("Inf"), |_| INFINITY),
81 map(tag::<_, _, nom::error::Error<&str>>("+Inf"), |_| INFINITY),
82 map(tag::<_, _, nom::error::Error<&str>>("-Inf"), |_| NEG_INFINITY)
83 )).parse(input) {
84 return Ok(result);
85 }
86
87 // If special values don't match, try regular numbers
88 map_res(
89 alt((
90 parse_scientific_notation,
91 parse_float,
92 parse_integer,
93 )),
94 |s: &str| s.parse::<f64>()
95 ).parse(input)
96}
97
98/// Parse an unsigned 32-bit integer from a string.
99///
100/// This function parses a sequence of digits into a u32 value. It's primarily
101/// used for parsing array indices and other non-negative integer values in
102/// the expression grammar.
103///
104/// # Arguments
105///
106/// * `input` - A string slice containing digits to parse
107///
108/// # Returns
109///
110/// * `IResult<&str, u32>` - A nom result containing the remaining input and parsed u32 value
111///
112/// # Examples
113///
114/// ```rust
115/// use aimx::literals::number::parse_unsigned;
116///
117/// assert_eq!(parse_unsigned("123"), Ok(("", 123)));
118/// assert_eq!(parse_unsigned("0"), Ok(("", 0)));
119/// ```
120pub fn parse_unsigned(input: &str) -> IResult<&str, u32> {
121 map_res(digit1, |s: &str| s.parse::<u32>()).parse(input)
122}
123
124/// Parse scientific notation numbers.
125///
126/// Helper function to parse numbers in scientific notation format (e.g., `1e10`, `2.5e-3`).
127/// Used internally by [`parse_number`].
128fn parse_scientific_notation(input: &str) -> IResult<&str, &str> {
129 recognize((
130 alt((parse_float, parse_integer)),
131 one_of("eE"),
132 opt(one_of("+-")),
133 digit1,
134 )).parse(input)
135}
136
137/// Parse floating-point numbers.
138///
139/// Helper function to parse floating-point numbers in various formats. Used internally by
140/// [`parse_scientific_notation`] and [`parse_number`].
141fn parse_float(input: &str) -> IResult<&str, &str> {
142 recognize((
143 opt(char('-')),
144 alt((
145 // Pattern: digits.digits (e.g., 123.456)
146 recognize((digit1, char('.'), digit1)),
147 // Pattern: .digits (e.g., .5)
148 recognize((char('.'), digit1)),
149 // Pattern: digits. (e.g., 123.)
150 recognize((digit1, char('.'))),
151 )),
152 )).parse(input)
153}
154
155/// Parse integer numbers.
156///
157/// Helper function to parse integer numbers with optional negative sign. Used internally by
158/// [`parse_scientific_notation`], [`parse_float`], and [`parse_number`].
159fn parse_integer(input: &str) -> IResult<&str, &str> {
160 recognize((
161 opt(char('-')),
162 digit1,
163 )).parse(input)
164}