Skip to main content

antlr4_runtime/
recognizer.rs

1use std::fmt;
2use std::sync::{Arc, Mutex};
3
4use crate::errors::{AntlrError, ConsoleErrorListener, ErrorListener};
5use crate::token::TokenView;
6use crate::vocabulary::Vocabulary;
7
8#[derive(Clone)]
9struct ErrorListenerSlot(Arc<Mutex<dyn for<'a> ErrorListener<dyn Recognizer + 'a> + Send>>);
10
11impl ErrorListenerSlot {
12    fn new<L>(listener: L) -> Self
13    where
14        L: for<'a> ErrorListener<dyn Recognizer + 'a> + Send + 'static,
15    {
16        Self(Arc::new(Mutex::new(listener)))
17    }
18
19    #[allow(clippy::too_many_arguments)] // mirrors ANTLR's canonical syntaxError signature
20    fn syntax_error(
21        &self,
22        recognizer: &(dyn Recognizer + '_),
23        offending: Option<TokenView<'_>>,
24        line: usize,
25        column: usize,
26        message: &str,
27        error: Option<&AntlrError>,
28    ) {
29        self.0
30            .lock()
31            .expect("error listener lock poisoned")
32            .syntax_error(recognizer, offending, line, column, message, error);
33    }
34}
35
36impl fmt::Debug for ErrorListenerSlot {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        f.write_str("ErrorListener")
39    }
40}
41
42#[derive(Clone, Debug)]
43pub struct RecognizerData {
44    grammar_file_name: String,
45    rule_names: Vec<String>,
46    channel_names: Vec<String>,
47    mode_names: Vec<String>,
48    vocabulary: Vocabulary,
49    state: isize,
50    error_listeners: Vec<ErrorListenerSlot>,
51}
52
53impl RecognizerData {
54    pub fn new(grammar_file_name: impl Into<String>, vocabulary: Vocabulary) -> Self {
55        Self {
56            grammar_file_name: grammar_file_name.into(),
57            rule_names: Vec::new(),
58            channel_names: Vec::new(),
59            mode_names: Vec::new(),
60            vocabulary,
61            state: -1,
62            error_listeners: vec![ErrorListenerSlot::new(ConsoleErrorListener)],
63        }
64    }
65
66    #[must_use]
67    pub fn with_rule_names(
68        mut self,
69        rule_names: impl IntoIterator<Item = impl Into<String>>,
70    ) -> Self {
71        self.rule_names = rule_names.into_iter().map(Into::into).collect();
72        self
73    }
74
75    #[must_use]
76    pub fn with_channel_names(
77        mut self,
78        channel_names: impl IntoIterator<Item = impl Into<String>>,
79    ) -> Self {
80        self.channel_names = channel_names.into_iter().map(Into::into).collect();
81        self
82    }
83
84    #[must_use]
85    pub fn with_mode_names(
86        mut self,
87        mode_names: impl IntoIterator<Item = impl Into<String>>,
88    ) -> Self {
89        self.mode_names = mode_names.into_iter().map(Into::into).collect();
90        self
91    }
92
93    /// Rule names owned by this recognizer's metadata.
94    ///
95    /// Also available through [`Recognizer::rule_names`]; this inherent
96    /// accessor lets callers that already hold a `RecognizerData` field
97    /// borrow rule names without borrowing the whole recognizer.
98    #[must_use]
99    pub fn rule_names(&self) -> &[String] {
100        &self.rule_names
101    }
102
103    /// The token vocabulary for literal/symbolic name resolution.
104    #[must_use]
105    pub const fn vocabulary(&self) -> &Vocabulary {
106        &self.vocabulary
107    }
108
109    pub const fn state(&self) -> isize {
110        self.state
111    }
112
113    pub const fn set_state(&mut self, state: isize) {
114        self.state = state;
115    }
116
117    fn add_error_listener<L>(&mut self, listener: L)
118    where
119        L: for<'a> ErrorListener<dyn Recognizer + 'a> + Send + 'static,
120    {
121        self.error_listeners.push(ErrorListenerSlot::new(listener));
122    }
123
124    fn remove_error_listeners(&mut self) {
125        self.error_listeners.clear();
126    }
127
128    #[allow(clippy::too_many_arguments)] // mirrors ANTLR's canonical syntaxError signature
129    fn notify_error_listeners(
130        &self,
131        recognizer: &dyn Recognizer,
132        offending: Option<TokenView<'_>>,
133        line: usize,
134        column: usize,
135        message: &str,
136        error: Option<&AntlrError>,
137    ) {
138        for listener in &self.error_listeners {
139            listener.syntax_error(recognizer, offending, line, column, message, error);
140        }
141    }
142}
143
144pub trait Recognizer {
145    fn data(&self) -> &RecognizerData;
146    fn data_mut(&mut self) -> &mut RecognizerData;
147
148    fn grammar_file_name(&self) -> &str {
149        &self.data().grammar_file_name
150    }
151
152    fn rule_names(&self) -> &[String] {
153        &self.data().rule_names
154    }
155
156    fn channel_names(&self) -> &[String] {
157        &self.data().channel_names
158    }
159
160    fn mode_names(&self) -> &[String] {
161        &self.data().mode_names
162    }
163
164    fn vocabulary(&self) -> &Vocabulary {
165        &self.data().vocabulary
166    }
167
168    fn state(&self) -> isize {
169        self.data().state()
170    }
171
172    fn set_state(&mut self, state: isize) {
173        self.data_mut().set_state(state);
174    }
175
176    /// Adds a listener for syntax and prediction diagnostics.
177    ///
178    /// Recognizers start with one [`ConsoleErrorListener`]. Call
179    /// [`Self::remove_error_listeners`] before adding a replacement when
180    /// diagnostics should not also be written to stderr.
181    fn add_error_listener<L>(&mut self, listener: L)
182    where
183        Self: Sized,
184        L: for<'a> ErrorListener<dyn Recognizer + 'a> + Send + 'static,
185    {
186        self.data_mut().add_error_listener(listener);
187    }
188
189    /// Removes every error listener, including the default console listener.
190    fn remove_error_listeners(&mut self) {
191        self.data_mut().remove_error_listeners();
192    }
193
194    /// Sends one diagnostic to every registered error listener.
195    ///
196    /// `offending` is the token the diagnostic is anchored to, when one
197    /// exists — parser diagnostics resolve it from the token store; lexer
198    /// diagnostics pass `None` because no token was produced.
199    fn notify_error_listeners(
200        &self,
201        offending: Option<TokenView<'_>>,
202        line: usize,
203        column: usize,
204        message: &str,
205        error: Option<&AntlrError>,
206    ) where
207        Self: Sized,
208    {
209        self.data()
210            .notify_error_listeners(self, offending, line, column, message, error);
211    }
212
213    fn sempred(&mut self, _rule_index: usize, _pred_index: usize) -> bool {
214        true
215    }
216
217    fn action(&mut self, _rule_index: usize, _action_index: usize) {}
218}
219
220#[cfg(test)]
221#[allow(clippy::disallowed_methods)] // `insta` assertion macros unwrap internal I/O.
222mod tests {
223    use super::*;
224
225    #[derive(Clone, Debug, Eq, PartialEq)]
226    struct RecordedError {
227        grammar_file_name: String,
228        offending_text: Option<String>,
229        line: usize,
230        column: usize,
231        message: String,
232        error: Option<AntlrError>,
233    }
234
235    #[derive(Clone, Debug)]
236    struct RecordingErrorListener {
237        errors: Arc<Mutex<Vec<RecordedError>>>,
238    }
239
240    impl<R> ErrorListener<R> for RecordingErrorListener
241    where
242        R: Recognizer + ?Sized,
243    {
244        fn syntax_error(
245            &mut self,
246            recognizer: &R,
247            offending: Option<TokenView<'_>>,
248            line: usize,
249            column: usize,
250            message: &str,
251            error: Option<&AntlrError>,
252        ) {
253            self.errors
254                .lock()
255                .expect("recorded errors lock")
256                .push(RecordedError {
257                    grammar_file_name: recognizer.grammar_file_name().to_owned(),
258                    offending_text: offending.and_then(|token| token.text().map(str::to_owned)),
259                    line,
260                    column,
261                    message: message.to_owned(),
262                    error: error.cloned(),
263                });
264        }
265    }
266
267    #[derive(Clone, Debug)]
268    struct TestRecognizer {
269        data: RecognizerData,
270    }
271
272    impl Recognizer for TestRecognizer {
273        fn data(&self) -> &RecognizerData {
274            &self.data
275        }
276
277        fn data_mut(&mut self) -> &mut RecognizerData {
278            &mut self.data
279        }
280    }
281
282    fn test_recognizer() -> TestRecognizer {
283        TestRecognizer {
284            data: RecognizerData::new(
285                "Test.g4",
286                Vocabulary::new(
287                    std::iter::empty::<Option<&str>>(),
288                    std::iter::empty::<Option<&str>>(),
289                    std::iter::empty::<Option<&str>>(),
290                ),
291            ),
292        }
293    }
294
295    #[test]
296    fn recognizers_replace_the_default_console_error_listener() {
297        let mut recognizer = test_recognizer();
298        assert_eq!(recognizer.data.error_listeners.len(), 1);
299
300        recognizer.remove_error_listeners();
301        assert!(recognizer.data.error_listeners.is_empty());
302
303        let errors = Arc::new(Mutex::new(Vec::new()));
304        recognizer.add_error_listener(RecordingErrorListener {
305            errors: Arc::clone(&errors),
306        });
307        let error = AntlrError::ParserError {
308            line: 3,
309            column: 5,
310            message: "unexpected token".to_owned(),
311            offending: None,
312        };
313        recognizer.notify_error_listeners(None, 3, 5, "unexpected token", Some(&error));
314
315        insta::assert_debug_snapshot!(
316            "recognizers_replace_the_default_console_error_listener",
317            *errors.lock().expect("recorded errors lock")
318        );
319    }
320
321    #[test]
322    fn recognizer_data_remains_send_and_sync() {
323        fn assert_send_and_sync<T: Send + Sync>() {}
324
325        assert_send_and_sync::<RecognizerData>();
326    }
327
328    #[test]
329    fn cloned_recognizers_can_reconfigure_their_listener_lists_independently() {
330        let mut original = test_recognizer();
331        let clone = original.clone();
332
333        original.remove_error_listeners();
334
335        assert!(original.data.error_listeners.is_empty());
336        assert_eq!(clone.data.error_listeners.len(), 1);
337    }
338}