diff --git a/CHANGELOG.md b/CHANGELOG.md index 79c20e4..a8085c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,6 @@ - [ ] Stdlib - [ ] Document code - ## v0.0.4 - Explicit datatype of variables @@ -27,6 +26,11 @@ - Compilation of `val` and `var` bindings with a number, string or boolean as value. - Register symbols and datatypes in the Symbol table. +- Add better error messages for lexical errors. Show: + - Offending line + - Pointer to offending character + - Error message + ## v0.0.1 diff --git a/src/error_handling/lex_error.rs b/src/error_handling/lex_error.rs index 803f3b0..d3352f3 100644 --- a/src/error_handling/lex_error.rs +++ b/src/error_handling/lex_error.rs @@ -1,29 +1,47 @@ -use std::collections::VecDeque; +use std::{collections::VecDeque, fmt::format}; use super::{PrintableError, LexError}; impl PrintableError for LexError { + // TODO: Count and show line number fn get_error_str(&self, chars: &Vec) -> String { - String::from("D:") + + let (erroneous_code, back_count) = get_line(chars, self.position); + + let mut whitespace = Vec::::new(); + for i in 0..back_count { + whitespace.push(' '); + } + let whitespace = whitespace.iter().collect::(); + + format!( + "\n{}\n{}^\n\n{}{}", + erroneous_code, + whitespace, + "Invalid character at pos ", + self.position + 1, + ) } } -/// Extracts a line of code from `chars`. `pos` indicates a position, -/// from where to extract the line. +/// Extracts a line of code from `chars` and the number of characters in the back. +/// `pos` indicates a position, from where to extract the line. /// /// Ex. Given: /// - `input = "first line\nsecond line\nthird line"` /// - `pos = 15` /// -/// this function should return `"second line"` -fn get_line(chars: &Vec, pos: usize) -> String { +/// this function should return `("second line", 4)` +fn get_line(chars: &Vec, pos: usize) -> (String, usize) { let mut result_chars = VecDeque::::new(); // Push chars to the front until a new line is found + // TODO: refactor let mut before_pos = pos; loop { let current_char = chars[before_pos]; if current_char == '\n' { + before_pos += 1; break; } @@ -50,7 +68,7 @@ fn get_line(chars: &Vec, pos: usize) -> String { after_pos += 1; } - result_chars.iter().collect::() + (result_chars.iter().collect::(), pos - before_pos) } @@ -61,7 +79,7 @@ mod tests { use super::*; #[test] - fn test() { + fn test_error_msg() { let input = String::from("val name' = 20"); let result = lexic::get_tokens(&input); @@ -70,12 +88,16 @@ mod tests { Err(err_data) => { let chars: Vec = input.chars().into_iter().collect(); let err_str = err_data.get_error_str(&chars); - assert_ne!( - "\n\ - val name' = 20\n\ - . ^\n\ - \n\ - Invalid character at line 1, pos 9", + + // TODO: check for line number + let expected_str = format!( + "\n{}\n{}^\n\nInvalid character at pos 9", + "val name' = 20", + " " + ); + + assert_eq!( + expected_str, err_str, ); } @@ -87,16 +109,18 @@ mod tests { let input = String::from("first line\nsecond line\nthird line"); let chars: Vec = input.chars().into_iter().collect(); - let result = get_line(&chars, 15); + let (result, back_count) = get_line(&chars, 15); assert_eq!("second line", result); + assert_eq!(4, back_count); let input = String::from("val binding = 322"); let chars: Vec = input.chars().into_iter().collect(); - let result = get_line(&chars, 6); + let (result, back_count) = get_line(&chars, 6); assert_eq!("val binding = 322", result); + assert_eq!(6, back_count); } }