Skip to content

Commit

Permalink
Utils: make error messages beautiful
Browse files Browse the repository at this point in the history
  • Loading branch information
Ernest1338 committed Jan 28, 2025
1 parent ebb8f2a commit 28f8a29
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 27 deletions.
32 changes: 17 additions & 15 deletions src/black.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
compiler::Compiler,
interpreter::Interpreter,
utils::{display_error, ErrorType, Output},
utils::{display_error, get_tmp_fname, ErrorType, Output},
};
use std::{
fs::{canonicalize, read_to_string},
Expand All @@ -10,7 +10,6 @@ use std::{
};

// TODO:
// - beatutiful error messages
// - more test cases for error returns (eg var access when doesnt exist in interpreter)
// - restructurize so the copiler and interpreter are libraries and cli is a bin
// - if, else expr
Expand Down Expand Up @@ -81,18 +80,22 @@ fn main() {
input = input.trim().to_string();

let code = preprocess(&input);

let tmp_code_fname = get_tmp_fname("black_interactive");
std::fs::write(&tmp_code_fname, &code).expect("Failed to write temporary file");

let tokens = match lexer(&code) {
Ok(tokens) => tokens,
Err(err) => {
display_error(err, &code, Output::Stdout);
display_error(err, &tmp_code_fname, Output::Stdout);
continue;
}
};
let mut parser = Parser::new(&tokens);
let ast = match parser.parse() {
Ok(ast) => ast,
Err(err) => {
display_error(err, &code, Output::Stdout);
display_error(err, &tmp_code_fname, Output::Stdout);
continue;
}
};
Expand All @@ -103,8 +106,10 @@ fn main() {

let res = interpreter.run();
if let Err(err) = res {
display_error(err, &code, Output::Stdout);
display_error(err, &tmp_code_fname, Output::Stdout);
}

std::fs::remove_file(tmp_code_fname).expect("Failed to remove temporary file");
}
}

Expand All @@ -117,14 +122,15 @@ fn main() {
Err(_) => {
display_error(
ErrorType::Generic("Could not read source code file".to_string()),
"",
args.input.as_ref().unwrap().to_str().unwrap(),
Output::Stderr,
);
exit(1);
}
},
None => panic!("Input argument unexpectedly None. This is a bug."),
};
let input_file = args.input.as_ref().unwrap().to_str().unwrap();

// -------------
// Preprocessing
Expand All @@ -137,7 +143,7 @@ fn main() {
let tokens = measure_time("Lexical Analysis", || match lexer(&source_code) {
Ok(tokens) => tokens,
Err(err) => {
display_error(err, &orig_source_code, Output::Stderr);
display_error(err, input_file, Output::Stderr);
exit(1);
}
});
Expand All @@ -154,11 +160,7 @@ fn main() {
ErrorType::SyntaxError(e) => e,
_ => "".to_string(),
};
display_error(
ErrorType::SyntaxError(message),
&orig_source_code,
Output::Stderr,
);
display_error(ErrorType::SyntaxError(message), input_file, Output::Stderr);
exit(1);
}
});
Expand All @@ -171,7 +173,7 @@ fn main() {
let mut interpreter = Interpreter::from_ast(ast);
measure_time("Interpreter Execution", || {
if let Err(err) = interpreter.run() {
display_error(err, &orig_source_code, Output::Stderr);
display_error(err, input_file, Output::Stderr);
exit(1);
}
});
Expand All @@ -182,7 +184,7 @@ fn main() {
let mut compiler = Compiler::from_ast(ast);
measure_time("Full Compiler Execution", || {
if let Err(err) = compiler.compile(&args) {
display_error(err, &orig_source_code, Output::Stderr);
display_error(err, input_file, Output::Stderr);
exit(1);
}
});
Expand All @@ -203,7 +205,7 @@ fn main() {
let mut compiler = Compiler::from_ast(ast);
measure_time("Full Compiler Execution", || {
if let Err(err) = compiler.compile(&args) {
display_error(err, &orig_source_code, Output::Stderr);
display_error(err, input_file, Output::Stderr);
exit(1);
}
});
Expand Down
62 changes: 50 additions & 12 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
use std::{
env,
fmt::{Debug, Display},
fs::OpenOptions,
fs::{read_to_string, OpenOptions},
io::{stdout, Write},
time::{Instant, SystemTime, UNIX_EPOCH},
};
Expand Down Expand Up @@ -154,12 +154,12 @@ pub enum ErrorType {
Generic(String),
}

fn get_line_nr_str(line_nr: Option<usize>) -> String {
match line_nr {
Some(line_nr) => format!(" on line {line_nr}:"),
None => "".to_string(),
}
}
// fn get_line_nr_str(line_nr: Option<usize>) -> String {
// match line_nr {
// Some(line_nr) => color(&format!(" on line {line_nr}:"), Color::Gray),
// None => "".to_string(),
// }
// }

#[derive(Debug, PartialEq)]
pub enum Output {
Expand All @@ -168,7 +168,7 @@ pub enum Output {
}

/// Display error to the user in a pretty way
pub fn display_error(err: ErrorType, src: &str, target: Output) {
pub fn display_error(err: ErrorType, filename: &str, target: Output) {
let output_fn = match target {
Output::Stdout => |msg| println!("{msg}"),
Output::Stderr => |msg| eprintln!("{msg}"),
Expand All @@ -179,11 +179,49 @@ pub fn display_error(err: ErrorType, src: &str, target: Output) {
ErrorType::Generic(msg) => ("[Error]", msg),
};

let formatted_message = format!(
"{}{} {message}",
// Early return if disabled line number backtracing
if env::var("DISABLE_LINE_NUMBER_BACKTRACING").is_ok() {
let formatted_message = format!("{} {}", color(prefix, Color::LightRed), message);
return output_fn(&formatted_message);
}

let source = read_to_string(filename).ok();
let line_nr = source
.as_ref()
.and_then(|src| find_error_line_number(src))
.unwrap();
let lines = source.as_ref().map(|src| src.lines().collect::<Vec<_>>());

let mut formatted_message = String::new();
formatted_message.push_str(&color("─────────────────────────────────", Color::Gray));
formatted_message.push_str(&format!(
"\n{} {}\n",
color(prefix, Color::LightRed),
get_line_nr_str(find_error_line_number(src))
);
color(&format!("{filename}:{line_nr}"), Color::Underline)
));

// Append the source line if available
if let (n, Some(lines)) = (line_nr, lines) {
if let Some(line) = lines.get(n - 1) {
let line_length = line.len();
let underline = format!(
" {}{} {} {}",
" ".repeat(n.to_string().len()),
color("|", Color::Gray),
color(&"‾".repeat(line_length), Color::Red),
color(&message, Color::Red)
);
formatted_message.push_str(&format!(
" {}{}\n{}{} {line}",
" ".repeat(n.to_string().len()),
color("|", Color::Gray),
color(&n.to_string(), Color::Gray),
color(" |", Color::Gray)
));
formatted_message.push_str(&format!("\n{underline}"));
formatted_message.push_str(&color("\n─────────────────────────────────", Color::Gray));
}
}

output_fn(&formatted_message);
}
Expand Down

0 comments on commit 28f8a29

Please sign in to comment.