1use std::sync::Arc;
4
5use crate::{files::Files, lexer::Pos};
6
7#[derive(Debug)]
9pub struct Errors {
10 pub errors: Vec<Error>,
12 pub(crate) files: Arc<Files>,
13}
14
15impl std::error::Error for Errors {}
16
17impl std::fmt::Display for Errors {
18 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
19 if self.errors.is_empty() {
20 return Ok(());
21 }
22 let diagnostics = Vec::from_iter(self.errors.iter().map(|e| {
23 let message = match e {
24 Error::IoError { context, .. } => context.clone(),
25 Error::ParseError { msg, .. } => format!("parse error: {msg}"),
26 Error::TypeError { msg, .. } => format!("type error: {msg}"),
27 Error::UnreachableError { msg, .. } => format!("unreachable rule: {msg}"),
28 Error::OverlapError { msg, .. } => format!("overlap error: {msg}"),
29 Error::RecursionError { msg, .. } => format!("recursion error: {msg}"),
30 Error::ShadowedError { .. } => {
31 "more general higher-priority rule shadows other rules".to_string()
32 }
33 };
34
35 let labels = match e {
36 Error::IoError { .. } => vec![],
37
38 Error::ParseError { span, .. }
39 | Error::TypeError { span, .. }
40 | Error::UnreachableError { span, .. }
41 | Error::RecursionError { span, .. } => {
42 vec![Label::primary(span.from.file, span)]
43 }
44
45 Error::OverlapError { rules, .. } => {
46 let mut labels = vec![Label::primary(rules[0].from.file, &rules[0])];
47 labels.extend(
48 rules[1..]
49 .iter()
50 .map(|span| Label::secondary(span.from.file, span)),
51 );
52 labels
53 }
54
55 Error::ShadowedError { shadowed, mask } => {
56 let mut labels = vec![Label::primary(mask.from.file, mask)];
57 labels.extend(
58 shadowed
59 .iter()
60 .map(|span| Label::secondary(span.from.file, span)),
61 );
62 labels
63 }
64 };
65
66 let mut sources = Vec::new();
67 let mut source = e.source();
68 while let Some(e) = source {
69 sources.push(format!("{e:?}"));
70 source = std::error::Error::source(e);
71 }
72
73 Diagnostic::error()
74 .with_message(message)
75 .with_labels(labels)
76 .with_notes(sources)
77 }));
78 self.emit(f, diagnostics)?;
79 if self.errors.len() > 1 {
80 writeln!(f, "found {} errors", self.errors.len())?;
81 }
82 Ok(())
83 }
84}
85
86#[derive(Debug)]
88pub enum Error {
89 IoError {
91 error: std::io::Error,
93 context: String,
95 },
96
97 ParseError {
99 msg: String,
101
102 span: Span,
104 },
105
106 TypeError {
108 msg: String,
110
111 span: Span,
113 },
114
115 UnreachableError {
117 msg: String,
119
120 span: Span,
122 },
123
124 OverlapError {
126 msg: String,
128
129 rules: Vec<Span>,
133 },
134
135 RecursionError {
137 msg: String,
139
140 span: Span,
142 },
143
144 ShadowedError {
146 shadowed: Vec<Span>,
148
149 mask: Span,
151 },
152}
153
154impl Errors {
155 pub fn new(errors: Vec<Error>, files: Arc<Files>) -> Self {
157 Self { errors, files }
158 }
159
160 pub fn from_io(error: std::io::Error, context: impl Into<String>) -> Self {
162 Errors {
163 errors: vec![Error::IoError {
164 error,
165 context: context.into(),
166 }],
167 files: Arc::new(Files::default()),
168 }
169 }
170
171 #[cfg(feature = "fancy-errors")]
172 fn emit(
173 &self,
174 f: &mut std::fmt::Formatter,
175 diagnostics: Vec<Diagnostic<usize>>,
176 ) -> std::fmt::Result {
177 use codespan_reporting::term::termcolor;
178 let w = termcolor::BufferWriter::stderr(termcolor::ColorChoice::Auto);
179 let mut b = w.buffer();
180 let mut files = codespan_reporting::files::SimpleFiles::new();
181 for (name, source) in self
182 .files
183 .file_names
184 .iter()
185 .zip(self.files.file_texts.iter())
186 {
187 files.add(name, source);
188 }
189 for diagnostic in diagnostics {
190 codespan_reporting::term::emit(&mut b, &Default::default(), &files, &diagnostic)
191 .map_err(|_| std::fmt::Error)?;
192 }
193 let b = b.into_inner();
194 let b = std::str::from_utf8(&b).map_err(|_| std::fmt::Error)?;
195 f.write_str(b)
196 }
197
198 #[cfg(not(feature = "fancy-errors"))]
199 fn emit(
200 &self,
201 f: &mut std::fmt::Formatter,
202 diagnostics: Vec<Diagnostic<usize>>,
203 ) -> std::fmt::Result {
204 let pos = |file_id: usize, offset| {
205 let ends = self.files.file_line_map(file_id).unwrap();
206 let line0 = ends.line(offset);
207 let text = &self.files.file_texts[file_id];
208 let start = line0.checked_sub(1).map_or(0, |prev| ends[prev]);
209 let end = ends.get(line0).copied().unwrap_or(text.len());
210 let col = offset - start + 1;
211 format!(
212 "{}:{}:{}: {}",
213 self.files.file_names[file_id],
214 line0 + 1,
215 col,
216 &text[start..end]
217 )
218 };
219 for diagnostic in diagnostics {
220 writeln!(f, "{}", diagnostic.message)?;
221 for label in diagnostic.labels {
222 f.write_str(&pos(label.file_id, label.range.start))?;
223 }
224 for note in diagnostic.notes {
225 writeln!(f, "{note}")?;
226 }
227 writeln!(f)?;
228 }
229 Ok(())
230 }
231}
232
233pub struct ErrorsBuilder(Errors);
235
236impl ErrorsBuilder {
237 pub fn new() -> Self {
239 Self(Errors {
240 errors: Vec::new(),
241 files: Arc::new(Files::default()),
242 })
243 }
244
245 pub fn build(self) -> Errors {
247 self.0
248 }
249
250 pub fn errors(mut self, errors: Vec<Error>) -> Self {
252 self.0.errors = errors;
253 self
254 }
255
256 pub fn error(self, error: Error) -> Self {
258 self.errors(vec![error])
259 }
260
261 pub fn files(mut self, files: Arc<Files>) -> Self {
263 self.0.files = files;
264 self
265 }
266}
267
268impl Error {
269 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
270 match self {
271 Error::IoError { error, .. } => Some(error),
272 _ => None,
273 }
274 }
275}
276
277#[derive(Clone, Debug)]
279pub struct Span {
280 pub from: Pos,
282 pub to: Pos,
284}
285
286impl Span {
287 pub fn new_single(pos: Pos) -> Span {
289 Span {
290 from: pos,
291 to: Pos {
296 file: pos.file,
297 offset: pos.offset + 1,
298 },
299 }
300 }
301}
302
303impl From<&Span> for std::ops::Range<usize> {
304 fn from(span: &Span) -> Self {
305 span.from.offset..span.to.offset
306 }
307}
308
309use diagnostic::{Diagnostic, Label};
310
311#[cfg(feature = "fancy-errors")]
312use codespan_reporting::diagnostic;
313
314#[cfg(not(feature = "fancy-errors"))]
315mod diagnostic {
317 use std::ops::Range;
318
319 pub struct Diagnostic<FileId> {
320 pub message: String,
321 pub labels: Vec<Label<FileId>>,
322 pub notes: Vec<String>,
323 }
324
325 impl<FileId> Diagnostic<FileId> {
326 pub fn error() -> Self {
327 Self {
328 message: String::new(),
329 labels: Vec::new(),
330 notes: Vec::new(),
331 }
332 }
333
334 pub fn with_message(mut self, message: impl Into<String>) -> Self {
335 self.message = message.into();
336 self
337 }
338
339 pub fn with_labels(mut self, labels: Vec<Label<FileId>>) -> Self {
340 self.labels = labels;
341 self
342 }
343
344 pub fn with_notes(mut self, notes: Vec<String>) -> Self {
345 self.notes = notes;
346 self
347 }
348 }
349
350 pub struct Label<FileId> {
351 pub file_id: FileId,
352 pub range: Range<usize>,
353 }
354
355 impl<FileId> Label<FileId> {
356 pub fn primary(file_id: FileId, range: impl Into<Range<usize>>) -> Self {
357 Self {
358 file_id,
359 range: range.into(),
360 }
361 }
362
363 pub fn secondary(file_id: FileId, range: impl Into<Range<usize>>) -> Self {
364 Self::primary(file_id, range)
365 }
366 }
367}