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