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}