reqlang_expr/
errors.rs

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