1use std::sync::Arc;
2
3use ariadne::{Color, Label, Report, ReportKind, Source};
4
5use crate::source::SourceFile;
6use crate::span::Span;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[derive(Default)]
10pub enum Severity {
11 #[default]
12 Error,
13 Warning,
14}
15
16
17#[derive(Debug, Clone)]
19struct ErrorDetail {
20 primary: (Span, String),
22 labels: Vec<(Span, String)>,
24 source: Option<Arc<String>>,
26 source_name: Option<String>,
28 help: Option<String>,
30}
31
32#[derive(Debug, Clone)]
34pub struct Error {
35 message: String,
37 line: Option<usize>,
39 reason: Option<ErrorReason>,
41 detail: Option<Box<ErrorDetail>>,
43 location: Option<(Arc<str>, usize, usize)>,
49 severity: Severity,
50}
51
52#[derive(Debug, Clone)]
54pub struct ErrorReason {
55 error_type: Reason,
57 data: Option<Vec<String>>,
59}
60
61#[derive(Clone, Copy, Debug)]
63pub enum Reason {
64 Parse,
66 AST,
68 Lexer,
70 Interpreter,
72 Utils,
74 Compile,
76 Runtime,
78}
79
80impl Error {
81 pub fn at(kind: Reason, message: impl Into<String>, span: Span) -> Self {
84 let message = message.into();
85 #[cfg(feature = "debug")]
86 log::debug!("Error: {}", message);
87 Self {
88 message: message.clone(),
89 line: None,
90 reason: Some(ErrorReason::init(kind, None)),
91 detail: Some(Box::new(ErrorDetail {
92 primary: (span, message),
93 labels: Vec::new(),
94 source: None,
95 source_name: None,
96 help: None,
97 })),
98 location: None,
99 severity: Severity::Error,
100 }
101 }
102
103 pub fn as_warning(mut self) -> Self {
104 self.severity = Severity::Warning;
105 self
106 }
107
108 pub fn severity(&self) -> Severity {
109 self.severity
110 }
111
112 pub fn with_location_from(mut self, index: &crate::line_index::LineIndex) -> Self {
120 if let Some(span) = self.span() {
121 let (line, col) = index.line_col(span.start);
122 self.location = Some((Arc::clone(index.source_name()), line, col));
123 }
124 self
125 }
126
127 pub fn with_primary_label(mut self, label: impl Into<String>) -> Self {
129 if let Some(d) = &mut self.detail {
130 d.primary.1 = label.into();
131 }
132 self
133 }
134
135 pub fn with_span(mut self, span: Span) -> Self {
140 match &mut self.detail {
141 Some(d) => d.primary.0 = span,
142 None => {
143 self.detail = Some(Box::new(ErrorDetail {
144 primary: (span, self.message.clone()),
145 labels: Vec::new(),
146 source: None,
147 source_name: None,
148 help: None,
149 }));
150 }
151 }
152 self
153 }
154
155 pub fn with_label(mut self, span: Span, label: impl Into<String>) -> Self {
157 if let Some(d) = &mut self.detail {
158 d.labels.push((span, label.into()));
159 }
160 self
161 }
162
163 pub fn with_source(mut self, source: Arc<String>) -> Self {
165 if let Some(d) = &mut self.detail {
166 d.source = Some(source);
167 }
168 self
169 }
170
171 pub fn with_source_name(mut self, name: impl Into<String>) -> Self {
173 if let Some(d) = &mut self.detail {
174 d.source_name = Some(name.into());
175 }
176 self
177 }
178
179 pub fn with_help(mut self, help: impl Into<String>) -> Self {
181 if let Some(d) = &mut self.detail {
182 d.help = Some(help.into());
183 }
184 self
185 }
186
187 pub fn with_source_file(mut self, file: &SourceFile) -> Self {
189 if let Some(d) = &mut self.detail {
190 d.source = Some(Arc::clone(&file.text));
191 d.source_name = Some(file.name.to_string());
192 }
193 self
194 }
195
196 pub fn print_error(&self) {
201 self.report_to_stderr();
202 panic!("rl error");
203 }
204
205 pub fn report_to_stderr(&self) {
208 if let Some(d) = &self.detail
209 && let Some(src) = &d.source
210 {
211 let name: &str = d.source_name.as_deref().unwrap_or("<source>");
212 let (sp, primary_label) = &d.primary;
213 let kind = match self.severity {
214 Severity::Error => ReportKind::Error,
215 Severity::Warning => ReportKind::Warning,
216 };
217 let label_color = match self.severity {
218 Severity::Error => Color::Red,
219 Severity::Warning => Color::Yellow,
220 };
221 let mut builder = Report::build(kind, (name, sp.start..sp.end))
222 .with_message(&self.message)
223 .with_label(
224 Label::new((name, sp.start..sp.end))
225 .with_message(primary_label)
226 .with_color(label_color),
227 );
228 for (lsp, label) in &d.labels {
229 builder = builder.with_label(
230 Label::new((name, lsp.start..lsp.end))
231 .with_message(label)
232 .with_color(Color::Yellow),
233 );
234 }
235 if let Some(help) = &d.help {
236 builder = builder.with_help(help);
237 }
238 let _ = builder.finish().eprint((name, Source::from(src.as_str())));
239 return;
240 }
241
242 self.fallback_text();
243 }
244
245 fn fallback_text(&self) {
251 let prefix = match self.severity {
252 Severity::Error => "Error",
253 Severity::Warning => "Warning",
254 };
255 match (&self.location, &self.line) {
256 (Some((name, line, col)), _) => {
257 println!("{}:{}:{}: [{}: {}]", name, line, col, prefix, self.message)
258 }
259 (None, Some(l)) => println!("[{}) {}: {}]", l, prefix, self.message),
260 (None, None) => println!("[{}: {}]", prefix, self.message),
261 }
262
263 if let Some(r) = &self.reason {
264 match &r.data {
265 Some(d) => {
266 println!("[{}]", r.get_type_string());
267 for l in d {
268 println!("{}", l);
269 }
270 }
271 _ => println!("[{}]", r.get_type_string()),
272 }
273 }
274 }
275
276 pub fn span(&self) -> Option<crate::span::Span> {
278 self.detail.as_ref().map(|d| d.primary.0)
279 }
280
281 pub fn source_text(&self) -> Option<&Arc<String>> {
285 self.detail.as_ref().and_then(|d| d.source.as_ref())
286 }
287
288 pub fn source_name(&self) -> Option<&str> {
290 self.detail
291 .as_ref()
292 .and_then(|d| d.source_name.as_deref())
293 }
294}
295
296impl ErrorReason {
297 pub fn init(error_type: Reason, data: Option<Vec<String>>) -> Self {
306 Self { error_type, data }
307 }
308
309 fn get_type_string(&self) -> String {
311 match &self.error_type {
312 Reason::Parse => "Parse Error",
313 Reason::AST => "AST Error",
314 Reason::Lexer => "Lexer Error",
315 Reason::Interpreter => "Interpreter Error",
316 Reason::Utils => "Utils Error",
317 Reason::Compile => "Compile Error",
318 Reason::Runtime => "Runtime Error",
319 }
320 .to_string()
321 }
322}
323
324impl Error {
325 pub fn message(&self) -> &str {
327 &self.message
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use crate::{
334 errors::{ErrorReason, Reason},
335 source::SourceFile,
336 span::Span,
337 };
338
339 use super::Error;
340
341 #[test]
342 fn error_basic() {
343 let span = Span::new(1, 5);
344 let error = Error::at(Reason::Parse, "syntax error", span);
345
346 assert_eq!(error.message(), "syntax error");
347 assert_eq!(error.span(), Some(span));
348 }
349
350 #[test]
351 fn test_error_builders() {
352 let span1 = Span::new(0, 3);
353 let span2 = Span::new(5, 8);
354
355 let err = Error::at(Reason::Compile, "type error", span1)
356 .with_primary_label("expected int")
357 .with_label(span2, "found string")
358 .with_help("try casting")
359 .with_source_name("main.rl");
360
361 assert_eq!(err.message(), "type error");
362 assert_eq!(err.span(), Some(span1));
363 }
364
365 #[test]
366 fn test_error_with_source_file() {
367 let span = Span::new(0, 5);
368 let source_file = SourceFile::new("main.rl", "print(\"foobar\")".to_string());
369
370 let err = Error::at(Reason::Lexer, "bad token", span).with_source_file(&source_file);
371
372 assert_eq!(err.span(), Some(span));
373 }
374
375 #[test]
376 fn test_span_override() {
377 let span_override = Span::new(1, 5);
378 let error =
379 Error::at(Reason::Parse, "syntax error", Span::new(0, 0)).with_span(span_override);
380
381 assert_eq!(error.message(), "syntax error");
382 assert_eq!(error.span(), Some(span_override));
383 }
384
385 #[test]
386 fn test_error_reason_string() {
387 let reason = ErrorReason::init(
388 Reason::Interpreter,
389 Some(vec!["stack overflow".to_string()]),
390 );
391 assert_eq!(reason.get_type_string(), "Interpreter Error");
392 }
393}