reqlang_expr/
errors.rs

1//! Errors
2
3use lalrpop_util::ParseError;
4use thiserror::Error;
5
6use crate::{
7    errors::diagnostics::{ExprDiagnosisSeverity, ExprDiagnostic, get_range},
8    lexer::Token,
9    span::{Span, Spanned},
10    types::Type,
11};
12
13pub type ExprResult<T> = std::result::Result<T, Vec<ExprErrorS>>;
14
15#[derive(Debug, Error, PartialEq)]
16pub enum ExprError {
17    #[error("There was an error lexing expression: {0}")]
18    LexError(#[from] LexicalError),
19    #[error("There was an error in the expression syntax: {0}")]
20    SyntaxError(#[from] SyntaxError),
21    #[error("There was a compliation error with the expression: {0}")]
22    CompileError(#[from] CompileError),
23    #[error("There was a runtime error with the expression: {0}")]
24    RuntimeError(#[from] RuntimeError),
25}
26
27impl diagnostics::AsDiagnostic for ExprError {
28    fn as_diagnostic(&self, source: &str, span: &Span) -> ExprDiagnostic {
29        match self {
30            ExprError::LexError(e) => e.as_diagnostic(source, span),
31            ExprError::CompileError(e) => e.as_diagnostic(source, span),
32            ExprError::SyntaxError(e) => e.as_diagnostic(source, span),
33            ExprError::RuntimeError(e) => e.as_diagnostic(source, span),
34        }
35    }
36}
37
38#[derive(Default, Debug, Clone, PartialEq, Error)]
39pub enum LexicalError {
40    #[default]
41    #[error("Invalid token")]
42    InvalidToken,
43}
44
45impl diagnostics::AsDiagnostic for LexicalError {
46    fn as_diagnostic(&self, source: &str, span: &Span) -> ExprDiagnostic {
47        let error_code = "lexical".to_string();
48        match self {
49            LexicalError::InvalidToken => ExprDiagnostic {
50                code: error_code,
51                range: get_range(source, span),
52                severity: Some(ExprDiagnosisSeverity::ERROR),
53                message: format!("{self}"),
54            },
55        }
56    }
57}
58
59#[derive(Debug, Clone, Error, PartialEq)]
60pub enum SyntaxError {
61    #[error("extraneous input: {token:?}")]
62    ExtraToken { token: String },
63    #[error("invalid input")]
64    InvalidToken,
65    #[error("unexpected input: {token:?}")]
66    UnexpectedInput { token: String },
67    #[error("unexpected end of file; expected: {expected:?}")]
68    UnrecognizedEOF { expected: Vec<String> },
69    #[error("unexpected {token:?}; expected: {expected:?}")]
70    UnrecognizedToken {
71        token: String,
72        expected: Vec<String>,
73    },
74    #[error("unterminated string")]
75    UnterminatedString,
76}
77
78impl SyntaxError {
79    pub fn from_parser_error(
80        err: ParseError<usize, Token, ExprErrorS>,
81        source: &str,
82    ) -> ExprErrorS {
83        match err {
84            ParseError::InvalidToken { location } => {
85                (SyntaxError::InvalidToken.into(), location..location)
86            }
87            ParseError::UnrecognizedEof { location, expected } => (
88                SyntaxError::UnrecognizedEOF { expected }.into(),
89                location..location,
90            ),
91            ParseError::UnrecognizedToken {
92                token: (start, _, end),
93                expected,
94            } => (
95                SyntaxError::UnrecognizedToken {
96                    token: source[start..end].to_string(),
97                    expected,
98                }
99                .into(),
100                start..end,
101            ),
102            ParseError::ExtraToken {
103                token: (start, _, end),
104            } => (
105                SyntaxError::ExtraToken {
106                    token: source[start..end].to_string(),
107                }
108                .into(),
109                start..end,
110            ),
111            ParseError::User { error } => error,
112        }
113    }
114}
115
116impl diagnostics::AsDiagnostic for SyntaxError {
117    fn as_diagnostic(&self, source: &str, span: &Span) -> ExprDiagnostic {
118        let error_code = "syntax".to_string();
119        match self {
120            SyntaxError::ExtraToken { token: _ } => ExprDiagnostic {
121                code: error_code,
122                range: get_range(source, span),
123                severity: Some(ExprDiagnosisSeverity::ERROR),
124                message: format!("{self}"),
125            },
126            SyntaxError::InvalidToken => ExprDiagnostic {
127                code: error_code,
128                range: get_range(source, span),
129                severity: Some(ExprDiagnosisSeverity::ERROR),
130                message: format!("{self}"),
131            },
132            SyntaxError::UnexpectedInput { token: _ } => ExprDiagnostic {
133                code: error_code,
134                range: get_range(source, span),
135                severity: Some(ExprDiagnosisSeverity::ERROR),
136                message: format!("{self}"),
137            },
138            SyntaxError::UnrecognizedEOF { expected: _ } => ExprDiagnostic {
139                code: error_code,
140                range: get_range(source, span),
141                severity: Some(ExprDiagnosisSeverity::ERROR),
142                message: format!("{self}"),
143            },
144            SyntaxError::UnrecognizedToken {
145                token: _,
146                expected: _,
147            } => ExprDiagnostic {
148                code: error_code,
149                range: get_range(source, span),
150                severity: Some(ExprDiagnosisSeverity::ERROR),
151                message: format!("{self}"),
152            },
153            SyntaxError::UnterminatedString => ExprDiagnostic {
154                code: error_code,
155                range: get_range(source, span),
156                severity: Some(ExprDiagnosisSeverity::ERROR),
157                message: format!("{self}"),
158            },
159        }
160    }
161}
162
163#[derive(Debug, Clone, PartialEq, Error)]
164pub enum CompileError {
165    #[error("undefined: {0}")]
166    Undefined(String),
167    #[error("expects {expected} arguments but received {actual}")]
168    WrongNumberOfArgs { expected: usize, actual: usize },
169    #[error("call expression without a callee")]
170    NoCallee,
171    #[error("expected type {expected} but received {actual}")]
172    TypeMismatch { expected: Type, actual: Type },
173    #[error("invalid lookup type: {0}")]
174    InvalidLookupType(u8),
175}
176
177impl diagnostics::AsDiagnostic for CompileError {
178    fn as_diagnostic(&self, source: &str, span: &Span) -> ExprDiagnostic {
179        let error_code = "compiler".to_string();
180        match self {
181            CompileError::Undefined(_) => ExprDiagnostic {
182                code: error_code,
183                range: get_range(source, span),
184                severity: Some(ExprDiagnosisSeverity::ERROR),
185                message: format!("{self}"),
186            },
187            CompileError::WrongNumberOfArgs {
188                expected: _,
189                actual: _,
190            } => ExprDiagnostic {
191                code: error_code,
192                range: get_range(source, span),
193                severity: Some(ExprDiagnosisSeverity::ERROR),
194                message: format!("{self}"),
195            },
196            CompileError::NoCallee => ExprDiagnostic {
197                code: error_code,
198                range: get_range(source, span),
199                severity: Some(ExprDiagnosisSeverity::ERROR),
200                message: format!("{self}"),
201            },
202            CompileError::TypeMismatch {
203                expected: _,
204                actual: _,
205            } => ExprDiagnostic {
206                code: error_code,
207                range: get_range(source, span),
208                severity: Some(ExprDiagnosisSeverity::ERROR),
209                message: format!("{self}"),
210            },
211            CompileError::InvalidLookupType(_) => ExprDiagnostic {
212                code: error_code,
213                range: get_range(source, span),
214                severity: Some(ExprDiagnosisSeverity::ERROR),
215                message: format!("{self}"),
216            },
217        }
218    }
219}
220
221#[derive(Debug, Clone, PartialEq, Error)]
222pub enum RuntimeError {
223    #[error("attempting to pop from an empty stack")]
224    EmptyStack,
225    #[error("expected type {expected} but received {actual}")]
226    TypeMismatch { expected: Type, actual: Type },
227}
228
229impl diagnostics::AsDiagnostic for RuntimeError {
230    fn as_diagnostic(&self, source: &str, span: &Span) -> ExprDiagnostic {
231        let error_code = "runtime".to_string();
232        match self {
233            RuntimeError::EmptyStack => ExprDiagnostic {
234                code: error_code,
235                range: get_range(source, span),
236                severity: Some(ExprDiagnosisSeverity::ERROR),
237                message: format!("{self}"),
238            },
239            RuntimeError::TypeMismatch {
240                expected: _,
241                actual: _,
242            } => ExprDiagnostic {
243                code: error_code,
244                range: get_range(source, span),
245                severity: Some(ExprDiagnosisSeverity::ERROR),
246                message: format!("{self}"),
247            },
248        }
249    }
250}
251
252pub type ExprErrorS = Spanned<ExprError>;
253
254pub mod diagnostics {
255    use codespan_reporting::diagnostic::{Diagnostic, Label, Severity};
256    use line_col::LineColLookup;
257
258    use crate::{errors::ExprErrorS, span::Span};
259
260    pub fn get_diagnostics(errs: &[ExprErrorS], source: &str) -> Vec<Diagnostic<()>> {
261        errs.iter()
262            .map(|(err, span)| {
263                let a = err.as_diagnostic(source, span);
264                let b = a.to_diagnostic(span).with_message(a.message.clone());
265
266                b
267            })
268            .collect()
269    }
270
271    pub trait AsDiagnostic {
272        fn as_diagnostic(&self, source: &str, span: &Span) -> ExprDiagnostic;
273    }
274
275    #[derive(Debug, Eq, PartialEq, Clone, Default)]
276    pub struct ExprDiagnostic {
277        pub code: String,
278
279        pub range: ExprDiagnosticRange,
280
281        pub severity: Option<ExprDiagnosisSeverity>,
282
283        pub message: String,
284    }
285
286    impl ExprDiagnostic {
287        pub fn to_diagnostic(&self, span: &Span) -> codespan_reporting::diagnostic::Diagnostic<()> {
288            codespan_reporting::diagnostic::Diagnostic {
289                severity: ExprDiagnosisSeverity::ERROR.to_severity(),
290                code: Some(self.code.clone()),
291                message: self.message.clone(),
292                labels: vec![Label::primary((), span.clone())],
293                notes: vec![],
294            }
295        }
296    }
297
298    #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)]
299    pub struct ExprDiagnosisSeverity(i32);
300    #[allow(dead_code)]
301    impl ExprDiagnosisSeverity {
302        pub const ERROR: ExprDiagnosisSeverity = ExprDiagnosisSeverity(1);
303        pub const WARNING: ExprDiagnosisSeverity = ExprDiagnosisSeverity(2);
304        pub const INFORMATION: ExprDiagnosisSeverity = ExprDiagnosisSeverity(3);
305        pub const HINT: ExprDiagnosisSeverity = ExprDiagnosisSeverity(4);
306    }
307
308    impl ExprDiagnosisSeverity {
309        fn to_severity(&self) -> Severity {
310            match *self {
311                ExprDiagnosisSeverity::HINT => Severity::Help,
312                ExprDiagnosisSeverity::INFORMATION => Severity::Note,
313                ExprDiagnosisSeverity::WARNING => Severity::Warning,
314                ExprDiagnosisSeverity::ERROR => Severity::Error,
315                _ => panic!("Invalid diagnosis severity: {}", self.0),
316            }
317        }
318    }
319
320    #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Default)]
321    pub struct ExprDiagnosticPosition {
322        pub line: u32,
323        pub character: u32,
324    }
325
326    impl ExprDiagnosticPosition {
327        pub fn new(line: u32, character: u32) -> ExprDiagnosticPosition {
328            ExprDiagnosticPosition { line, character }
329        }
330    }
331
332    #[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
333    pub struct ExprDiagnosticRange {
334        /// The range's start position (inclusive)
335        pub start: ExprDiagnosticPosition,
336        /// The range's end position (exclusive)
337        pub end: ExprDiagnosticPosition,
338    }
339
340    impl ExprDiagnosticRange {
341        pub fn new(
342            start: ExprDiagnosticPosition,
343            end: ExprDiagnosticPosition,
344        ) -> ExprDiagnosticRange {
345            ExprDiagnosticRange { start, end }
346        }
347    }
348
349    pub fn get_range(source: &str, span: &Span) -> ExprDiagnosticRange {
350        ExprDiagnosticRange::new(
351            get_position(source, span.start),
352            get_position(source, span.end),
353        )
354    }
355
356    pub fn get_position(source: &str, idx: usize) -> ExprDiagnosticPosition {
357        let (line, character) = index_to_position(source, idx);
358
359        ExprDiagnosticPosition::new(line as u32, character as u32)
360    }
361
362    /// Map index to position (line, column)
363    ///
364    /// Line and column are zero based
365    pub fn index_to_position(source: &str, index: usize) -> (usize, usize) {
366        let lookup = LineColLookup::new(source);
367
368        let (line, char) = lookup.get(index);
369
370        (line - 1, char - 1)
371    }
372
373    /// Map position (line, column) to index
374    ///
375    /// Line and column are zero based
376    pub fn position_to_index(source: &str, position: (usize, usize)) -> usize {
377        let (line, character) = position;
378        let lines = source.split('\n');
379        let lines_before = lines.take(line);
380        let line_chars_before = lines_before.fold(0usize, |acc, e| acc + e.len() + 1);
381        let chars = character;
382
383        line_chars_before + chars
384    }
385
386    #[cfg(test)]
387    mod index_position_fn_tests {
388        use super::*;
389
390        #[test]
391        fn it_should_convert_index_to_position() {
392            let source = "let a = 123;\nlet b = 456;";
393
394            let index = 17usize;
395            let expected_position = (1, 4);
396
397            let index_to_position = index_to_position(source, index);
398            let actual_position = index_to_position;
399
400            assert_eq!(expected_position, actual_position);
401        }
402
403        #[test]
404        fn it_should_convert_position_to_index() {
405            let source = "let a = 123;\nlet b = 456;";
406            let position = (1, 4);
407            let expected_index = 17usize;
408            let actual_index = position_to_index(source, position);
409
410            assert_eq!(expected_index, actual_index);
411        }
412
413        #[test]
414        fn it_should_convert_position_to_index_and_back() {
415            let source = "let a = 123;\nlet b = 456;";
416            let position = (1, 4);
417            let actual_index = position_to_index(source, position);
418
419            assert_eq!(position, index_to_position(source, actual_index));
420        }
421
422        #[test]
423        fn it_should_convert_position_to_index_and_back_b() {
424            let source = "let a = 123;\n{\n    let b = 456;\n}";
425            let position = (2, 12);
426            let actual_index = position_to_index(source, position);
427
428            assert_eq!(position, index_to_position(source, actual_index));
429        }
430
431        #[test]
432        fn it_should_convert_position_to_index_b() {
433            let source = "let a = 123;\n{\n    let b = 456;\n}";
434            let position = (2, 12);
435            let actual_index = position_to_index(source, position);
436
437            assert_eq!(27, actual_index);
438        }
439
440        #[test]
441        fn it_should_convert_position_to_index_c() {
442            let source = "let a = 123;\nlet b = 456;\nlet c = 789;";
443            let position = (2, 8);
444            let actual_index = position_to_index(source, position);
445
446            assert_eq!(34, actual_index);
447        }
448
449        #[test]
450        fn it_should_convert_position_to_index_d() {
451            let source = "let a = 123;\nlet b = 456;\nlet c = 789;\nlet d = 000;";
452            let position = (3, 8);
453            let actual_index = position_to_index(source, position);
454
455            assert_eq!(47, actual_index);
456        }
457
458        #[test]
459        fn it_should_convert_position_to_index_e() {
460            let source = "let a = 123;\nlet b = 456;\nlet c = 789;\nlet d = 000;\nlet e = 999;";
461            let position = (4, 8);
462            let actual_index = position_to_index(source, position);
463
464            assert_eq!(60, actual_index);
465        }
466
467        #[test]
468        fn it_should_convert_position_to_index_f() {
469            let source = "let a = 123;\nlet b = 456;\nlet c = 789;\nlet d = 000;\nlet e = 999;\n";
470            let position = (4, 8);
471            let actual_index = position_to_index(source, position);
472
473            assert_eq!(60, actual_index);
474        }
475    }
476
477    #[cfg(test)]
478    mod error_to_diagnostics_tests {
479        use crate::errors::{CompileError, ExprError, LexicalError};
480
481        use super::*;
482        use std::ops::Range;
483
484        fn dummy_source() -> &'static str {
485            "fn test_function(x: i32) -> i32 { x + 1 }"
486        }
487
488        fn dummy_range() -> Span {
489            Range { start: 0, end: 5 }
490        }
491
492        #[test]
493        fn it_converts_lexerror_to_diagnostic() {
494            let source = dummy_source();
495            let range = dummy_range();
496            let error = ExprError::LexError(LexicalError::InvalidToken);
497            let diagnostics = get_diagnostics(&[(error, range.clone())], source);
498
499            assert_eq!(diagnostics.len(), 1);
500            let diagnostic = &diagnostics[0];
501            assert_eq!(diagnostic.code, Some("lexical".to_string()));
502            assert_eq!(diagnostic.message, "Invalid token".to_string());
503            assert_eq!(diagnostic.severity, Severity::Error);
504            assert_eq!(diagnostic.labels.len(), 1);
505            assert_eq!(diagnostic.labels[0], Label::primary((), range));
506        }
507
508        #[test]
509        fn it_converts_compileerror_undefined_to_diagnostic() {
510            let source = dummy_source();
511            let range = dummy_range();
512            let error = ExprError::CompileError(CompileError::Undefined("var".to_string()));
513            let diagnostics = get_diagnostics(&[(error, range.clone())], source);
514
515            assert_eq!(diagnostics.len(), 1);
516            let diagnostic = &diagnostics[0];
517            assert_eq!(diagnostic.code, Some("compiler".to_string()));
518            assert_eq!(diagnostic.message, "undefined: var".to_string());
519            assert_eq!(diagnostic.severity, Severity::Error);
520            assert_eq!(diagnostic.labels.len(), 1);
521            assert_eq!(diagnostic.labels[0], Label::primary((), range));
522        }
523
524        #[test]
525        fn it_converts_compileerror_wrong_number_of_args_to_diagnostic() {
526            let source = dummy_source();
527            let range = dummy_range();
528            let error = ExprError::CompileError(CompileError::WrongNumberOfArgs {
529                expected: 2,
530                actual: 3,
531            });
532            let diagnostics = get_diagnostics(&[(error, range.clone())], source);
533
534            assert_eq!(diagnostics.len(), 1);
535            let diagnostic = &diagnostics[0];
536            assert_eq!(diagnostic.code, Some("compiler".to_string()));
537            assert_eq!(
538                diagnostic.message,
539                "expects 2 arguments but received 3".to_string()
540            );
541            assert_eq!(diagnostic.severity, Severity::Error);
542            assert_eq!(diagnostic.labels.len(), 1);
543            assert_eq!(diagnostic.labels[0], Label::primary((), range));
544        }
545    }
546}