use crate::Location; use std::fmt::Display; #[derive(Debug, PartialEq, Eq)] pub struct BaseError { pub error: T, pub location: Location, pub source_path: String, } impl std::ops::Deref for BaseError { type Target = T; fn deref(&self) -> &Self::Target { &self.error } } impl std::error::Error for BaseError where T: std::fmt::Display + std::fmt::Debug {} impl Display for BaseError where T: std::fmt::Display, { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { self.location.fmt_with(f, &self.error) } } impl BaseError { pub fn error(self) -> T { self.error } pub fn from(obj: BaseError) -> Self where U: Into, { Self { error: obj.error.into(), location: obj.location, source_path: obj.source_path, } } pub fn into(self) -> BaseError where T: Into, { BaseError::from(self) } } #[derive(Debug, thiserror::Error)] pub struct CompileError { pub body: BaseError, pub statement: Option, } impl std::ops::Deref for CompileError { type Target = BaseError; fn deref(&self) -> &Self::Target { &self.body } } impl std::fmt::Display for CompileError where T: std::fmt::Display, { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let loc = self.location; if let Some(ref stmt) = self.statement { // visualize the error when location and statement are provided loc.fmt_with(f, &self.error)?; write!(f, "\n{stmt}{arrow:>pad$}", pad = loc.column(), arrow = "^") } else { loc.fmt_with(f, &self.error) } } } impl CompileError { pub fn from(error: BaseError, source: &str) -> Self where T: From, { let statement = get_statement(source, error.location); CompileError { body: error.into(), statement, } } } fn get_statement(source: &str, loc: Location) -> Option { if loc.column() == 0 || loc.row() == 0 { return None; } let line = source.split('\n').nth(loc.row() - 1)?.to_owned(); Some(line + "\n") }