From 53fd824e8dedf48c6cd0375dd7bd504cf6a2b91f Mon Sep 17 00:00:00 2001 From: Araozu Date: Tue, 29 Nov 2022 20:13:48 -0500 Subject: [PATCH] Simple scientific notation scanning --- .gitignore | 1 + src/lexic/mod.rs | 2 - src/lexic/scanner/number.rs | 83 +++++++++++++++++++++++++++++++------ 3 files changed, 72 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 9026c77..d6155df 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target .vscode +*.profraw \ No newline at end of file diff --git a/src/lexic/mod.rs b/src/lexic/mod.rs index b8cd36a..71311ec 100644 --- a/src/lexic/mod.rs +++ b/src/lexic/mod.rs @@ -131,8 +131,6 @@ mod tests { assert_eq!(TokenType::Number, t3.token_type); assert_eq!("0.282398", t3.value); /* - assert_eq!("278.98", tokens.get(1).unwrap().value); - assert_eq!("0.282398", tokens.get(2).unwrap().value); assert_eq!("1798e+1", tokens.get(3).unwrap().value); assert_eq!("239.3298e-103", tokens.get(4).unwrap().value); assert_eq!(TokenType::EOF, tokens.get(5).unwrap().token_type); diff --git a/src/lexic/scanner/number.rs b/src/lexic/scanner/number.rs index d2974e2..53286d1 100644 --- a/src/lexic/scanner/number.rs +++ b/src/lexic/scanner/number.rs @@ -25,12 +25,12 @@ pub fn scan(chars: &Vec, start_pos: usize) -> Result<(Token, usize), Strin } -/// Recursively scans an integer, and if a dot `.` is found, scans a double. -/// -/// It may fail due to scanning a double. +/// Recursively scans an integer. If a dot `.` is found, scans a double, +/// if a `e` is found, scans a number in scientific notation fn scan_decimal(chars: &Vec, start_pos: usize, current: String) -> Result<(Token, usize), String> { let next_char = chars.get(start_pos); + // If a char is found if let Some(c) = next_char { let c = *c; @@ -39,12 +39,14 @@ fn scan_decimal(chars: &Vec, start_pos: usize, current: String) -> Result< if c == '.' { return scan_double(chars, start_pos + 1, utils::str_append(current, c)) } + // If a `e` is found scan a double with exponent + else if c == 'e' { + return scan_scientific(chars, start_pos + 1, utils::str_append(current, c)) + } // Scan a decimal number else if utils::is_digit(c) { - let new_value = format!("{}{}", current, c); - return scan_decimal(chars, start_pos + 1, new_value) + return scan_decimal(chars, start_pos + 1, utils::str_append(current, c)) } - } // Return the current value @@ -80,15 +82,16 @@ fn scan_hex(chars: &Vec, start_pos: usize, current: String) -> Result<(Tok /// Scans a floating point number /// -/// This function expects the following on the first call: -/// - The char at `start_pos` is a value between [0-9]. If not will return an error. +/// This function expects the following: /// - `start_pos` is the position after the dot. E.g., if the input is `3.22` then `start_pos == 2`. +/// +/// Returns a syntax error if the char at `start_pos` is not a value between [0-9] fn scan_double(chars: &Vec, start_pos: usize, current: String) -> Result<(Token, usize), String> { let next_char = chars.get(start_pos); // Check that the first characters exists and is a number if let Some(c) = next_char { - if utils::is_digit(*c) { Ok(scan_double_impl(chars, start_pos, current)) } + if utils::is_digit(*c) { Ok(scan_digits(chars, start_pos, current)) } else {Err(String::from("The character after the dot when scanning a double is not a number."))} } else { @@ -96,15 +99,38 @@ fn scan_double(chars: &Vec, start_pos: usize, current: String) -> Result<( } } -// Implementation of scan_double -fn scan_double_impl(chars: &Vec, start_pos: usize, current: String) -> (Token, usize) { + +/// Scans a double in scientific notation +/// +/// This function expects the following: +/// - `start_pos` is the position after the `e`. E.g., if the input is `3e+10` then `start_pos == 2` +/// +/// Returns a syntax error if: +/// - The char at `start_pos` is not `+` or `-` +/// - The char at `start_pos + 1` is not between [0-9] +fn scan_scientific(chars: &Vec, start_pos: usize, current: String) -> Result<(Token, usize), String> { + let next_char_1 = chars.get(start_pos); + let next_char_2 = chars.get(start_pos + 1); + + match (next_char_1, next_char_2) { + (Some(c1), Some(c2)) if (*c1 == '+' || *c1 == '-') && utils::is_digit(*c2) => { + let new_value = format!("{}{}{}", current, *c1, *c2); + Ok(scan_digits(chars, start_pos + 2, new_value)) + }, + _ => Err(String::from("The characters after 'e' are not + or -, or are not followed by a number")) + } +} + + +/// Scans chars between [0-9], returns when none is found +fn scan_digits(chars: &Vec, start_pos: usize, current: String) -> (Token, usize) { let next_char = chars.get(start_pos); if let Some(c) = next_char { let c = *c; if utils::is_digit(c) { - return scan_double_impl(chars, start_pos + 1, utils::str_append(current, c)) + return scan_digits(chars, start_pos + 1, utils::str_append(current, c)) } } @@ -255,4 +281,37 @@ mod tests { Err(reason) => assert_eq!("EOF when scanning a double number.", reason) } } + + // Should scan a double without decimal part, with exponent + #[test] + fn test_exp_1() { + let input = str_to_vec("1e+0"); + let start_pos = 0; + let (token, next) = scan(&input, start_pos).unwrap(); + assert_eq!("1e+0", token.value); + assert_eq!(4, next); + assert_eq!(TokenType::Number, token.token_type); + + let input = str_to_vec("1e-0"); + let start_pos = 0; + let (token, next) = scan(&input, start_pos).unwrap(); + assert_eq!(4, next); + assert_eq!(TokenType::Number, token.token_type); + assert_eq!("1e-0", token.value); + + + let input = str_to_vec("0e+0"); + let start_pos = 0; + let (token, next) = scan(&input, start_pos).unwrap(); + assert_eq!(4, next); + assert_eq!(TokenType::Number, token.token_type); + assert_eq!("0e+0", token.value); + + let input = str_to_vec("123498790e+12349870"); + let start_pos = 0; + let (token, next) = scan(&input, start_pos).unwrap(); + assert_eq!(19, next); + assert_eq!(TokenType::Number, token.token_type); + assert_eq!("123498790e+12349870", token.value); + } }