Skip to main content

antlr4_runtime/
errors.rs

1use std::ops::Range;
2
3use crate::recognizer::Recognizer;
4use crate::token::{TokenId, TokenSourceError, TokenView};
5use thiserror::Error;
6
7#[derive(Debug, Error, Clone, Eq, PartialEq)]
8pub enum AntlrError {
9    #[error("mismatched input: expected {expected}, found {found}")]
10    MismatchedInput { expected: String, found: String },
11    #[error("no viable alternative at input {input}")]
12    NoViableAlternative { input: String },
13    #[error("lexer error at {line}:{column}: {message}")]
14    LexerError {
15        line: usize,
16        column: usize,
17        message: String,
18    },
19    #[error("parser error at {line}:{column}: {message}")]
20    ParserError {
21        line: usize,
22        column: usize,
23        message: String,
24        /// Token the error is anchored to, when one exists. The anchor must
25        /// be captured where the error is built: prediction restores the
26        /// input cursor, so the current lookahead at reporting time is not
27        /// necessarily the offending token (`no viable alternative` anchors
28        /// at the error index while the cursor sits at the decision start).
29        offending: Option<TokenId>,
30    },
31    #[error("unsupported runtime feature: {0}")]
32    Unsupported(String),
33}
34
35/// Structured context for one recognizer diagnostic.
36#[derive(Clone, Debug, Eq, PartialEq)]
37#[non_exhaustive]
38pub struct SyntaxErrorEvent<'a> {
39    /// Token the diagnostic is anchored to, when one exists.
40    ///
41    /// Lexer errors have no offending token because the failed match did not
42    /// produce one.
43    pub offending: Option<TokenView<'a>>,
44    /// One-based input line where the diagnostic starts.
45    pub line: usize,
46    /// Zero-based column within `line` where the diagnostic starts.
47    pub column: usize,
48    /// Half-open UTF-8 byte span of the offending source text.
49    ///
50    /// Custom streams and token sources that cannot resolve byte offsets leave
51    /// this as `None`.
52    pub span: Option<Range<usize>>,
53    /// ANTLR-compatible diagnostic message without the leading line/column.
54    pub message: &'a str,
55    /// Recognition error that caused the diagnostic, when one exists.
56    pub error: Option<&'a AntlrError>,
57}
58
59impl<'a> From<&'a TokenSourceError> for SyntaxErrorEvent<'a> {
60    fn from(error: &'a TokenSourceError) -> Self {
61        Self {
62            offending: None,
63            line: error.line,
64            column: error.column,
65            span: error.span.clone(),
66            message: &error.message,
67            error: None,
68        }
69    }
70}
71
72/// Receives recognizer diagnostics.
73///
74/// Listeners registered through [`Recognizer::add_error_listener`] must be
75/// [`Send`] and work with every recognizer type. Implement the trait
76/// generically, as [`ConsoleErrorListener`] does, when a listener will be
77/// registered.
78pub trait ErrorListener<R: Recognizer + ?Sized> {
79    /// Receives one diagnostic with its ANTLR position and resolved byte span.
80    fn syntax_error(&mut self, recognizer: &R, event: &SyntaxErrorEvent<'_>);
81}
82
83#[derive(Debug, Default)]
84pub struct ConsoleErrorListener;
85
86impl<R: Recognizer + ?Sized> ErrorListener<R> for ConsoleErrorListener {
87    #[allow(clippy::print_stderr)]
88    fn syntax_error(&mut self, _recognizer: &R, event: &SyntaxErrorEvent<'_>) {
89        eprintln!("line {}:{} {}", event.line, event.column, event.message);
90    }
91}