diff --git a/src/error_handling/lex_error.rs b/src/error_handling/lex_error.rs index 833504e..8672da6 100644 --- a/src/error_handling/lex_error.rs +++ b/src/error_handling/lex_error.rs @@ -4,12 +4,14 @@ use std::collections::VecDeque; impl PrintableError for LexError { // TODO: Count and show line number fn get_error_str(&self, chars: &Vec) -> String { + let line_number = get_line_number(chars, self.position); let (erroneous_code, back_count) = get_line(chars, self.position); - let whitespace = vec![' '; back_count].iter().collect::(); + let whitespace = " ".repeat(back_count + line_number.to_string().len() + 1); format!( - "\n{}\n{}^\n\n{}{}\n{}", + "\n{}|{}\n{}^\n\n{}{}\n{}", + line_number, erroneous_code, whitespace, "Invalid character at pos ", @@ -69,6 +71,22 @@ fn get_line(chars: &Vec, pos: usize) -> (String, usize) { (result_chars.iter().collect::(), pos - before_pos) } +fn get_line_number(chars: &Vec, target_pos: usize) -> usize { + let mut count = 1; + + for (pos, char) in chars.iter().enumerate() { + if pos >= target_pos { + break; + } + + if *char == '\n' { + count += 1; + } + } + + count +} + #[cfg(test)] mod tests { use super::*; @@ -85,9 +103,8 @@ mod tests { let chars: Vec = input.chars().into_iter().collect(); let err_str = err_data.get_error_str(&chars); - // TODO: check for line number let expected_str = format!( - "\n{}\n{}^\n\nInvalid character at pos 9\n{}", + "\n1|{}\n {}^\n\nInvalid character at pos 9\n{}", "val name' = 20", " ", "Unrecognized character `'` (escaped: `\\'`)" ); @@ -114,4 +131,22 @@ mod tests { assert_eq!("val binding = 322", result); assert_eq!(6, back_count); } + + #[test] + fn should_get_line_number() { + let input = String::from("one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten"); + let chars: Vec = input.chars().into_iter().collect(); + + let line_number = get_line_number(&chars, 11); + assert_eq!(3, line_number); + + let line_number = get_line_number(&chars, 0); + assert_eq!(1, line_number); + + let line_number = get_line_number(&chars, 3); + assert_eq!(1, line_number); + + let line_number = get_line_number(&chars, 15); + assert_eq!(4, line_number); + } } diff --git a/src/file/mod.rs b/src/file/mod.rs index 44784b3..9157466 100644 --- a/src/file/mod.rs +++ b/src/file/mod.rs @@ -1,3 +1,4 @@ +use colored::*; use std::{fs, path::Path}; use crate::lexic::token::Token; @@ -7,28 +8,49 @@ pub fn compile_file(input: &String) { let input_path = Path::new(input); if !input_path.is_file() { - panic!("Input path is not a valid file") + println!( + "{}: {} {}", + "error".on_red(), + "Input path is not a valid file:".red(), + input + ); + return; } let bytes = fs::read(input_path).expect("INPUT_PATH should be valid"); - let contents = String::from_utf8(bytes).expect("INPUT_PATH's encoding MUST be UTF-8"); - let out_code = compile(&contents); + let contents = match String::from_utf8(bytes) { + Ok(str) => str, + Err(_) => { + println!("{}: Input file contains invalid UTF-8", "error".on_red()); + return; + } + }; + + let Some(out_code) = compile(&contents) else { + return; + }; let mut output_path = Path::new(input).canonicalize().unwrap(); output_path.set_extension("php"); + fs::write(output_path, out_code).expect("Error writing to output path"); } /// Executes Lexical analysis, handles errors and calls build_ast for the next phase -fn compile(input: &String) -> String { +fn compile(input: &String) -> Option { let tokens = lexic::get_tokens(input); match tokens { - Ok(tokens) => build_ast(input, tokens), + Ok(tokens) => Some(build_ast(input, tokens)), Err(error) => { let chars: Vec = input.chars().into_iter().collect(); - panic!("{}", error.get_error_str(&chars)) + println!( + "{}:\n{}", + "syntax error".on_red(), + error.get_error_str(&chars) + ); + None } } }