Skip to main content

reqlang_expr/
errors.rs

1//! Errors
2
3use std::num::ParseFloatError;
4
5use lalrpop_util::ParseError;
6use thiserror::Error;
7
8use crate::{
9    errors::diagnostics::{ExprDiagnosisSeverity, ExprDiagnostic, get_range},
10    lexer::Token,
11    span::{Span, Spanned},
12    types::Type,
13};
14
15pub type ExprResult<T> = std::result::Result<T, Vec<ExprErrorS>>;
16
17#[derive(Debug, Error, PartialEq)]
18pub enum ExprError {
19    #[error("There was an error lexing expression: {0}")]
20    LexError(#[from] LexicalError),
21    #[error("There was an error in the expression syntax: {0}")]
22    SyntaxError(#[from] SyntaxError),
23    #[error("There was a compliation error with the expression: {0}")]
24    CompileError(#[from] CompileError),
25    #[error("There was a runtime error with the expression: {0}")]
26    RuntimeError(#[from] RuntimeError),
27}
28
29impl diagnostics::AsDiagnostic for ExprError {
30    fn as_diagnostic(&self, source: &str, span: &Span) -> ExprDiagnostic {
31        match self {
32            ExprError::LexError(e) => e.as_diagnostic(source, span),
33            ExprError::CompileError(e) => e.as_diagnostic(source, span),
34            ExprError::SyntaxError(e) => e.as_diagnostic(source, span),
35            ExprError::RuntimeError(e) => e.as_diagnostic(source, span),
36        }
37    }
38}
39
40#[derive(Default, Debug, Clone, PartialEq, Error)]
41pub enum LexicalError {
42    #[default]
43    #[error("Invalid token")]
44    InvalidToken,
45    #[error("Invalid number $0")]
46    InvalidNumber(ParseFloatError),
47}
48
49impl diagnostics::AsDiagnostic for LexicalError {
50    fn as_diagnostic(&self, source: &str, span: &Span) -> ExprDiagnostic {
51        let error_code = "lexical".to_string();
52        ExprDiagnostic {
53            code: error_code,
54            range: get_range(source, span),
55            severity: Some(ExprDiagnosisSeverity::ERROR),
56            message: format!("{self}"),
57        }
58    }
59}
60
61#[derive(Debug, Clone, Error, PartialEq)]
62pub enum SyntaxError {
63    #[error("extraneous input: {token:?}")]
64    ExtraToken { token: String },
65    #[error("invalid input")]
66    InvalidToken,
67    #[error("unexpected input: {token:?}")]
68    UnexpectedInput { token: String },
69    #[error("unexpected end of file; expected: {expected:?}")]
70    UnrecognizedEOF { expected: Vec<String> },
71    #[error("unexpected {token:?}; expected: {expected:?}")]
72    UnrecognizedToken {
73        token: String,
74        expected: Vec<String>,
75    },
76    #[error("unterminated string")]
77    UnterminatedString,
78}
79
80impl SyntaxError {
81    pub fn from_parser_error(
82        err: ParseError<usize, Token, ExprErrorS>,
83        source: &str,
84    ) -> ExprErrorS {
85        match err {
86            ParseError::InvalidToken { location } => {
87                (SyntaxError::InvalidToken.into(), location..location)
88            }
89            ParseError::UnrecognizedEof { location, expected } => (
90                SyntaxError::UnrecognizedEOF { expected }.into(),
91                location..location,
92            ),
93            ParseError::UnrecognizedToken {
94                token: (start, _, end),
95                expected,
96            } => (
97                SyntaxError::UnrecognizedToken {
98                    token: source[start..end].to_string(),
99                    expected,
100                }
101                .into(),
102                start..end,
103            ),
104            ParseError::ExtraToken {
105                token: (start, _, end),
106            } => (
107                SyntaxError::ExtraToken {
108                    token: source[start..end].to_string(),
109                }
110                .into(),
111                start..end,
112            ),
113            ParseError::User { error } => error,
114        }
115    }
116}
117
118impl diagnostics::AsDiagnostic for SyntaxError {
119    fn as_diagnostic(&self, source: &str, span: &Span) -> ExprDiagnostic {
120        let error_code = "syntax".to_string();
121        match self {
122            SyntaxError::ExtraToken { token: _ } => ExprDiagnostic {
123                code: error_code,
124                range: get_range(source, span),
125                severity: Some(ExprDiagnosisSeverity::ERROR),
126                message: format!("{self}"),
127            },
128            SyntaxError::InvalidToken => ExprDiagnostic {
129                code: error_code,
130                range: get_range(source, span),
131                severity: Some(ExprDiagnosisSeverity::ERROR),
132                message: format!("{self}"),
133            },
134            SyntaxError::UnexpectedInput { token: _ } => ExprDiagnostic {
135                code: error_code,
136                range: get_range(source, span),
137                severity: Some(ExprDiagnosisSeverity::ERROR),
138                message: format!("{self}"),
139            },
140            SyntaxError::UnrecognizedEOF { expected: _ } => ExprDiagnostic {
141                code: error_code,
142                range: get_range(source, span),
143                severity: Some(ExprDiagnosisSeverity::ERROR),
144                message: format!("{self}"),
145            },
146            SyntaxError::UnrecognizedToken {
147                token: _,
148                expected: _,
149            } => ExprDiagnostic {
150                code: error_code,
151                range: get_range(source, span),
152                severity: Some(ExprDiagnosisSeverity::ERROR),
153                message: format!("{self}"),
154            },
155            SyntaxError::UnterminatedString => ExprDiagnostic {
156                code: error_code,
157                range: get_range(source, span),
158                severity: Some(ExprDiagnosisSeverity::ERROR),
159                message: format!("{self}"),
160            },
161        }
162    }
163}
164
165#[derive(Debug, Clone, PartialEq, Error)]
166pub enum CompileError {
167    #[error("undefined: {0}")]
168    Undefined(String),
169    #[error("expects {expected} arguments but received {actual}")]
170    WrongNumberOfArgs { expected: usize, actual: usize },
171    #[error("call expression without a callee")]
172    NoCallee,
173    #[error("expected type {expected} but received {actual}")]
174    TypeMismatch { expected: Type, actual: Type },
175    #[error("invalid lookup type: {0}")]
176    InvalidLookupType(u8),
177}
178
179impl diagnostics::AsDiagnostic for CompileError {
180    fn as_diagnostic(&self, source: &str, span: &Span) -> ExprDiagnostic {
181        let error_code = "compiler".to_string();
182        match self {
183            CompileError::Undefined(_) => ExprDiagnostic {
184                code: error_code,
185                range: get_range(source, span),
186                severity: Some(ExprDiagnosisSeverity::ERROR),
187                message: format!("{self}"),
188            },
189            CompileError::WrongNumberOfArgs {
190                expected: _,
191                actual: _,
192            } => ExprDiagnostic {
193                code: error_code,
194                range: get_range(source, span),
195                severity: Some(ExprDiagnosisSeverity::ERROR),
196                message: format!("{self}"),
197            },
198            CompileError::NoCallee => ExprDiagnostic {
199                code: error_code,
200                range: get_range(source, span),
201                severity: Some(ExprDiagnosisSeverity::ERROR),
202                message: format!("{self}"),
203            },
204            CompileError::TypeMismatch {
205                expected: _,
206                actual: _,
207            } => ExprDiagnostic {
208                code: error_code,
209                range: get_range(source, span),
210                severity: Some(ExprDiagnosisSeverity::ERROR),
211                message: format!("{self}"),
212            },
213            CompileError::InvalidLookupType(_) => ExprDiagnostic {
214                code: error_code,
215                range: get_range(source, span),
216                severity: Some(ExprDiagnosisSeverity::ERROR),
217                message: format!("{self}"),
218            },
219        }
220    }
221}
222
223#[derive(Debug, Clone, PartialEq, Error)]
224pub enum RuntimeError {
225    #[error("attempting to pop from an empty stack")]
226    EmptyStack,
227    #[error("expected type {expected} but received {actual}")]
228    TypeMismatch { expected: Type, actual: Type },
229}
230
231impl diagnostics::AsDiagnostic for RuntimeError {
232    fn as_diagnostic(&self, source: &str, span: &Span) -> ExprDiagnostic {
233        let error_code = "runtime".to_string();
234        match self {
235            RuntimeError::EmptyStack => ExprDiagnostic {
236                code: error_code,
237                range: get_range(source, span),
238                severity: Some(ExprDiagnosisSeverity::ERROR),
239                message: format!("{self}"),
240            },
241            RuntimeError::TypeMismatch {
242                expected: _,
243                actual: _,
244            } => ExprDiagnostic {
245                code: error_code,
246                range: get_range(source, span),
247                severity: Some(ExprDiagnosisSeverity::ERROR),
248                message: format!("{self}"),
249            },
250        }
251    }
252}
253
254pub type ExprErrorS = Spanned<ExprError>;
255
256pub mod diagnostics {
257    use codespan_reporting::diagnostic::{Diagnostic, Label, Severity};
258    use line_col::LineColLookup;
259
260    use crate::{errors::ExprErrorS, span::Span};
261
262    pub fn get_diagnostics(errs: &[ExprErrorS], source: &str) -> Vec<Diagnostic<()>> {
263        errs.iter()
264            .map(|(err, span)| {
265                let a = err.as_diagnostic(source, span);
266
267                a.to_diagnostic(span).with_message(a.message.clone())
268            })
269            .collect()
270    }
271
272    pub trait AsDiagnostic {
273        fn as_diagnostic(&self, source: &str, span: &Span) -> ExprDiagnostic;
274    }
275
276    #[derive(Debug, Eq, PartialEq, Clone, Default)]
277    pub struct ExprDiagnostic {
278        pub code: String,
279
280        pub range: ExprDiagnosticRange,
281
282        pub severity: Option<ExprDiagnosisSeverity>,
283
284        pub message: String,
285    }
286
287    impl ExprDiagnostic {
288        pub fn to_diagnostic(&self, span: &Span) -> codespan_reporting::diagnostic::Diagnostic<()> {
289            codespan_reporting::diagnostic::Diagnostic {
290                severity: ExprDiagnosisSeverity::ERROR.to_severity(),
291                code: Some(self.code.clone()),
292                message: self.message.clone(),
293                labels: vec![Label::primary((), span.clone())],
294                notes: vec![],
295            }
296        }
297    }
298
299    #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)]
300    pub struct ExprDiagnosisSeverity(i32);
301    #[allow(dead_code)]
302    impl ExprDiagnosisSeverity {
303        pub const ERROR: ExprDiagnosisSeverity = ExprDiagnosisSeverity(1);
304        pub const WARNING: ExprDiagnosisSeverity = ExprDiagnosisSeverity(2);
305        pub const INFORMATION: ExprDiagnosisSeverity = ExprDiagnosisSeverity(3);
306        pub const HINT: ExprDiagnosisSeverity = ExprDiagnosisSeverity(4);
307    }
308
309    impl ExprDiagnosisSeverity {
310        fn to_severity(self) -> Severity {
311            match self {
312                ExprDiagnosisSeverity::HINT => Severity::Help,
313                ExprDiagnosisSeverity::INFORMATION => Severity::Note,
314                ExprDiagnosisSeverity::WARNING => Severity::Warning,
315                ExprDiagnosisSeverity::ERROR => Severity::Error,
316                _ => panic!("Invalid diagnosis severity: {}", self.0),
317            }
318        }
319    }
320
321    #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Default)]
322    pub struct ExprDiagnosticPosition {
323        pub line: u32,
324        pub character: u32,
325    }
326
327    impl ExprDiagnosticPosition {
328        pub fn new(line: u32, character: u32) -> ExprDiagnosticPosition {
329            ExprDiagnosticPosition { line, character }
330        }
331    }
332
333    #[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
334    pub struct ExprDiagnosticRange {
335        /// The range's start position (inclusive)
336        pub start: ExprDiagnosticPosition,
337        /// The range's end position (exclusive)
338        pub end: ExprDiagnosticPosition,
339    }
340
341    impl ExprDiagnosticRange {
342        pub fn new(
343            start: ExprDiagnosticPosition,
344            end: ExprDiagnosticPosition,
345        ) -> ExprDiagnosticRange {
346            ExprDiagnosticRange { start, end }
347        }
348    }
349
350    pub fn get_range(source: &str, span: &Span) -> ExprDiagnosticRange {
351        ExprDiagnosticRange::new(
352            get_position(source, span.start),
353            get_position(source, span.end),
354        )
355    }
356
357    pub fn get_position(source: &str, idx: usize) -> ExprDiagnosticPosition {
358        let (line, character) = index_to_position(source, idx);
359
360        ExprDiagnosticPosition::new(line as u32, character as u32)
361    }
362
363    /// Map index to position (line, column)
364    ///
365    /// Line and column are zero based
366    pub fn index_to_position(source: &str, index: usize) -> (usize, usize) {
367        let lookup = LineColLookup::new(source);
368
369        let (line, char) = lookup.get(index);
370
371        (line - 1, char - 1)
372    }
373
374    /// Map position (line, column) to index
375    ///
376    /// Line and column are zero based
377    pub fn position_to_index(source: &str, position: (usize, usize)) -> usize {
378        let (line, character) = position;
379        let lines = source.split('\n');
380        let lines_before = lines.take(line);
381        let line_chars_before = lines_before.fold(0usize, |acc, e| acc + e.len() + 1);
382        let chars = character;
383
384        line_chars_before + chars
385    }
386
387    #[cfg(test)]
388    mod index_position_fn_tests {
389        use super::*;
390
391        #[test]
392        fn it_should_convert_index_to_position() {
393            let source = "let a = 123;\nlet b = 456;";
394
395            let index = 17usize;
396            let expected_position = (1, 4);
397
398            let index_to_position = index_to_position(source, index);
399            let actual_position = index_to_position;
400
401            assert_eq!(expected_position, actual_position);
402        }
403
404        #[test]
405        fn it_should_convert_position_to_index() {
406            let source = "let a = 123;\nlet b = 456;";
407            let position = (1, 4);
408            let expected_index = 17usize;
409            let actual_index = position_to_index(source, position);
410
411            assert_eq!(expected_index, actual_index);
412        }
413
414        #[test]
415        fn it_should_convert_position_to_index_and_back() {
416            let source = "let a = 123;\nlet b = 456;";
417            let position = (1, 4);
418            let actual_index = position_to_index(source, position);
419
420            assert_eq!(position, index_to_position(source, actual_index));
421        }
422
423        #[test]
424        fn it_should_convert_position_to_index_and_back_b() {
425            let source = "let a = 123;\n{\n    let b = 456;\n}";
426            let position = (2, 12);
427            let actual_index = position_to_index(source, position);
428
429            assert_eq!(position, index_to_position(source, actual_index));
430        }
431
432        #[test]
433        fn it_should_convert_position_to_index_b() {
434            let source = "let a = 123;\n{\n    let b = 456;\n}";
435            let position = (2, 12);
436            let actual_index = position_to_index(source, position);
437
438            assert_eq!(27, actual_index);
439        }
440
441        #[test]
442        fn it_should_convert_position_to_index_c() {
443            let source = "let a = 123;\nlet b = 456;\nlet c = 789;";
444            let position = (2, 8);
445            let actual_index = position_to_index(source, position);
446
447            assert_eq!(34, actual_index);
448        }
449
450        #[test]
451        fn it_should_convert_position_to_index_d() {
452            let source = "let a = 123;\nlet b = 456;\nlet c = 789;\nlet d = 000;";
453            let position = (3, 8);
454            let actual_index = position_to_index(source, position);
455
456            assert_eq!(47, actual_index);
457        }
458
459        #[test]
460        fn it_should_convert_position_to_index_e() {
461            let source = "let a = 123;\nlet b = 456;\nlet c = 789;\nlet d = 000;\nlet e = 999;";
462            let position = (4, 8);
463            let actual_index = position_to_index(source, position);
464
465            assert_eq!(60, actual_index);
466        }
467
468        #[test]
469        fn it_should_convert_position_to_index_f() {
470            let source = "let a = 123;\nlet b = 456;\nlet c = 789;\nlet d = 000;\nlet e = 999;\n";
471            let position = (4, 8);
472            let actual_index = position_to_index(source, position);
473
474            assert_eq!(60, actual_index);
475        }
476    }
477
478    #[cfg(test)]
479    mod error_to_diagnostics_tests {
480        use crate::{
481            errors::{CompileError, ExprError, LexicalError, RuntimeError, SyntaxError},
482            types::Type,
483        };
484
485        use super::*;
486        use std::ops::Range;
487
488        fn dummy_source() -> &'static str {
489            "fn test_function(x: i32) -> i32 { x + 1 }"
490        }
491
492        fn dummy_range() -> Span {
493            Range { start: 0, end: 5 }
494        }
495
496        #[test]
497        fn it_converts_lexerror_to_diagnostic() {
498            let source = dummy_source();
499            let range = dummy_range();
500            let error = ExprError::LexError(LexicalError::InvalidToken);
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("lexical".to_string()));
506            assert_eq!(diagnostic.message, "Invalid token".to_string());
507            assert_eq!(diagnostic.severity, Severity::Error);
508            assert_eq!(diagnostic.labels.len(), 1);
509            assert_eq!(diagnostic.labels[0], Label::primary((), range));
510        }
511
512        #[test]
513        fn it_converts_compileerror_undefined_to_diagnostic() {
514            let source = dummy_source();
515            let range = dummy_range();
516            let error = ExprError::CompileError(CompileError::Undefined("var".to_string()));
517            let diagnostics = get_diagnostics(&[(error, range.clone())], source);
518
519            assert_eq!(diagnostics.len(), 1);
520            let diagnostic = &diagnostics[0];
521            assert_eq!(diagnostic.code, Some("compiler".to_string()));
522            assert_eq!(diagnostic.message, "undefined: var".to_string());
523            assert_eq!(diagnostic.severity, Severity::Error);
524            assert_eq!(diagnostic.labels.len(), 1);
525            assert_eq!(diagnostic.labels[0], Label::primary((), range));
526        }
527
528        #[test]
529        fn it_converts_compileerror_wrong_number_of_args_to_diagnostic() {
530            let source = dummy_source();
531            let range = dummy_range();
532            let error = ExprError::CompileError(CompileError::WrongNumberOfArgs {
533                expected: 2,
534                actual: 3,
535            });
536            let diagnostics = get_diagnostics(&[(error, range.clone())], source);
537
538            assert_eq!(diagnostics.len(), 1);
539            let diagnostic = &diagnostics[0];
540            assert_eq!(diagnostic.code, Some("compiler".to_string()));
541            assert_eq!(
542                diagnostic.message,
543                "expects 2 arguments but received 3".to_string()
544            );
545            assert_eq!(diagnostic.severity, Severity::Error);
546            assert_eq!(diagnostic.labels.len(), 1);
547            assert_eq!(diagnostic.labels[0], Label::primary((), range));
548        }
549
550        #[test]
551        fn it_converts_compileerror_no_callee_to_diagnostic() {
552            let source = dummy_source();
553            let range = dummy_range();
554            let error = ExprError::CompileError(CompileError::NoCallee);
555            let diagnostics = get_diagnostics(&[(error, range.clone())], source);
556
557            assert_eq!(diagnostics.len(), 1);
558            let diagnostic = &diagnostics[0];
559            assert_eq!(diagnostic.code, Some("compiler".to_string()));
560            assert_eq!(
561                diagnostic.message,
562                "call expression without a callee".to_string()
563            );
564            assert_eq!(diagnostic.severity, Severity::Error);
565            assert_eq!(diagnostic.labels.len(), 1);
566            assert_eq!(diagnostic.labels[0], Label::primary((), range));
567        }
568
569        #[test]
570        fn it_converts_compileerror_type_mismatch_to_diagnostic() {
571            let source = dummy_source();
572            let range = dummy_range();
573            let error = ExprError::CompileError(CompileError::TypeMismatch {
574                expected: Type::String,
575                actual: Type::Bool,
576            });
577            let diagnostics = get_diagnostics(&[(error, range.clone())], source);
578
579            assert_eq!(diagnostics.len(), 1);
580            let diagnostic = &diagnostics[0];
581            assert_eq!(diagnostic.code, Some("compiler".to_string()));
582            assert_eq!(
583                diagnostic.message,
584                "expected type String but received Bool".to_string()
585            );
586            assert_eq!(diagnostic.severity, Severity::Error);
587            assert_eq!(diagnostic.labels.len(), 1);
588            assert_eq!(diagnostic.labels[0], Label::primary((), range));
589        }
590
591        #[test]
592        fn it_converts_compileerror_invalid_lookup_type_to_diagnostic() {
593            let source = dummy_source();
594            let range = dummy_range();
595            let error = ExprError::CompileError(CompileError::InvalidLookupType(99));
596            let diagnostics = get_diagnostics(&[(error, range.clone())], source);
597
598            assert_eq!(diagnostics.len(), 1);
599            let diagnostic = &diagnostics[0];
600            assert_eq!(diagnostic.code, Some("compiler".to_string()));
601            assert_eq!(diagnostic.message, "invalid lookup type: 99".to_string());
602            assert_eq!(diagnostic.severity, Severity::Error);
603            assert_eq!(diagnostic.labels.len(), 1);
604            assert_eq!(diagnostic.labels[0], Label::primary((), range));
605        }
606
607        #[test]
608        fn it_converts_runtimeerror_undefined_to_diagnostic() {
609            let source = dummy_source();
610            let range = dummy_range();
611            let error = ExprError::RuntimeError(RuntimeError::TypeMismatch {
612                expected: Type::Bool,
613                actual: Type::String,
614            });
615            let diagnostics = get_diagnostics(&[(error, range.clone())], source);
616
617            assert_eq!(diagnostics.len(), 1);
618            let diagnostic = &diagnostics[0];
619            assert_eq!(diagnostic.code, Some("runtime".to_string()));
620            assert_eq!(
621                diagnostic.message,
622                "expected type Bool but received String".to_string()
623            );
624            assert_eq!(diagnostic.severity, Severity::Error);
625            assert_eq!(diagnostic.labels.len(), 1);
626            assert_eq!(diagnostic.labels[0], Label::primary((), range));
627        }
628
629        #[test]
630        fn it_converts_runtimeerror_empty_stack_to_diagnostic() {
631            let source = dummy_source();
632            let range = dummy_range();
633            let error = ExprError::RuntimeError(RuntimeError::EmptyStack);
634            let diagnostics = get_diagnostics(&[(error, range.clone())], source);
635
636            assert_eq!(diagnostics.len(), 1);
637            let diagnostic = &diagnostics[0];
638            assert_eq!(diagnostic.code, Some("runtime".to_string()));
639            assert_eq!(
640                diagnostic.message,
641                "attempting to pop from an empty stack".to_string()
642            );
643            assert_eq!(diagnostic.severity, Severity::Error);
644            assert_eq!(diagnostic.labels.len(), 1);
645            assert_eq!(diagnostic.labels[0], Label::primary((), range));
646        }
647
648        #[test]
649        fn it_converts_syntaxerror_unrecognized_eof_to_diagnostic() {
650            let source = dummy_source();
651            let range = dummy_range();
652            let error = ExprError::SyntaxError(SyntaxError::UnrecognizedEOF {
653                expected: vec!["string".to_string()],
654            });
655            let diagnostics = get_diagnostics(&[(error, range.clone())], source);
656
657            assert_eq!(diagnostics.len(), 1);
658            let diagnostic = &diagnostics[0];
659            assert_eq!(diagnostic.code, Some("syntax".to_string()));
660            assert_eq!(
661                diagnostic.message,
662                "unexpected end of file; expected: [\"string\"]".to_string()
663            );
664            assert_eq!(diagnostic.severity, Severity::Error);
665            assert_eq!(diagnostic.labels.len(), 1);
666            assert_eq!(diagnostic.labels[0], Label::primary((), range));
667        }
668
669        #[test]
670        fn it_converts_syntaxerror_invalid_token_to_diagnostic() {
671            let source = dummy_source();
672            let range = dummy_range();
673            let error = ExprError::SyntaxError(SyntaxError::InvalidToken);
674            let diagnostics = get_diagnostics(&[(error, range.clone())], source);
675
676            assert_eq!(diagnostics.len(), 1);
677            let diagnostic = &diagnostics[0];
678            assert_eq!(diagnostic.code, Some("syntax".to_string()));
679            assert_eq!(diagnostic.message, "invalid input".to_string());
680            assert_eq!(diagnostic.severity, Severity::Error);
681            assert_eq!(diagnostic.labels.len(), 1);
682            assert_eq!(diagnostic.labels[0], Label::primary((), range));
683        }
684
685        #[test]
686        fn it_converts_syntaxerror_unexpected_input_to_diagnostic() {
687            let source = dummy_source();
688            let range = dummy_range();
689            let error = ExprError::SyntaxError(SyntaxError::UnexpectedInput {
690                token: "number".to_string(),
691            });
692            let diagnostics = get_diagnostics(&[(error, range.clone())], source);
693
694            assert_eq!(diagnostics.len(), 1);
695            let diagnostic = &diagnostics[0];
696            assert_eq!(diagnostic.code, Some("syntax".to_string()));
697            assert_eq!(
698                diagnostic.message,
699                "unexpected input: \"number\"".to_string()
700            );
701            assert_eq!(diagnostic.severity, Severity::Error);
702            assert_eq!(diagnostic.labels.len(), 1);
703            assert_eq!(diagnostic.labels[0], Label::primary((), range));
704        }
705
706        #[test]
707        fn it_converts_syntaxerror_unrecognized_token_to_diagnostic() {
708            let source = dummy_source();
709            let range = dummy_range();
710            let error = ExprError::SyntaxError(SyntaxError::UnrecognizedToken {
711                token: "number".to_string(),
712                expected: vec![",".to_string(), "number".to_string(), "]".to_string()],
713            });
714            let diagnostics = get_diagnostics(&[(error, range.clone())], source);
715
716            assert_eq!(diagnostics.len(), 1);
717            let diagnostic = &diagnostics[0];
718            assert_eq!(diagnostic.code, Some("syntax".to_string()));
719            assert_eq!(
720                diagnostic.message,
721                "unexpected \"number\"; expected: [\",\", \"number\", \"]\"]".to_string()
722            );
723            assert_eq!(diagnostic.severity, Severity::Error);
724            assert_eq!(diagnostic.labels.len(), 1);
725            assert_eq!(diagnostic.labels[0], Label::primary((), range));
726        }
727
728        #[test]
729        fn it_converts_syntaxerror_unterminated_string_to_diagnostic() {
730            let source = dummy_source();
731            let range = dummy_range();
732            let error = ExprError::SyntaxError(SyntaxError::UnterminatedString);
733            let diagnostics = get_diagnostics(&[(error, range.clone())], source);
734
735            assert_eq!(diagnostics.len(), 1);
736            let diagnostic = &diagnostics[0];
737            assert_eq!(diagnostic.code, Some("syntax".to_string()));
738            assert_eq!(diagnostic.message, "unterminated string".to_string());
739            assert_eq!(diagnostic.severity, Severity::Error);
740            assert_eq!(diagnostic.labels.len(), 1);
741            assert_eq!(diagnostic.labels[0], Label::primary((), range));
742        }
743
744        #[test]
745        fn it_converts_syntaxerror_extra_token_to_diagnostic() {
746            let source = dummy_source();
747            let range = dummy_range();
748            let error = ExprError::SyntaxError(SyntaxError::ExtraToken {
749                token: ",".to_string(),
750            });
751            let diagnostics = get_diagnostics(&[(error, range.clone())], source);
752
753            assert_eq!(diagnostics.len(), 1);
754            let diagnostic = &diagnostics[0];
755            assert_eq!(diagnostic.code, Some("syntax".to_string()));
756            assert_eq!(diagnostic.message, "extraneous input: \",\"".to_string());
757            assert_eq!(diagnostic.severity, Severity::Error);
758            assert_eq!(diagnostic.labels.len(), 1);
759            assert_eq!(diagnostic.labels[0], Label::primary((), range));
760        }
761    }
762    #[cfg(test)]
763    mod to_severity_tests {
764        use codespan_reporting::diagnostic::Severity;
765
766        use crate::errors::diagnostics::ExprDiagnosisSeverity;
767
768        #[test]
769        fn from_hint() {
770            assert_eq!(Severity::Help, ExprDiagnosisSeverity::HINT.to_severity());
771        }
772
773        #[test]
774        fn from_information() {
775            assert_eq!(
776                Severity::Note,
777                ExprDiagnosisSeverity::INFORMATION.to_severity()
778            );
779        }
780
781        #[test]
782        fn from_warning() {
783            assert_eq!(
784                Severity::Warning,
785                ExprDiagnosisSeverity::WARNING.to_severity()
786            );
787        }
788
789        #[test]
790        #[should_panic(expected = "Invalid diagnosis severity: 99")]
791        fn from_invalid() {
792            ExprDiagnosisSeverity(99).to_severity();
793        }
794    }
795}