use std::rc::Rc; use thiserror::Error; pub type Result = core::result::Result; #[derive(Error, Debug)] pub enum ErrorKind { #[error("error occurred during parse stage: {0}")] ParseError(String), #[error("error occurred during downgrade stage: {0}")] DowngradeError(String), #[error("error occurred during evaluation stage: {0}")] EvalError(String), #[error("{0}")] Catchable(String), #[error("an unknown or unexpected error occurred")] Unknown, } #[derive(Debug)] pub struct Error { pub kind: ErrorKind, pub span: Option, pub source: Option>, } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // Basic display write!(f, "{}", self.kind)?; // If we have source and span, print context if let (Some(source), Some(span)) = (&self.source, self.span) { let start_byte = usize::from(span.start()); let end_byte = usize::from(span.end()); if start_byte > source.len() || end_byte > source.len() { return Ok(()); // Span is out of bounds } let mut start_line = 1; let mut start_col = 1usize; let mut line_start_byte = 0; for (i, c) in source.char_indices() { if i >= start_byte { break; } if c == '\n' { start_line += 1; start_col = 1; line_start_byte = i + 1; } else { start_col += 1; } } let line_end_byte = source[line_start_byte..] .find('\n') .map(|i| line_start_byte + i) .unwrap_or(source.len()); let line_str = &source[line_start_byte..line_end_byte]; let underline_len = if end_byte > start_byte { end_byte - start_byte } else { 1 }; write!(f, "\n --> {}:{}", start_line, start_col)?; write!(f, "\n |\n")?; writeln!(f, "{:4} | {}", start_line, line_str)?; write!( f, " | {}{}", " ".repeat(start_col.saturating_sub(1)), "^".repeat(underline_len) )?; } Ok(()) } } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.kind) } } impl Error { pub fn new(kind: ErrorKind) -> Self { Self { kind, span: None, source: None, } } pub fn with_span(mut self, span: rnix::TextRange) -> Self { self.span = Some(span); self } pub fn with_source(mut self, source: Rc) -> Self { self.source = Some(source); self } pub fn parse_error(msg: String) -> Self { Self::new(ErrorKind::ParseError(msg)) } pub fn downgrade_error(msg: String) -> Self { Self::new(ErrorKind::DowngradeError(msg)) } pub fn eval_error(msg: String) -> Self { Self::new(ErrorKind::EvalError(msg)) } pub fn catchable(msg: String) -> Self { Self::new(ErrorKind::Catchable(msg)) } pub fn unknown() -> Self { Self::new(ErrorKind::Unknown) } }