1use std::fmt;
2use std::io::IsTerminal;
3use std::ops::Range;
4
5use thiserror::Error;
6
7#[derive(Debug, Clone)]
8pub struct ParseErrorDetails {
9 pub message: String,
10 pub input: String,
11 pub span: Range<usize>,
12}
13
14impl ParseErrorDetails {
15 pub fn new(message: String, input: String, span: Range<usize>) -> Self {
16 Self {
17 message,
18 input,
19 span,
20 }
21 }
22
23 fn find_line_info(&self) -> (usize, usize, &str) {
24 let offset = self.span.start.min(self.input.len());
25 let before = &self.input[..offset];
26 let line_num = before.matches('\n').count() + 1;
27 let line_start = before.rfind('\n').map_or(0, |i| i + 1);
28 let line_end = self.input[offset..]
29 .find('\n')
30 .map_or(self.input.len(), |i| offset + i);
31 let line = &self.input[line_start..line_end];
32 let col = offset - line_start;
33 (line_num, col, line)
34 }
35
36 pub fn render(&self, use_color: bool) -> String {
37 let (line_num, col, line) = self.find_line_info();
38 let line_num_width = line_num.to_string().len().max(1);
39
40 let (red, cyan, reset) = if use_color {
41 ("\x1b[31m", "\x1b[36m", "\x1b[0m")
42 } else {
43 ("", "", "")
44 };
45
46 let mut output = String::new();
47 output.push_str(&format!("{red}error:{reset} {}\n", self.message));
48 output.push_str(&format!(
49 "{cyan}{:>width$} |{reset}\n",
50 "",
51 width = line_num_width
52 ));
53 output.push_str(&format!(
54 "{cyan}{line_num:>line_num_width$} |{reset} {line}\n"
55 ));
56 output.push_str(&format!(
57 "{cyan}{:>width$} |{reset} {}{red}^{reset}\n",
58 "",
59 " ".repeat(col),
60 width = line_num_width
61 ));
62 output
63 }
64
65 pub fn render_auto(&self) -> String {
66 self.render(std::io::stderr().is_terminal())
67 }
68}
69
70impl fmt::Display for ParseErrorDetails {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 write!(f, "{}", self.render(false))
73 }
74}
75
76#[derive(Error, Debug)]
77pub enum Error {
78 #[error("{0}")]
79 Parse(ParseErrorDetails),
80
81 #[error("Compile error: {0}")]
82 Compile(String),
83
84 #[error("Runtime error: {0}")]
85 Runtime(String),
86
87 #[error("Undefined symbol: {0}")]
88 UndefinedSymbol(String),
89
90 #[error("Type error: expected {expected}, got {actual}")]
91 Type { expected: String, actual: String },
92
93 #[error("Arity error: {name} expects {expected} arguments, got {actual}")]
94 Arity {
95 name: String,
96 expected: usize,
97 actual: usize,
98 },
99}
100
101impl Error {
102 #[must_use]
103 pub fn parse(message: String, input: &str, span: Range<usize>) -> Self {
104 Self::Parse(ParseErrorDetails::new(message, input.to_string(), span))
105 }
106
107 #[must_use]
108 pub fn render(&self, use_color: bool) -> String {
109 match self {
110 Error::Parse(details) => details.render(use_color),
111 _ => self.to_string(),
112 }
113 }
114
115 #[must_use]
116 pub fn render_auto(&self) -> String {
117 match self {
118 Error::Parse(details) => details.render_auto(),
119 _ => self.to_string(),
120 }
121 }
122}
123
124pub type Result<T> = std::result::Result<T, Error>;