Add slightly more readable error messages in the compiler
Previously, the CompileError error messages were just `Debug::fmt` written to stdout and there wasn't really a backtrace in the code included. Now, when there is an error in an imported file, it will display a backtrace of the files included that caused this error. These are not perfect error messages and are a bit rough around the edges but they are good enough for now. Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
@@ -70,17 +70,44 @@ impl Scope {
|
|||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub struct CompileError {
|
pub enum CompileError {
|
||||||
pub line: Option<LineRange>,
|
Error {
|
||||||
pub message: String,
|
line: Option<LineRange>,
|
||||||
|
source_path: String,
|
||||||
|
message: String,
|
||||||
|
},
|
||||||
|
Import {
|
||||||
|
error: Box<dyn std::error::Error>,
|
||||||
|
line: LineRange,
|
||||||
|
source_path: String,
|
||||||
|
dest_path: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for CompileError {
|
impl Display for CompileError {
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
if let Some(line) = &self.line {
|
match self {
|
||||||
write!(fmt, "line {:?}: {}", line, self.message)
|
CompileError::Error {
|
||||||
} else {
|
line: Some((line, _)),
|
||||||
write!(fmt, "{}", self.message)
|
source_path,
|
||||||
|
message,
|
||||||
|
} => write!(fmt, "{source_path}: line {line}: {message}"),
|
||||||
|
CompileError::Error {
|
||||||
|
line: None,
|
||||||
|
source_path,
|
||||||
|
message,
|
||||||
|
} => write!(fmt, "{source_path}: {}", message),
|
||||||
|
CompileError::Import {
|
||||||
|
error,
|
||||||
|
line: (line, _),
|
||||||
|
source_path,
|
||||||
|
dest_path,
|
||||||
|
} => {
|
||||||
|
write!(
|
||||||
|
fmt,
|
||||||
|
"error in {dest_path} (included from {source_path} on line {line}:\n\t{error}",
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,8 +165,9 @@ impl<'c> Compiler<'c> {
|
|||||||
|
|
||||||
pub fn compile_path(self, path: impl AsRef<Path>) -> Result<Ptr<Module>> {
|
pub fn compile_path(self, path: impl AsRef<Path>) -> Result<Ptr<Module>> {
|
||||||
let path_str = &path.as_ref().as_os_str().to_str().unwrap();
|
let path_str = &path.as_ref().as_os_str().to_str().unwrap();
|
||||||
let mut file = File::open(path.as_ref()).map_err(|e| CompileError {
|
let mut file = File::open(path.as_ref()).map_err(|e| CompileError::Error {
|
||||||
line: None,
|
line: None,
|
||||||
|
source_path: self.path.display().to_string(),
|
||||||
message: format!("could not open {}: {}", path.as_ref().display(), e),
|
message: format!("could not open {}: {}", path.as_ref().display(), e),
|
||||||
})?;
|
})?;
|
||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
@@ -149,8 +177,9 @@ impl<'c> Compiler<'c> {
|
|||||||
let ast = parser.parse_all()?;
|
let ast = parser.parse_all()?;
|
||||||
|
|
||||||
if parser.was_error() {
|
if parser.was_error() {
|
||||||
return Err(CompileError {
|
return Err(CompileError::Error {
|
||||||
line: None,
|
line: None,
|
||||||
|
source_path: self.path.display().to_string(),
|
||||||
message: format!("error in '{}'", path.as_ref().display()),
|
message: format!("error in '{}'", path.as_ref().display()),
|
||||||
}
|
}
|
||||||
.into());
|
.into());
|
||||||
@@ -217,8 +246,9 @@ impl<'c> Compiler<'c> {
|
|||||||
|
|
||||||
let index = self.constants.len();
|
let index = self.constants.len();
|
||||||
if index > (ConstantId::MAX as usize) {
|
if index > (ConstantId::MAX as usize) {
|
||||||
return Err(CompileError {
|
return Err(CompileError::Error {
|
||||||
line: None,
|
line: None,
|
||||||
|
source_path: self.path.display().to_string(),
|
||||||
message: format!("too many constants (maximum {})", ConstantId::MAX),
|
message: format!("too many constants (maximum {})", ConstantId::MAX),
|
||||||
}
|
}
|
||||||
.into());
|
.into());
|
||||||
@@ -243,8 +273,9 @@ impl<'c> Compiler<'c> {
|
|||||||
|
|
||||||
let index = self.globals.len();
|
let index = self.globals.len();
|
||||||
if index > (GlobalId::MAX as usize) {
|
if index > (GlobalId::MAX as usize) {
|
||||||
return Err(CompileError {
|
return Err(CompileError::Error {
|
||||||
line: None,
|
line: None,
|
||||||
|
source_path: self.path.display().to_string(),
|
||||||
message: format!("too many globals (maximum {})", GlobalId::MAX),
|
message: format!("too many globals (maximum {})", GlobalId::MAX),
|
||||||
}
|
}
|
||||||
.into());
|
.into());
|
||||||
@@ -302,8 +333,9 @@ impl<'c> Compiler<'c> {
|
|||||||
fn insert_local(&mut self, name: String) -> Result<&Local> {
|
fn insert_local(&mut self, name: String) -> Result<&Local> {
|
||||||
let index = self.chunk().locals.len();
|
let index = self.chunk().locals.len();
|
||||||
if index > (LocalIndex::MAX as usize) {
|
if index > (LocalIndex::MAX as usize) {
|
||||||
return Err(CompileError {
|
return Err(CompileError::Error {
|
||||||
line: None,
|
line: None,
|
||||||
|
source_path: self.path.display().to_string(),
|
||||||
message: format!("too many locals (maximum: {})", LocalIndex::MAX),
|
message: format!("too many locals (maximum: {})", LocalIndex::MAX),
|
||||||
}
|
}
|
||||||
.into());
|
.into());
|
||||||
@@ -327,8 +359,9 @@ impl<'c> Compiler<'c> {
|
|||||||
// get the last allocated slot and increment by one
|
// get the last allocated slot and increment by one
|
||||||
let last = &scope.scope.last().unwrap();
|
let last = &scope.scope.last().unwrap();
|
||||||
if last.slot == LocalSlot::MAX {
|
if last.slot == LocalSlot::MAX {
|
||||||
return Err(CompileError {
|
return Err(CompileError::Error {
|
||||||
line: None,
|
line: None,
|
||||||
|
source_path: self.path.display().to_string(),
|
||||||
message: format!(
|
message: format!(
|
||||||
"too many stack slots used by locals(maximum: {})",
|
"too many stack slots used by locals(maximum: {})",
|
||||||
LocalSlot::MAX
|
LocalSlot::MAX
|
||||||
@@ -444,9 +477,11 @@ impl StmtVisitor for Compiler<'_> {
|
|||||||
// also into the modules cache
|
// also into the modules cache
|
||||||
let module = Compiler::new(path.clone(), self.constants, self.imported)
|
let module = Compiler::new(path.clone(), self.constants, self.imported)
|
||||||
.compile_path(&path)
|
.compile_path(&path)
|
||||||
.map_err(|e| CompileError {
|
.map_err(|error| CompileError::Import {
|
||||||
line: Some(line),
|
error,
|
||||||
message: format!("while importing module '{}': {}", stmt.module.text, e),
|
line,
|
||||||
|
source_path: self.path.display().to_string(),
|
||||||
|
dest_path: path_str.to_string(),
|
||||||
})?;
|
})?;
|
||||||
self.imported.insert(path_str.to_string(), module.clone());
|
self.imported.insert(path_str.to_string(), module.clone());
|
||||||
module
|
module
|
||||||
@@ -684,8 +719,9 @@ impl ExprVisitor for Compiler<'_> {
|
|||||||
self.compile_expr(arg)?;
|
self.compile_expr(arg)?;
|
||||||
}
|
}
|
||||||
if expr.args.len() > (Argc::MAX as usize) {
|
if expr.args.len() > (Argc::MAX as usize) {
|
||||||
return Err(CompileError {
|
return Err(CompileError::Error {
|
||||||
line: Some(expr_line_number(expr)),
|
line: Some(expr_line_number(expr)),
|
||||||
|
source_path: self.path.display().to_string(),
|
||||||
message: format!("too many function arguments (maximum: {})", Argc::MAX),
|
message: format!("too many function arguments (maximum: {})", Argc::MAX),
|
||||||
}
|
}
|
||||||
.into());
|
.into());
|
||||||
@@ -718,8 +754,9 @@ impl ExprVisitor for Compiler<'_> {
|
|||||||
if let Some(local) = self.get_local(name) {
|
if let Some(local) = self.get_local(name) {
|
||||||
self.emit(expr_line_number(expr), Op::GetLocal(local.index));
|
self.emit(expr_line_number(expr), Op::GetLocal(local.index));
|
||||||
} else {
|
} else {
|
||||||
let global = self.get_global(name).ok_or_else(|| CompileError {
|
let global = self.get_global(name).ok_or_else(|| CompileError::Error {
|
||||||
line: Some(expr_line_number(expr)),
|
line: Some(expr_line_number(expr)),
|
||||||
|
source_path: self.path.display().to_string(),
|
||||||
message: if self.is_global_scope() {
|
message: if self.is_global_scope() {
|
||||||
format!("unknown global {}", name)
|
format!("unknown global {}", name)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
17
src/main.rs
17
src/main.rs
@@ -22,7 +22,11 @@ struct Args {
|
|||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() {
|
||||||
|
// While main() is allowed to return an error, it will always do `Debug::fmt` on the output wrather
|
||||||
|
// than `Display::fmt`. We want to display pretty errors, and the easiest way to do this (in my
|
||||||
|
// experience) is to just wrap it and output the error.
|
||||||
|
fn main_wrapper() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
// initialize type system
|
// initialize type system
|
||||||
@@ -56,3 +60,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let result = main_wrapper();
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(()) => { /* return 0 OK */ }
|
||||||
|
Err(e) => {
|
||||||
|
println!("{}", e);
|
||||||
|
std::process::exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user