toml_edit/parser/
numbers.rs

1use std::ops::RangeInclusive;
2
3use nom8::branch::alt;
4use nom8::bytes::one_of;
5use nom8::bytes::tag;
6use nom8::bytes::take;
7use nom8::combinator::cut;
8use nom8::combinator::opt;
9use nom8::combinator::peek;
10use nom8::combinator::rest;
11use nom8::multi::many0_count;
12use nom8::sequence::preceded;
13
14use crate::parser::prelude::*;
15use crate::parser::trivia::from_utf8_unchecked;
16
17// ;; Boolean
18
19// boolean = true / false
20#[allow(dead_code)] // directly define in `fn value`
21pub(crate) fn boolean(input: Input<'_>) -> IResult<Input<'_>, bool, ParserError<'_>> {
22    alt((true_, false_)).parse(input)
23}
24
25pub(crate) fn true_(input: Input<'_>) -> IResult<Input<'_>, bool, ParserError<'_>> {
26    (peek(TRUE[0]), cut(TRUE)).value(true).parse(input)
27}
28const TRUE: &[u8] = b"true";
29
30pub(crate) fn false_(input: Input<'_>) -> IResult<Input<'_>, bool, ParserError<'_>> {
31    (peek(FALSE[0]), cut(FALSE)).value(false).parse(input)
32}
33const FALSE: &[u8] = b"false";
34
35// ;; Integer
36
37// integer = dec-int / hex-int / oct-int / bin-int
38pub(crate) fn integer(input: Input<'_>) -> IResult<Input<'_>, i64, ParserError<'_>> {
39    dispatch! {peek(opt::<_, &[u8], _, _>(take(2usize)));
40        Some(b"0x") => cut(hex_int.map_res(|s| i64::from_str_radix(&s.replace('_', ""), 16))),
41        Some(b"0o") => cut(oct_int.map_res(|s| i64::from_str_radix(&s.replace('_', ""), 8))),
42        Some(b"0b") => cut(bin_int.map_res(|s| i64::from_str_radix(&s.replace('_', ""), 2))),
43        _ => dec_int.and_then(cut(rest
44            .map_res(|s: &str| s.replace('_', "").parse())))
45    }
46    .parse(input)
47}
48
49// dec-int = [ minus / plus ] unsigned-dec-int
50// unsigned-dec-int = DIGIT / digit1-9 1*( DIGIT / underscore DIGIT )
51pub(crate) fn dec_int(input: Input<'_>) -> IResult<Input<'_>, &str, ParserError<'_>> {
52    (
53        opt(one_of((b'+', b'-'))),
54        alt((
55            (
56                one_of(DIGIT1_9),
57                many0_count(alt((
58                    digit.value(()),
59                    (
60                        one_of(b'_'),
61                        cut(digit).context(Context::Expected(ParserValue::Description("digit"))),
62                    )
63                        .value(()),
64                ))),
65            )
66                .value(()),
67            digit.value(()),
68        )),
69    )
70        .recognize()
71        .map(|b: &[u8]| unsafe { from_utf8_unchecked(b, "`digit` and `_` filter out non-ASCII") })
72        .context(Context::Expression("integer"))
73        .parse(input)
74}
75const DIGIT1_9: RangeInclusive<u8> = b'1'..=b'9';
76
77// hex-prefix = %x30.78               ; 0x
78// hex-int = hex-prefix HEXDIG *( HEXDIG / underscore HEXDIG )
79pub(crate) fn hex_int(input: Input<'_>) -> IResult<Input<'_>, &str, ParserError<'_>> {
80    preceded(
81        HEX_PREFIX,
82        cut((
83            hexdig,
84            many0_count(alt((
85                hexdig.value(()),
86                (
87                    one_of(b'_'),
88                    cut(hexdig).context(Context::Expected(ParserValue::Description("digit"))),
89                )
90                    .value(()),
91            ))),
92        ))
93        .recognize(),
94    )
95    .map(|b| unsafe { from_utf8_unchecked(b, "`hexdig` and `_` filter out non-ASCII") })
96    .context(Context::Expression("hexadecimal integer"))
97    .parse(input)
98}
99const HEX_PREFIX: &[u8] = b"0x";
100
101// oct-prefix = %x30.6F               ; 0o
102// oct-int = oct-prefix digit0-7 *( digit0-7 / underscore digit0-7 )
103pub(crate) fn oct_int(input: Input<'_>) -> IResult<Input<'_>, &str, ParserError<'_>> {
104    preceded(
105        OCT_PREFIX,
106        cut((
107            one_of(DIGIT0_7),
108            many0_count(alt((
109                one_of(DIGIT0_7).value(()),
110                (
111                    one_of(b'_'),
112                    cut(one_of(DIGIT0_7))
113                        .context(Context::Expected(ParserValue::Description("digit"))),
114                )
115                    .value(()),
116            ))),
117        ))
118        .recognize(),
119    )
120    .map(|b| unsafe { from_utf8_unchecked(b, "`DIGIT0_7` and `_` filter out non-ASCII") })
121    .context(Context::Expression("octal integer"))
122    .parse(input)
123}
124const OCT_PREFIX: &[u8] = b"0o";
125const DIGIT0_7: RangeInclusive<u8> = b'0'..=b'7';
126
127// bin-prefix = %x30.62               ; 0b
128// bin-int = bin-prefix digit0-1 *( digit0-1 / underscore digit0-1 )
129pub(crate) fn bin_int(input: Input<'_>) -> IResult<Input<'_>, &str, ParserError<'_>> {
130    preceded(
131        BIN_PREFIX,
132        cut((
133            one_of(DIGIT0_1),
134            many0_count(alt((
135                one_of(DIGIT0_1).value(()),
136                (
137                    one_of(b'_'),
138                    cut(one_of(DIGIT0_1))
139                        .context(Context::Expected(ParserValue::Description("digit"))),
140                )
141                    .value(()),
142            ))),
143        ))
144        .recognize(),
145    )
146    .map(|b| unsafe { from_utf8_unchecked(b, "`DIGIT0_1` and `_` filter out non-ASCII") })
147    .context(Context::Expression("binary integer"))
148    .parse(input)
149}
150const BIN_PREFIX: &[u8] = b"0b";
151const DIGIT0_1: RangeInclusive<u8> = b'0'..=b'1';
152
153// ;; Float
154
155// float = float-int-part ( exp / frac [ exp ] )
156// float =/ special-float
157// float-int-part = dec-int
158pub(crate) fn float(input: Input<'_>) -> IResult<Input<'_>, f64, ParserError<'_>> {
159    alt((
160        float_.and_then(cut(rest
161            .map_res(|s: &str| s.replace('_', "").parse())
162            .verify(|f: &f64| *f != f64::INFINITY))),
163        special_float,
164    ))
165    .context(Context::Expression("floating-point number"))
166    .parse(input)
167}
168
169pub(crate) fn float_(input: Input<'_>) -> IResult<Input<'_>, &str, ParserError<'_>> {
170    (dec_int, alt((exp, (frac, opt(exp)).map(|_| ""))))
171        .recognize()
172        .map(|b: &[u8]| unsafe {
173            from_utf8_unchecked(
174                b,
175                "`dec_int`, `one_of`, `exp`, and `frac` filter out non-ASCII",
176            )
177        })
178        .parse(input)
179}
180
181// frac = decimal-point zero-prefixable-int
182// decimal-point = %x2E               ; .
183pub(crate) fn frac(input: Input<'_>) -> IResult<Input<'_>, &str, ParserError<'_>> {
184    (
185        b'.',
186        cut(zero_prefixable_int).context(Context::Expected(ParserValue::Description("digit"))),
187    )
188        .recognize()
189        .map(|b: &[u8]| unsafe {
190            from_utf8_unchecked(
191                b,
192                "`.` and `parse_zero_prefixable_int` filter out non-ASCII",
193            )
194        })
195        .parse(input)
196}
197
198// zero-prefixable-int = DIGIT *( DIGIT / underscore DIGIT )
199pub(crate) fn zero_prefixable_int(input: Input<'_>) -> IResult<Input<'_>, &str, ParserError<'_>> {
200    (
201        digit,
202        many0_count(alt((
203            digit.value(()),
204            (
205                one_of(b'_'),
206                cut(digit).context(Context::Expected(ParserValue::Description("digit"))),
207            )
208                .value(()),
209        ))),
210    )
211        .recognize()
212        .map(|b: &[u8]| unsafe { from_utf8_unchecked(b, "`digit` and `_` filter out non-ASCII") })
213        .parse(input)
214}
215
216// exp = "e" float-exp-part
217// float-exp-part = [ minus / plus ] zero-prefixable-int
218pub(crate) fn exp(input: Input<'_>) -> IResult<Input<'_>, &str, ParserError<'_>> {
219    (
220        one_of((b'e', b'E')),
221        opt(one_of([b'+', b'-'])),
222        cut(zero_prefixable_int),
223    )
224        .recognize()
225        .map(|b: &[u8]| unsafe {
226            from_utf8_unchecked(
227                b,
228                "`one_of` and `parse_zero_prefixable_int` filter out non-ASCII",
229            )
230        })
231        .parse(input)
232}
233
234// special-float = [ minus / plus ] ( inf / nan )
235pub(crate) fn special_float(input: Input<'_>) -> IResult<Input<'_>, f64, ParserError<'_>> {
236    (opt(one_of((b'+', b'-'))), alt((inf, nan)))
237        .map(|(s, f)| match s {
238            Some(b'+') | None => f,
239            Some(b'-') => -f,
240            _ => unreachable!("one_of should prevent this"),
241        })
242        .parse(input)
243}
244// inf = %x69.6e.66  ; inf
245pub(crate) fn inf(input: Input<'_>) -> IResult<Input<'_>, f64, ParserError<'_>> {
246    tag(INF).value(f64::INFINITY).parse(input)
247}
248const INF: &[u8] = b"inf";
249// nan = %x6e.61.6e  ; nan
250pub(crate) fn nan(input: Input<'_>) -> IResult<Input<'_>, f64, ParserError<'_>> {
251    tag(NAN).value(f64::NAN).parse(input)
252}
253const NAN: &[u8] = b"nan";
254
255// DIGIT = %x30-39 ; 0-9
256pub(crate) fn digit(input: Input<'_>) -> IResult<Input<'_>, u8, ParserError<'_>> {
257    one_of(DIGIT).parse(input)
258}
259const DIGIT: RangeInclusive<u8> = b'0'..=b'9';
260
261// HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
262pub(crate) fn hexdig(input: Input<'_>) -> IResult<Input<'_>, u8, ParserError<'_>> {
263    one_of(HEXDIG).parse(input)
264}
265pub(crate) const HEXDIG: (RangeInclusive<u8>, RangeInclusive<u8>, RangeInclusive<u8>) =
266    (DIGIT, b'A'..=b'F', b'a'..=b'f');
267
268#[cfg(test)]
269mod test {
270    use super::*;
271
272    #[test]
273    fn integers() {
274        let cases = [
275            ("+99", 99),
276            ("42", 42),
277            ("0", 0),
278            ("-17", -17),
279            ("1_000", 1_000),
280            ("5_349_221", 5_349_221),
281            ("1_2_3_4_5", 1_2_3_4_5),
282            ("0xF", 15),
283            ("0o0_755", 493),
284            ("0b1_0_1", 5),
285            (&std::i64::MIN.to_string()[..], std::i64::MIN),
286            (&std::i64::MAX.to_string()[..], std::i64::MAX),
287        ];
288        for &(input, expected) in &cases {
289            dbg!(input);
290            let parsed = integer.parse(new_input(input)).finish();
291            assert_eq!(parsed, Ok(expected), "Parsing {input:?}");
292        }
293
294        let overflow = "1000000000000000000000000000000000";
295        let parsed = integer.parse(new_input(overflow)).finish();
296        assert!(parsed.is_err());
297    }
298
299    #[track_caller]
300    fn assert_float_eq(actual: f64, expected: f64) {
301        if expected.is_nan() {
302            assert!(actual.is_nan());
303        } else if expected.is_infinite() {
304            assert!(actual.is_infinite());
305            assert_eq!(expected.is_sign_positive(), actual.is_sign_positive());
306        } else {
307            dbg!(expected);
308            dbg!(actual);
309            assert!((expected - actual).abs() < std::f64::EPSILON);
310        }
311    }
312
313    #[test]
314    fn floats() {
315        let cases = [
316            ("+1.0", 1.0),
317            ("3.1419", 3.1419),
318            ("-0.01", -0.01),
319            ("5e+22", 5e+22),
320            ("1e6", 1e6),
321            ("-2E-2", -2E-2),
322            ("6.626e-34", 6.626e-34),
323            ("9_224_617.445_991_228_313", 9_224_617.445_991_227),
324            ("-1.7976931348623157e+308", std::f64::MIN),
325            ("1.7976931348623157e+308", std::f64::MAX),
326            ("nan", f64::NAN),
327            ("+nan", f64::NAN),
328            ("-nan", f64::NAN),
329            ("inf", f64::INFINITY),
330            ("+inf", f64::INFINITY),
331            ("-inf", f64::NEG_INFINITY),
332            // ("1e+400", std::f64::INFINITY),
333        ];
334        for &(input, expected) in &cases {
335            dbg!(input);
336            let parsed = float.parse(new_input(input)).finish().unwrap();
337            assert_float_eq(parsed, expected);
338
339            let overflow = "9e99999";
340            let parsed = float.parse(new_input(overflow)).finish();
341            assert!(parsed.is_err(), "{:?}", parsed);
342        }
343    }
344}