Skip to main content

rantlr_core/
error.rs

1use std::fmt;
2
3use thiserror::Error;
4
5use crate::diagnostic::{Diagnostic, DiagnosticKind};
6use crate::fix_hint::FixHint;
7use crate::span::Span;
8
9/// User-facing error for lex / parse / lint failures.
10#[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
93/// Build a small caret snippet around `span`.
94pub 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}