1mod compile_error;
2mod handler;
3mod lex_error;
4mod parse_error;
5
6use crate::span::{empty_span, Span, Spanned};
7use ariadne::{FnCache, Label, Report, ReportKind, Source};
8use std::fmt::{Display, Formatter, Result, Write};
9use thiserror::Error;
10use yansi::{Color, Paint, Style};
11
12pub(super) use compile_error::CompileError;
13pub(super) use compile_error::LargeTypeError;
14pub use handler::{ErrorEmitted, Handler};
15pub(super) use lex_error::LexError;
16pub(super) use parse_error::ParseError;
17
18pub struct ErrorLabel {
20 pub(super) message: String,
21 pub(super) span: Span,
22 pub(super) color: Color,
23}
24
25#[derive(Error, Debug)]
27pub enum Error {
28 #[error("{error}")]
29 Lex { span: Span, error: LexError },
30 #[error("{error}")]
31 Parse { error: ParseError },
32 #[error("{error}")]
33 Compile { error: CompileError },
34 #[error("{child}")]
35 MacroBodyWrapper {
36 child: Box<Self>,
37 macro_name: String,
38 macro_span: Span,
39 },
40 #[error("compiler internal error: {msg}")]
41 Internal { msg: String, span: Span },
42}
43
44#[derive(Debug)]
45pub struct Errors(pub Vec<Error>);
46
47impl Display for Errors {
48 fn fmt(&self, f: &mut Formatter) -> Result {
49 write!(
50 f,
51 "{}",
52 self.0
53 .iter()
54 .map(|err| err.display_raw())
55 .collect::<String>()
56 .trim_end()
57 )
58 }
59}
60
61pub trait ReportableError
64where
65 Self: std::fmt::Display + Spanned,
66{
67 fn labels(&self) -> Vec<ErrorLabel>;
69
70 fn note(&self) -> Option<String>;
72
73 fn code(&self) -> Option<String>;
75
76 fn help(&self) -> Option<String>;
78
79 fn print(&self) {
81 let filepaths_and_sources = self
82 .labels()
83 .iter()
84 .map(|label| {
85 let filepath = format!("{}", label.span.context().display());
86 let source = std::fs::read_to_string(filepath.clone()).unwrap_or("<none>".into());
87 (filepath, source)
88 })
89 .collect::<Vec<(String, String)>>();
90
91 let error_file: &str = &format!("{}", self.span().context().display());
92 let mut report_builder =
93 Report::build(ReportKind::Error, (error_file, self.span().range.clone()))
94 .with_message(format!("{}", self.bold()))
95 .with_labels(
96 self.labels()
97 .iter()
98 .enumerate()
99 .map(|(index, label)| {
100 let filepath: &str = &filepaths_and_sources[index].0;
101 let mut style = Style::new().bold();
102 style.foreground = Some(label.color);
103 Label::new((filepath, label.span.start()..label.span.end()))
104 .with_message(label.message.clone().paint(style))
105 .with_color(label.color)
106 })
107 .collect::<Vec<_>>(),
108 );
109
110 if let Some(code) = self.code() {
111 report_builder = report_builder.with_code(code);
112 }
113
114 if let Some(note) = self.note() {
115 report_builder = report_builder.with_note(note);
116 }
117
118 if let Some(help) = self.help() {
119 report_builder = report_builder.with_help(help);
120 }
121
122 report_builder
123 .finish()
124 .eprint(
125 FnCache::new(|id: &&str| Err(Box::new(format!("Failed to fetch source '{id}'"))))
126 .with_sources(
127 filepaths_and_sources
128 .iter()
129 .map(|(id, s)| (&id[..], Source::from(s)))
130 .collect(),
131 ),
132 )
133 .unwrap();
134 }
135
136 fn display_raw(&self) -> String {
137 self.to_string()
138 + "\n"
139 + &self.labels().iter().fold(String::new(), |mut acc, label| {
140 writeln!(
141 &mut acc,
142 "@{}..{}: {}",
143 label.span.start(),
144 label.span.end(),
145 label.message
146 )
147 .expect("Failed to write label to string");
148 acc
149 })
150 + &self
151 .note()
152 .map_or(String::new(), |note| format!("{note}\n"))
153 + &self
154 .help()
155 .map_or(String::new(), |help| format!("{help}\n"))
156 }
157}
158
159impl ReportableError for Error {
160 fn labels(&self) -> Vec<ErrorLabel> {
161 use Error::*;
162 match self {
163 Lex { error, span } => match error {
164 LexError::InvalidToken => vec![ErrorLabel {
167 message: "invalid token".into(),
168 span: span.clone(),
169 color: Color::Red,
170 }],
171 },
172 Parse { error } => error.labels(),
173 Compile { error } => error.labels(),
174 MacroBodyWrapper {
175 child,
176 macro_name,
177 macro_span,
178 } => {
179 let mut labels = child.labels();
180 labels.push(ErrorLabel {
181 message: format!("when making macro call to '{macro_name}'"),
182 span: macro_span.clone(),
183 color: Color::Yellow,
184 });
185 labels
186 }
187 Internal { msg, span } => {
188 if span == &empty_span() {
189 Vec::new()
190 } else {
191 vec![ErrorLabel {
192 message: msg.to_string(),
193 span: span.clone(),
194 color: Color::Red,
195 }]
196 }
197 }
198 }
199 }
200
201 fn note(&self) -> Option<String> {
202 use Error::*;
203 match self {
204 Lex { .. } => None,
205 Parse { error } => error.note(),
206 Compile { error } => error.note(),
207 MacroBodyWrapper { child, .. } => child.note(),
208 Internal { .. } => None,
209 }
210 }
211
212 fn code(&self) -> Option<String> {
213 use Error::*;
214 match self {
215 Lex { .. } => None,
216 Parse { error } => error.code().map(|code| format!("P{code}")),
217 Compile { error } => error.code().map(|code| format!("C{code}")),
218 MacroBodyWrapper { child, .. } => child.code(),
219 Internal { .. } => None,
220 }
221 }
222
223 fn help(&self) -> Option<String> {
224 use Error::*;
225 match self {
226 Lex { .. } => None,
227 Parse { error } => error.help(),
228 Compile { error } => error.help(),
229 MacroBodyWrapper { child, .. } => child.help(),
230 Internal { .. } => None,
231 }
232 }
233}
234
235impl Spanned for Error {
236 fn span(&self) -> &Span {
237 use Error::*;
238 match &self {
239 Lex { span, .. } => span,
240 Parse { error } => error.span(),
241 Compile { error } => error.span(),
242 MacroBodyWrapper { child, .. } => child.span(),
243 Internal { span, .. } => span,
244 }
245 }
246}
247
248pub fn print_errors(errs: &Errors) {
250 for err in &errs.0 {
251 err.print();
252 }
253}
254
255#[macro_export]
258macro_rules! pintc_bail {
259 ($number_of_errors: expr, $filepath: expr) => {
260 if $number_of_errors == 1 {
261 anyhow::bail!(
262 "could not compile `{}` due to previous error",
263 format!("{}", $filepath.display())
264 )
265 } else {
266 anyhow::bail!(
267 "could not compile `{}` due to {} previous errors",
268 format!("{}", $filepath.display()),
269 $number_of_errors
270 )
271 }
272 };
273}