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)]
10struct ErrorDetail {
11 primary: (Span, String),
13 labels: Vec<(Span, String)>,
15 source: Option<Arc<String>>,
17 source_name: Option<String>,
19 help: Option<String>,
21}
22
23#[derive(Debug, Clone)]
25pub struct Error {
26 message: String,
28 line: Option<usize>,
30 reason: Option<ErrorReason>,
32 detail: Option<Box<ErrorDetail>>,
34 location: Option<(Arc<str>, usize, usize)>,
40}
41
42#[derive(Debug, Clone)]
44pub struct ErrorReason {
45 error_type: Reason,
47 data: Option<Vec<String>>,
49}
50
51#[derive(Clone, Copy, Debug)]
53pub enum Reason {
54 Parse,
56 AST,
58 Lexer,
60 Interpreter,
62 Utils,
64 Compile,
66 Runtime,
68}
69
70impl Error {
71 pub fn at(kind: Reason, message: impl Into<String>, span: Span) -> Self {
74 let message = message.into();
75 #[cfg(feature = "debug")]
76 log::debug!("Error: {}", message);
77 Self {
78 message: message.clone(),
79 line: None,
80 reason: Some(ErrorReason::init(kind, None)),
81 detail: Some(Box::new(ErrorDetail {
82 primary: (span, message),
83 labels: Vec::new(),
84 source: None,
85 source_name: None,
86 help: None,
87 })),
88 location: None,
89 }
90 }
91
92 pub fn with_location_from(mut self, index: &crate::line_index::LineIndex) -> Self {
100 if let Some(span) = self.span() {
101 let (line, col) = index.line_col(span.start);
102 self.location = Some((Arc::clone(index.source_name()), line, col));
103 }
104 self
105 }
106
107 pub fn with_primary_label(mut self, label: impl Into<String>) -> Self {
109 if let Some(d) = &mut self.detail {
110 d.primary.1 = label.into();
111 }
112 self
113 }
114
115 pub fn with_span(mut self, span: Span) -> Self {
120 match &mut self.detail {
121 Some(d) => d.primary.0 = span,
122 None => {
123 self.detail = Some(Box::new(ErrorDetail {
124 primary: (span, self.message.clone()),
125 labels: Vec::new(),
126 source: None,
127 source_name: None,
128 help: None,
129 }));
130 }
131 }
132 self
133 }
134
135 pub fn with_label(mut self, span: Span, label: impl Into<String>) -> Self {
137 if let Some(d) = &mut self.detail {
138 d.labels.push((span, label.into()));
139 }
140 self
141 }
142
143 pub fn with_source(mut self, source: Arc<String>) -> Self {
145 if let Some(d) = &mut self.detail {
146 d.source = Some(source);
147 }
148 self
149 }
150
151 pub fn with_source_name(mut self, name: impl Into<String>) -> Self {
153 if let Some(d) = &mut self.detail {
154 d.source_name = Some(name.into());
155 }
156 self
157 }
158
159 pub fn with_help(mut self, help: impl Into<String>) -> Self {
161 if let Some(d) = &mut self.detail {
162 d.help = Some(help.into());
163 }
164 self
165 }
166
167 pub fn with_source_file(mut self, file: &SourceFile) -> Self {
169 if let Some(d) = &mut self.detail {
170 d.source = Some(Arc::clone(&file.text));
171 d.source_name = Some(file.name.to_string());
172 }
173 self
174 }
175
176 pub fn print_error(&self) {
181 self.report_to_stderr();
182 panic!("rl error");
183 }
184
185 pub fn report_to_stderr(&self) {
188 if let Some(d) = &self.detail
189 && let Some(src) = &d.source
190 {
191 let name: &str = d.source_name.as_deref().unwrap_or("<source>");
192 let (sp, primary_label) = &d.primary;
193 let mut builder = Report::build(ReportKind::Error, (name, sp.start..sp.end))
194 .with_message(&self.message)
195 .with_label(
196 Label::new((name, sp.start..sp.end))
197 .with_message(primary_label)
198 .with_color(Color::Red),
199 );
200 for (lsp, label) in &d.labels {
201 builder = builder.with_label(
202 Label::new((name, lsp.start..lsp.end))
203 .with_message(label)
204 .with_color(Color::Yellow),
205 );
206 }
207 if let Some(help) = &d.help {
208 builder = builder.with_help(help);
209 }
210 let _ = builder.finish().eprint((name, Source::from(src.as_str())));
211 return;
212 }
213
214 self.fallback_text();
215 }
216
217 fn fallback_text(&self) {
223 match (&self.location, &self.line) {
224 (Some((name, line, col)), _) => {
225 println!("{}:{}:{}: [Error: {}]", name, line, col, self.message)
226 }
227 (None, Some(l)) => println!("[{}) Error: {}]", l, self.message),
228 (None, None) => println!("[Error: {}]", self.message),
229 }
230
231 if let Some(r) = &self.reason {
232 match &r.data {
233 Some(d) => {
234 println!("[{}]", r.get_type_string());
235 for l in d {
236 println!("{}", l);
237 }
238 }
239 _ => println!("[{}]", r.get_type_string()),
240 }
241 }
242 }
243
244 pub fn span(&self) -> Option<crate::span::Span> {
246 self.detail.as_ref().map(|d| d.primary.0)
247 }
248}
249
250impl ErrorReason {
251 pub fn init(error_type: Reason, data: Option<Vec<String>>) -> Self {
260 Self { error_type, data }
261 }
262
263 fn get_type_string(&self) -> String {
265 match &self.error_type {
266 Reason::Parse => "Parse Error",
267 Reason::AST => "AST Error",
268 Reason::Lexer => "Lexer Error",
269 Reason::Interpreter => "Interpreter Error",
270 Reason::Utils => "Utils Error",
271 Reason::Compile => "Compile Error",
272 Reason::Runtime => "Runtime Error",
273 }
274 .to_string()
275 }
276}
277
278impl Error {
279 pub fn message(&self) -> &str {
281 &self.message
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use crate::{
288 errors::{ErrorReason, Reason},
289 source::SourceFile,
290 span::Span,
291 };
292
293 use super::Error;
294
295 #[test]
296 fn error_basic() {
297 let span = Span::new(1, 5);
298 let error = Error::at(Reason::Parse, "syntax error", span);
299
300 assert_eq!(error.message(), "syntax error");
301 assert_eq!(error.span(), Some(span));
302 }
303
304 #[test]
305 fn test_error_builders() {
306 let span1 = Span::new(0, 3);
307 let span2 = Span::new(5, 8);
308
309 let err = Error::at(Reason::Compile, "type error", span1)
310 .with_primary_label("expected int")
311 .with_label(span2, "found string")
312 .with_help("try casting")
313 .with_source_name("main.rl");
314
315 assert_eq!(err.message(), "type error");
316 assert_eq!(err.span(), Some(span1));
317 }
318
319 #[test]
320 fn test_error_with_source_file() {
321 let span = Span::new(0, 5);
322 let source_file = SourceFile::new("main.rl", "print(\"foobar\")".to_string());
323
324 let err = Error::at(Reason::Lexer, "bad token", span).with_source_file(&source_file);
325
326 assert_eq!(err.span(), Some(span));
327 }
328
329 #[test]
330 fn test_span_override() {
331 let span_override = Span::new(1, 5);
332 let error =
333 Error::at(Reason::Parse, "syntax error", Span::new(0, 0)).with_span(span_override);
334
335 assert_eq!(error.message(), "syntax error");
336 assert_eq!(error.span(), Some(span_override));
337 }
338
339 #[test]
340 fn test_error_reason_string() {
341 let reason = ErrorReason::init(
342 Reason::Interpreter,
343 Some(vec!["stack overflow".to_string()]),
344 );
345 assert_eq!(reason.get_type_string(), "Interpreter Error");
346 }
347}