Skip to main content

antlr4_runtime/
errors.rs

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