Compare commits

..

2 Commits

Author SHA1 Message Date
52bb445f90 Improve error messages for lexic errors 2023-12-24 19:43:51 -05:00
e0a33e22ba Re-enable REPL support 2023-12-23 18:55:21 -05:00
5 changed files with 73 additions and 10 deletions

View File

@ -1,6 +1,7 @@
mod compile; mod compile;
mod empty; mod empty;
mod help; mod help;
mod repl;
mod types; mod types;
use types::CommandType; use types::CommandType;

4
src/cli/repl.rs Normal file
View File

@ -0,0 +1,4 @@
pub fn repl_command(_arguments: Vec<String>) {
println!("{}", super::get_version());
let _ = crate::repl::run();
}

View File

@ -16,6 +16,7 @@ impl CommandType {
match self { match self {
CommandType::Help => super::help::help_command(options), CommandType::Help => super::help::help_command(options),
CommandType::Compile => super::compile::compile_command(options), CommandType::Compile => super::compile::compile_command(options),
CommandType::Repl => super::repl::repl_command(options),
CommandType::None => super::empty::empty_command(options), CommandType::None => super::empty::empty_command(options),
_ => { _ => {
println!("Not implemented yet! {:?} {:?}", self, options); println!("Not implemented yet! {:?} {:?}", self, options);

View File

@ -4,12 +4,14 @@ use std::collections::VecDeque;
impl PrintableError for LexError { impl PrintableError for LexError {
// TODO: Count and show line number // TODO: Count and show line number
fn get_error_str(&self, chars: &Vec<char>) -> String { fn get_error_str(&self, chars: &Vec<char>) -> String {
let line_number = get_line_number(chars, self.position);
let (erroneous_code, back_count) = get_line(chars, self.position); let (erroneous_code, back_count) = get_line(chars, self.position);
let whitespace = vec![' '; back_count].iter().collect::<String>(); let whitespace = " ".repeat(back_count + line_number.to_string().len() + 1);
format!( format!(
"\n{}\n{}^\n\n{}{}\n{}", "\n{}|{}\n{}^\n\n{}{}\n{}",
line_number,
erroneous_code, erroneous_code,
whitespace, whitespace,
"Invalid character at pos ", "Invalid character at pos ",
@ -69,6 +71,22 @@ fn get_line(chars: &Vec<char>, pos: usize) -> (String, usize) {
(result_chars.iter().collect::<String>(), pos - before_pos) (result_chars.iter().collect::<String>(), pos - before_pos)
} }
fn get_line_number(chars: &Vec<char>, 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -85,9 +103,8 @@ mod tests {
let chars: Vec<char> = input.chars().into_iter().collect(); let chars: Vec<char> = input.chars().into_iter().collect();
let err_str = err_data.get_error_str(&chars); let err_str = err_data.get_error_str(&chars);
// TODO: check for line number
let expected_str = format!( 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: `\\'`)" "val name' = 20", " ", "Unrecognized character `'` (escaped: `\\'`)"
); );
@ -114,4 +131,22 @@ mod tests {
assert_eq!("val binding = 322", result); assert_eq!("val binding = 322", result);
assert_eq!(6, back_count); 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<char> = 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);
}
} }

View File

@ -1,3 +1,4 @@
use colored::*;
use std::{fs, path::Path}; use std::{fs, path::Path};
use crate::lexic::token::Token; use crate::lexic::token::Token;
@ -7,28 +8,49 @@ pub fn compile_file(input: &String) {
let input_path = Path::new(input); let input_path = Path::new(input);
if !input_path.is_file() { 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 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(); let mut output_path = Path::new(input).canonicalize().unwrap();
output_path.set_extension("php"); output_path.set_extension("php");
fs::write(output_path, out_code).expect("Error writing to output path"); 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 /// Executes Lexical analysis, handles errors and calls build_ast for the next phase
fn compile(input: &String) -> String { fn compile(input: &String) -> Option<String> {
let tokens = lexic::get_tokens(input); let tokens = lexic::get_tokens(input);
match tokens { match tokens {
Ok(tokens) => build_ast(input, tokens), Ok(tokens) => Some(build_ast(input, tokens)),
Err(error) => { Err(error) => {
let chars: Vec<char> = input.chars().into_iter().collect(); let chars: Vec<char> = input.chars().into_iter().collect();
panic!("{}", error.get_error_str(&chars)) println!(
"{}:\n{}",
"syntax error".on_red(),
error.get_error_str(&chars)
);
None
} }
} }
} }