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