Lines
67.42 %
Functions
100 %
Branches
/*
* Hurl (https://hurl.dev)
* Copyright (C) 2024 Orange
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::ast::U64;
use crate::ast::{Float, Number, I64};
use crate::parser::primitives::try_literal;
use crate::parser::{ParseError, ParseErrorKind, ParseResult};
use crate::reader::Reader;
pub fn natural(reader: &mut Reader) -> ParseResult<U64> {
let start = reader.cursor();
let s = parse_natural_str(reader, "natural")?;
match s.parse() {
Ok(value) => {
let encoded = reader.read_from(start.index);
Ok(U64::new(value, encoded))
}
Err(_) => {
let kind = ParseErrorKind::Expecting {
value: String::from("natural"),
};
Err(ParseError::new(start.pos, false, kind))
pub fn integer(reader: &mut Reader) -> ParseResult<I64> {
let sign = match try_literal("-", reader) {
Err(_) => 1,
Ok(_) => -1,
let s = parse_natural_str(reader, "integer")?;
match s.to_string().parse::<i64>() {
let value = sign * value;
Ok(I64::new(value, encoded))
value: String::from("integer"),
/// Parses a natural string.
/// This is common code between natural and integer parsers.
fn parse_natural_str(reader: &mut Reader, expecting: &str) -> ParseResult<String> {
let mut save = reader.cursor();
if reader.is_eof() {
value: expecting.to_string(),
return Err(ParseError::new(save.pos, true, kind));
let first_digit = reader.read().unwrap();
if !first_digit.is_ascii_digit() {
save.pos = reader.cursor().pos;
let s = reader.read_while(|c| c.is_ascii_digit());
// if the first digit is zero, you should not have any more digits
if first_digit == '0' && !s.is_empty() {
return Err(ParseError::new(save.pos, false, kind));
Ok(format!("{first_digit}{s}"))
pub fn number(reader: &mut Reader) -> ParseResult<Number> {
Err(_) => "",
Ok(_) => "-",
let integer_digits = reader.read_while(|c| c.is_ascii_digit());
if integer_digits.is_empty() {
value: "number".to_string(),
return Err(ParseError::new(reader.cursor().pos, true, kind));
} else if integer_digits.len() > 1 && integer_digits.starts_with('0') {
let save = reader.cursor();
// Float
if try_literal(".", reader).is_ok() {
let decimal_digits = reader.read_while(|c| c.is_ascii_digit());
if decimal_digits.is_empty() {
value: String::from("decimal digits"),
match format!("{sign}{integer_digits}.{decimal_digits}").parse() {
Ok(Number::Float(Float { value, encoded }))
value: String::from("float"),
// Integer or BigInteger
} else {
match format!("{sign}{integer_digits}").parse::<i64>() {
let integer = I64::new(value, encoded);
Ok(Number::Integer(integer))
Err(_) => Ok(Number::BigInteger(integer_digits)),
#[cfg(test)]
mod tests {
use super::*;
use crate::reader::Pos;
#[test]
fn test_natural() {
let mut reader = Reader::new("0");
assert_eq!(natural(&mut reader).unwrap(), U64::new(0, "0".to_string()));
assert_eq!(reader.cursor().index, 1);
let mut reader = Reader::new("10x");
assert_eq!(
natural(&mut reader).unwrap(),
U64::new(10, "10".to_string())
);
assert_eq!(reader.cursor().index, 2);
fn test_natural_error() {
let mut reader = Reader::new("");
let error = natural(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
error.kind,
ParseErrorKind::Expecting {
value: String::from("natural")
assert!(error.recoverable);
let mut reader = Reader::new("01");
assert_eq!(error.pos, Pos { line: 1, column: 2 });
assert!(!error.recoverable);
let mut reader = Reader::new("x");
fn test_integer() {
assert_eq!(integer(&mut reader).unwrap(), I64::new(0, "0".to_string()));
let mut reader = Reader::new("-1");
integer(&mut reader).unwrap(),
I64::new(-1, "-1".to_string())
number(&mut reader).unwrap(),
Number::Integer(I64::new(0, "0".to_string()))
Number::Integer(I64::new(10, "10".to_string()))
let mut reader = Reader::new("-10x");
Number::Integer(I64::new(-10, "-10".to_string()))
assert_eq!(reader.cursor().index, 3);
fn test_float() {
let mut reader = Reader::new("1.0");
Number::Float(Float {
value: 1.0,
encoded: "1.0".to_string()
})
let mut reader = Reader::new("-1.0");
value: -1.0,
encoded: "-1.0".to_string()
assert_eq!(reader.cursor().index, 4);
let mut reader = Reader::new("1.1");
value: 1.1,
encoded: "1.1".to_string()
let mut reader = Reader::new("1.100");
encoded: "1.100".to_string()
assert_eq!(reader.cursor().index, 5);
let mut reader = Reader::new("1.01");
value: 1.01,
encoded: "1.01".to_string()
let mut reader = Reader::new("1.010");
encoded: "1.010".to_string()
// provide more digits than necessary
let mut reader = Reader::new("-0.3333333333333333333");
value: -0.3333333333333333,
encoded: "-0.3333333333333333333".to_string()
assert_eq!(reader.cursor().index, 22);
let mut reader = Reader::new("1000000000000000000000.5");
value: 1000000000000000000000.0,
encoded: "1000000000000000000000.5".to_string()
assert_eq!(reader.cursor().index, 24);
pub fn test_number_error() {
let error = number(&mut reader).err().unwrap();
value: String::from("number")
let mut reader = Reader::new("-");
let mut reader = Reader::new("1.");
assert_eq!(error.pos, Pos { line: 1, column: 3 });
value: String::from("decimal digits")