1use std::fmt;
2
3use crate::{DebugLisp, DebugLispStruct, SourceStr, Span};
4
5#[derive(Clone)]
6pub struct SpannedError {
7 pub message: String,
8 pub source: SourceStr,
9 pub span: Option<Span>,
10}
11
12pub type Result<T> = std::result::Result<T, SpannedError>;
13
14impl std::error::Error for SpannedError {}
15
16impl fmt::Display for SpannedError {
17 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
18 if f.alternate() {
19 write!(f, "{}", self.message)
20 } else {
21 writeln!(f)?;
22 writeln!(f, "ERROR: {}", self.message)?;
23
24 if let Some(span) = self.span {
25 let line_num = span.start.line + 1;
26 let line_digits = ((line_num as f32).log10() + 1.0) as usize;
27 let len = span.end.character - span.start.character;
28
29 let line = self
30 .source
31 .lines()
32 .enumerate()
33 .find_map(|(idx, line)| {
34 if idx == span.start.line {
35 Some(line)
36 } else {
37 None
38 }
39 })
40 .ok_or(fmt::Error)?;
41
42 let tab_count = line.chars().filter(|c| *c == '\t').count();
43 let offset = span.start.character - tab_count + (tab_count * 4);
44 let line = line.replace('\t', " ");
45
46 writeln!(f, "{:>offset$}", "|", offset = line_digits + 2)?;
47 writeln!(f, "{} | {}", line_num, line)?;
48 write!(f, "{:>offset$} ", "|", offset = line_digits + 2)?;
49 write!(f, "{:>offset$}", "^", offset = offset + 1)?;
50
51 if len > 1 {
52 write!(f, "{:-<width$}", "-", width = len - 1)?;
53 }
54
55 writeln!(f)?;
56 }
57
58 Ok(())
59 }
60 }
61}
62
63impl fmt::Debug for SpannedError {
64 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
65 f.debug_struct("SpannedError")
66 .field("message", &self.message)
67 .field("source", &self.source)
68 .field("span", &self.span)
69 .finish()
70 }
71}
72
73impl DebugLisp for SpannedError {
74 fn fmt(&self, f: &mut fmt::Formatter, indent: usize) -> fmt::Result {
75 DebugLispStruct::new(f, indent, "SpannedError")
76 .field("message", &self.message)
77 .optional_field(
78 "span",
79 self.span.as_ref().map(|span| span as &dyn DebugLisp),
80 )
81 .finish()
82 }
83}