1use 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 pub start: ExprDiagnosticPosition,
337 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 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 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}