gramatika/
error.rs

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}