reqlang_expr/
errors.rs

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