1use std::fmt;
2
3use thiserror::Error;
4
5use crate::diagnostic::{Diagnostic, DiagnosticKind};
6use crate::fix_hint::FixHint;
7use crate::span::Span;
8
9#[derive(Debug, Error, Clone, PartialEq, Eq)]
11#[error("{message}")]
12pub struct RantlrError {
13 pub kind: ErrorKind,
14 pub message: String,
15 pub line: usize,
16 pub column: usize,
17 pub snippet: String,
18 pub help: Option<String>,
19 pub code: Option<String>,
20 pub fix_hint: Option<FixHint>,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum ErrorKind {
25 Lex,
26 Parse,
27 Lint,
28}
29
30impl RantlrError {
31 pub fn from_diagnostic(source: &str, diag: Diagnostic) -> Self {
32 let kind = match diag.kind {
33 DiagnosticKind::LexError => ErrorKind::Lex,
34 DiagnosticKind::ParseError => ErrorKind::Parse,
35 DiagnosticKind::Lint => ErrorKind::Lint,
36 };
37 let (line, column) = diag.span.line_col(source);
38 let snippet = context_snippet(source, diag.span);
39 Self {
40 kind,
41 message: diag.message,
42 line,
43 column,
44 snippet,
45 help: diag.help,
46 code: diag.code,
47 fix_hint: diag.fix,
48 }
49 }
50
51 pub fn parse(source: &str, message: impl Into<String>, span: Span) -> Self {
52 Self::from_diagnostic(
53 source,
54 Diagnostic::error(DiagnosticKind::ParseError, message, span),
55 )
56 }
57
58 pub fn parse_with_help(
59 source: &str,
60 message: impl Into<String>,
61 span: Span,
62 help: impl Into<String>,
63 ) -> Self {
64 Self::from_diagnostic(
65 source,
66 Diagnostic::error(DiagnosticKind::ParseError, message, span).with_help(help),
67 )
68 }
69
70 pub fn format(&self, file: &str) -> String {
71 let mut out = format!(
72 "{file}:{}:{}: {:?}: {}\n{}",
73 self.line, self.column, self.kind, self.message, self.snippet
74 );
75 if let Some(help) = &self.help {
76 out.push_str("\n help: ");
77 out.push_str(help);
78 }
79 out
80 }
81}
82
83impl fmt::Display for ErrorKind {
84 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85 match self {
86 Self::Lex => write!(f, "Lex"),
87 Self::Parse => write!(f, "Parse"),
88 Self::Lint => write!(f, "Lint"),
89 }
90 }
91}
92
93pub fn context_snippet(source: &str, span: Span) -> String {
95 let (line_no, col) = span.line_col(source);
96 let line_text = source
97 .lines()
98 .nth(line_no.saturating_sub(1))
99 .unwrap_or("");
100
101 let gutter = format!("{line_no}");
102 let pad = " ".repeat(gutter.len());
103 let caret_col = col.saturating_sub(1);
104 let caret = format!("{}{}", " ".repeat(caret_col), "^");
105
106 format!(" {gutter} | {line_text}\n {pad} | {caret}")
107}