1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
use std::fmt;

use arcstr::ArcStr;

use crate::{DebugLisp, DebugLispStruct, Span};

#[derive(Clone)]
pub struct SpannedError {
	pub message: String,
	pub source: ArcStr,
	pub span: Option<Span>,
}

impl std::error::Error for SpannedError {}

pub type Result<T> = std::result::Result<T, SpannedError>;

impl fmt::Display for SpannedError {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		if f.alternate() {
			write!(f, "{}", self.message)
		} else {
			writeln!(f)?;
			writeln!(f, "ERROR: {}", self.message)?;

			if let Some(span) = self.span {
				let line_num = span.start.line + 1;
				let line_digits = ((line_num as f32).log10() + 1.0) as usize;
				let len = span.end.character - span.start.character;

				let line = self
					.source
					.lines()
					.enumerate()
					.find_map(|(idx, line)| {
						if idx == span.start.line {
							Some(line)
						} else {
							None
						}
					})
					.ok_or(fmt::Error)?;

				let tab_count = line.chars().filter(|c| *c == '\t').count();
				let offset = span.start.character - tab_count + (tab_count * 4);
				let line = line.replace('\t', "    ");

				writeln!(f, "{:>offset$}", "|", offset = line_digits + 2)?;
				writeln!(f, "{} | {}", line_num, line)?;
				write!(f, "{:>offset$} ", "|", offset = line_digits + 2)?;
				write!(f, "{:>offset$}", "^", offset = offset + 1)?;

				if len > 1 {
					write!(f, "{:-<width$}", "-", width = len - 1)?;
				}

				writeln!(f)?;
			}

			Ok(())
		}
	}
}

impl fmt::Debug for SpannedError {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		f.debug_struct("SpannedError")
			.field("message", &self.message)
			.field("source", &self.source)
			.field("span", &self.span)
			.finish()
	}
}

impl DebugLisp for SpannedError {
	fn fmt(&self, f: &mut fmt::Formatter, indent: usize) -> fmt::Result {
		DebugLispStruct::new(f, indent, "SpannedError")
			.field("message", &self.message)
			.optional_field(
				"span",
				self.span.as_ref().map(|span| span as &dyn DebugLisp),
			)
			.finish()
	}
}