1
/*
2
 * Hurl (https://hurl.dev)
3
 * Copyright (C) 2026 Orange
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 *          http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 */
18
use crate::ast::{Float, I64, Number, U64};
19
use crate::reader::Reader;
20
use crate::types::ToSource;
21

            
22
use super::ParseResult;
23
use super::error::{ParseError, ParseErrorKind};
24
use super::primitives::try_literal;
25

            
26
/// Parses a string to a `U64`.
27
716
pub fn natural(reader: &mut Reader) -> ParseResult<U64> {
28
716
    let start = reader.cursor();
29
716
    let s = parse_natural_str(reader, "natural")?;
30
632
    match s.parse() {
31
632
        Ok(value) => {
32
632
            let source = reader.read_from(start.index).to_source();
33
632
            Ok(U64::new(value, source))
34
        }
35
        Err(_) => {
36
            let kind = ParseErrorKind::Expecting {
37
                value: String::from("natural"),
38
            };
39
            Err(ParseError::new(start.pos, false, kind))
40
        }
41
    }
42
}
43

            
44
1420
pub fn integer(reader: &mut Reader) -> ParseResult<I64> {
45
1420
    let start = reader.cursor();
46
1420
    let sign = match try_literal("-", reader) {
47
1344
        Err(_) => 1,
48
76
        Ok(_) => -1,
49
    };
50
1420
    let s = parse_natural_str(reader, "integer")?;
51
1296
    match s.to_string().parse::<i64>() {
52
1296
        Ok(value) => {
53
1296
            let value = sign * value;
54
1296
            let source = reader.read_from(start.index).to_source();
55
1296
            Ok(I64::new(value, source))
56
        }
57
        Err(_) => {
58
            let kind = ParseErrorKind::Expecting {
59
                value: String::from("integer"),
60
            };
61
            Err(ParseError::new(start.pos, false, kind))
62
        }
63
    }
64
}
65

            
66
/// Parses a natural string.
67
/// This is common code between natural and integer parsers.
68
2136
fn parse_natural_str(reader: &mut Reader, expecting: &str) -> ParseResult<String> {
69
2136
    let mut save = reader.cursor();
70
2136
    if reader.is_eof() {
71
        let kind = ParseErrorKind::Expecting {
72
            value: expecting.to_string(),
73
        };
74
        return Err(ParseError::new(save.pos, true, kind));
75
    }
76
2136
    let first_digit = reader.read().unwrap();
77
2136
    if !first_digit.is_ascii_digit() {
78
208
        let kind = ParseErrorKind::Expecting {
79
208
            value: expecting.to_string(),
80
208
        };
81
208
        return Err(ParseError::new(save.pos, true, kind));
82
    }
83
1928
    save.pos = reader.cursor().pos;
84
3164
    let s = reader.read_while(|c| c.is_ascii_digit());
85
    // if the first digit is zero, you should not have any more digits
86
1928
    if first_digit == '0' && !s.is_empty() {
87
        let kind = ParseErrorKind::Expecting {
88
            value: expecting.to_string(),
89
        };
90
        return Err(ParseError::new(save.pos, false, kind));
91
    }
92
1928
    Ok(format!("{first_digit}{s}"))
93
}
94

            
95
18524
pub fn number(reader: &mut Reader) -> ParseResult<Number> {
96
18524
    let start = reader.cursor();
97
18524
    let sign = match try_literal("-", reader) {
98
18492
        Err(_) => "",
99
32
        Ok(_) => "-",
100
    };
101
28084
    let integer_digits = reader.read_while(|c| c.is_ascii_digit());
102
18524
    if integer_digits.is_empty() {
103
13268
        let kind = ParseErrorKind::Expecting {
104
13268
            value: "number".to_string(),
105
13268
        };
106
13268
        return Err(ParseError::new(reader.cursor().pos, true, kind));
107

            
108
        // if the first digit is zero, you should not have any more digits
109
5256
    } else if integer_digits.len() > 1 && integer_digits.starts_with('0') {
110
        let save = reader.cursor();
111
        let kind = ParseErrorKind::Expecting {
112
            value: String::from("natural"),
113
        };
114
        return Err(ParseError::new(save.pos, false, kind));
115
    }
116

            
117
    // Float
118
5256
    if try_literal(".", reader).is_ok() {
119
792
        let save = reader.cursor();
120
5532
        let decimal_digits = reader.read_while(|c| c.is_ascii_digit());
121
792
        if decimal_digits.is_empty() {
122
            let kind = ParseErrorKind::Expecting {
123
                value: String::from("decimal digits"),
124
            };
125
            return Err(ParseError::new(save.pos, false, kind));
126
        }
127
792
        match format!("{sign}{integer_digits}.{decimal_digits}").parse() {
128
792
            Ok(value) => {
129
792
                let source = reader.read_from(start.index).to_source();
130
792
                Ok(Number::Float(Float::new(value, source)))
131
            }
132
            Err(_) => {
133
                let kind = ParseErrorKind::Expecting {
134
                    value: String::from("float"),
135
                };
136
                Err(ParseError::new(start.pos, false, kind))
137
            }
138
        }
139

            
140
    // Integer or BigInteger
141
    } else {
142
4464
        match format!("{sign}{integer_digits}").parse::<i64>() {
143
4444
            Ok(value) => {
144
4444
                let source = reader.read_from(start.index).to_source();
145
4444
                let integer = I64::new(value, source);
146
4444
                Ok(Number::Integer(integer))
147
            }
148
20
            Err(_) => Ok(Number::BigInteger(integer_digits)),
149
        }
150
    }
151
}
152

            
153
#[cfg(test)]
154
mod tests {
155
    use super::*;
156
    use crate::reader::{CharPos, Pos};
157

            
158
    #[test]
159
    fn test_natural() {
160
        let mut reader = Reader::new("0");
161
        assert_eq!(natural(&mut reader).unwrap(), U64::new(0, "0".to_source()));
162
        assert_eq!(reader.cursor().index, CharPos(1));
163

            
164
        let mut reader = Reader::new("10x");
165
        assert_eq!(
166
            natural(&mut reader).unwrap(),
167
            U64::new(10, "10".to_source())
168
        );
169
        assert_eq!(reader.cursor().index, CharPos(2));
170
    }
171

            
172
    #[test]
173
    fn test_natural_error() {
174
        let mut reader = Reader::new("");
175
        let error = natural(&mut reader).err().unwrap();
176
        assert_eq!(error.pos, Pos { line: 1, column: 1 });
177
        assert_eq!(
178
            error.kind,
179
            ParseErrorKind::Expecting {
180
                value: String::from("natural")
181
            }
182
        );
183
        assert!(error.recoverable);
184

            
185
        let mut reader = Reader::new("01");
186
        let error = natural(&mut reader).err().unwrap();
187
        assert_eq!(error.pos, Pos { line: 1, column: 2 });
188
        assert_eq!(
189
            error.kind,
190
            ParseErrorKind::Expecting {
191
                value: String::from("natural")
192
            }
193
        );
194
        assert!(!error.recoverable);
195

            
196
        let mut reader = Reader::new("x");
197
        let error = natural(&mut reader).err().unwrap();
198
        assert_eq!(error.pos, Pos { line: 1, column: 1 });
199
        assert_eq!(
200
            error.kind,
201
            ParseErrorKind::Expecting {
202
                value: String::from("natural")
203
            }
204
        );
205
        assert!(error.recoverable);
206
    }
207

            
208
    #[test]
209
    fn test_integer() {
210
        let mut reader = Reader::new("0");
211
        assert_eq!(integer(&mut reader).unwrap(), I64::new(0, "0".to_source()));
212
        assert_eq!(reader.cursor().index, CharPos(1));
213

            
214
        let mut reader = Reader::new("-1");
215
        assert_eq!(
216
            integer(&mut reader).unwrap(),
217
            I64::new(-1, "-1".to_source())
218
        );
219
        assert_eq!(reader.cursor().index, CharPos(2));
220

            
221
        let mut reader = Reader::new("0");
222
        assert_eq!(
223
            number(&mut reader).unwrap(),
224
            Number::Integer(I64::new(0, "0".to_source()))
225
        );
226
        assert_eq!(reader.cursor().index, CharPos(1));
227

            
228
        let mut reader = Reader::new("10x");
229
        assert_eq!(
230
            number(&mut reader).unwrap(),
231
            Number::Integer(I64::new(10, "10".to_source()))
232
        );
233
        assert_eq!(reader.cursor().index, CharPos(2));
234

            
235
        let mut reader = Reader::new("-10x");
236
        assert_eq!(
237
            number(&mut reader).unwrap(),
238
            Number::Integer(I64::new(-10, "-10".to_source()))
239
        );
240
        assert_eq!(reader.cursor().index, CharPos(3));
241
    }
242

            
243
    #[test]
244
    fn test_float() {
245
        let mut reader = Reader::new("1.0");
246
        assert_eq!(
247
            number(&mut reader).unwrap(),
248
            Number::Float(Float::new(1.0, "1.0".to_source())),
249
        );
250
        assert_eq!(reader.cursor().index, CharPos(3));
251

            
252
        let mut reader = Reader::new("-1.0");
253
        assert_eq!(
254
            number(&mut reader).unwrap(),
255
            Number::Float(Float::new(-1.0, "-1.0".to_source())),
256
        );
257
        assert_eq!(reader.cursor().index, CharPos(4));
258

            
259
        let mut reader = Reader::new("1.1");
260
        assert_eq!(
261
            number(&mut reader).unwrap(),
262
            Number::Float(Float::new(1.1, "1.1".to_source())),
263
        );
264
        assert_eq!(reader.cursor().index, CharPos(3));
265

            
266
        let mut reader = Reader::new("1.100");
267
        assert_eq!(
268
            number(&mut reader).unwrap(),
269
            Number::Float(Float::new(1.1, "1.100".to_source())),
270
        );
271
        assert_eq!(reader.cursor().index, CharPos(5));
272

            
273
        let mut reader = Reader::new("1.01");
274
        assert_eq!(
275
            number(&mut reader).unwrap(),
276
            Number::Float(Float::new(1.01, "1.01".to_source())),
277
        );
278
        assert_eq!(reader.cursor().index, CharPos(4));
279

            
280
        let mut reader = Reader::new("1.010");
281
        assert_eq!(
282
            number(&mut reader).unwrap(),
283
            Number::Float(Float::new(1.01, "1.010".to_source()))
284
        );
285
        assert_eq!(reader.cursor().index, CharPos(5));
286

            
287
        // provide more digits than necessary
288
        let mut reader = Reader::new("-0.3333333333333333333");
289
        assert_eq!(
290
            number(&mut reader).unwrap(),
291
            Number::Float(Float::new(
292
                -0.3333333333333333,
293
                "-0.3333333333333333333".to_source()
294
            ))
295
        );
296
        assert_eq!(reader.cursor().index, CharPos(22));
297

            
298
        let mut reader = Reader::new("1000000000000000000000.5");
299
        assert_eq!(
300
            number(&mut reader).unwrap(),
301
            Number::Float(Float::new(
302
                1000000000000000000000.0,
303
                "1000000000000000000000.5".to_source()
304
            ))
305
        );
306
        assert_eq!(reader.cursor().index, CharPos(24));
307
    }
308

            
309
    #[test]
310
    pub fn test_number_error() {
311
        let mut reader = Reader::new("");
312
        let error = number(&mut reader).err().unwrap();
313
        assert_eq!(
314
            error.kind,
315
            ParseErrorKind::Expecting {
316
                value: String::from("number")
317
            }
318
        );
319
        assert_eq!(error.pos, Pos { line: 1, column: 1 });
320
        assert!(error.recoverable);
321

            
322
        let mut reader = Reader::new("-");
323
        let error = number(&mut reader).err().unwrap();
324
        assert_eq!(
325
            error.kind,
326
            ParseErrorKind::Expecting {
327
                value: String::from("number")
328
            }
329
        );
330
        assert_eq!(error.pos, Pos { line: 1, column: 2 });
331
        assert!(error.recoverable);
332

            
333
        let mut reader = Reader::new("x");
334
        let error = number(&mut reader).err().unwrap();
335
        assert_eq!(error.pos, Pos { line: 1, column: 1 });
336
        assert_eq!(
337
            error.kind,
338
            ParseErrorKind::Expecting {
339
                value: String::from("number")
340
            }
341
        );
342
        assert!(error.recoverable);
343

            
344
        let mut reader = Reader::new("1.");
345
        let error = number(&mut reader).err().unwrap();
346
        assert_eq!(error.pos, Pos { line: 1, column: 3 });
347
        assert_eq!(
348
            error.kind,
349
            ParseErrorKind::Expecting {
350
                value: String::from("decimal digits")
351
            }
352
        );
353
        assert!(!error.recoverable);
354
    }
355
}