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