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