Skip to main content

antlr4_runtime/
recognizer.rs

1// SPDX-License-Identifier: BSD-3-Clause
2// Copyright (c) 2026 Konstantin Vyatkin
3use std::fmt;
4use std::sync::{Arc, Mutex};
5
6use crate::errors::{ConsoleErrorListener, ErrorListener, SyntaxErrorEvent};
7use crate::vocabulary::Vocabulary;
8
9#[derive(Clone)]
10struct ErrorListenerSlot(Arc<Mutex<dyn for<'a> ErrorListener<dyn Recognizer + 'a> + Send>>);
11
12impl ErrorListenerSlot {
13    fn new<L>(listener: L) -> Self
14    where
15        L: for<'a> ErrorListener<dyn Recognizer + 'a> + Send + 'static,
16    {
17        Self(Arc::new(Mutex::new(listener)))
18    }
19
20    fn syntax_error(&self, recognizer: &(dyn Recognizer + '_), event: &SyntaxErrorEvent<'_>) {
21        self.0
22            .lock()
23            .expect("error listener lock poisoned")
24            .syntax_error(recognizer, event);
25    }
26}
27
28impl fmt::Debug for ErrorListenerSlot {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        f.write_str("ErrorListener")
31    }
32}
33
34#[derive(Clone, Debug)]
35pub(crate) struct RecognizerMetadata {
36    grammar_file_name: String,
37    rule_names: Vec<String>,
38    channel_names: Vec<String>,
39    mode_names: Vec<String>,
40    vocabulary: Vocabulary,
41}
42
43impl RecognizerMetadata {
44    pub(crate) fn from_static(
45        grammar_file_name: &'static str,
46        rule_names: &'static [&'static str],
47        channel_names: &'static [&'static str],
48        mode_names: &'static [&'static str],
49        vocabulary: Vocabulary,
50    ) -> Self {
51        Self {
52            grammar_file_name: grammar_file_name.to_owned(),
53            rule_names: rule_names.iter().map(|name| (*name).to_owned()).collect(),
54            channel_names: channel_names
55                .iter()
56                .map(|name| (*name).to_owned())
57                .collect(),
58            mode_names: mode_names.iter().map(|name| (*name).to_owned()).collect(),
59            vocabulary,
60        }
61    }
62}
63
64#[derive(Clone, Debug)]
65pub struct RecognizerData {
66    metadata: Arc<RecognizerMetadata>,
67    state: isize,
68    console_error_listener: bool,
69    error_listeners: Vec<ErrorListenerSlot>,
70}
71
72impl RecognizerData {
73    pub fn new(grammar_file_name: impl Into<String>, vocabulary: Vocabulary) -> Self {
74        Self {
75            metadata: Arc::new(RecognizerMetadata {
76                grammar_file_name: grammar_file_name.into(),
77                rule_names: Vec::new(),
78                channel_names: Vec::new(),
79                mode_names: Vec::new(),
80                vocabulary,
81            }),
82            state: -1,
83            console_error_listener: true,
84            error_listeners: Vec::new(),
85        }
86    }
87
88    pub(crate) const fn from_shared(metadata: Arc<RecognizerMetadata>) -> Self {
89        Self {
90            metadata,
91            state: -1,
92            console_error_listener: true,
93            error_listeners: Vec::new(),
94        }
95    }
96
97    #[must_use]
98    pub fn with_rule_names(
99        mut self,
100        rule_names: impl IntoIterator<Item = impl Into<String>>,
101    ) -> Self {
102        Arc::make_mut(&mut self.metadata).rule_names =
103            rule_names.into_iter().map(Into::into).collect();
104        self
105    }
106
107    #[must_use]
108    pub fn with_channel_names(
109        mut self,
110        channel_names: impl IntoIterator<Item = impl Into<String>>,
111    ) -> Self {
112        Arc::make_mut(&mut self.metadata).channel_names =
113            channel_names.into_iter().map(Into::into).collect();
114        self
115    }
116
117    #[must_use]
118    pub fn with_mode_names(
119        mut self,
120        mode_names: impl IntoIterator<Item = impl Into<String>>,
121    ) -> Self {
122        Arc::make_mut(&mut self.metadata).mode_names =
123            mode_names.into_iter().map(Into::into).collect();
124        self
125    }
126
127    /// Rule names owned by this recognizer's metadata.
128    ///
129    /// Also available through [`Recognizer::rule_names`]; this inherent
130    /// accessor lets callers that already hold a `RecognizerData` field
131    /// borrow rule names without borrowing the whole recognizer.
132    #[must_use]
133    pub fn rule_names(&self) -> &[String] {
134        &self.metadata.rule_names
135    }
136
137    /// The token vocabulary for literal/symbolic name resolution.
138    #[must_use]
139    pub fn vocabulary(&self) -> &Vocabulary {
140        &self.metadata.vocabulary
141    }
142
143    pub const fn state(&self) -> isize {
144        self.state
145    }
146
147    pub const fn set_state(&mut self, state: isize) {
148        self.state = state;
149    }
150
151    fn add_error_listener<L>(&mut self, listener: L)
152    where
153        L: for<'a> ErrorListener<dyn Recognizer + 'a> + Send + 'static,
154    {
155        self.error_listeners.push(ErrorListenerSlot::new(listener));
156    }
157
158    fn remove_error_listeners(&mut self) {
159        self.console_error_listener = false;
160        self.error_listeners.clear();
161    }
162
163    fn notify_error_listeners(&self, recognizer: &dyn Recognizer, event: &SyntaxErrorEvent<'_>) {
164        if self.console_error_listener {
165            ConsoleErrorListener.syntax_error(recognizer, event);
166        }
167        for listener in &self.error_listeners {
168            listener.syntax_error(recognizer, event);
169        }
170    }
171}
172
173pub trait Recognizer {
174    fn data(&self) -> &RecognizerData;
175    fn data_mut(&mut self) -> &mut RecognizerData;
176
177    fn grammar_file_name(&self) -> &str {
178        &self.data().metadata.grammar_file_name
179    }
180
181    fn rule_names(&self) -> &[String] {
182        &self.data().metadata.rule_names
183    }
184
185    fn channel_names(&self) -> &[String] {
186        &self.data().metadata.channel_names
187    }
188
189    fn mode_names(&self) -> &[String] {
190        &self.data().metadata.mode_names
191    }
192
193    fn vocabulary(&self) -> &Vocabulary {
194        &self.data().metadata.vocabulary
195    }
196
197    fn state(&self) -> isize {
198        self.data().state()
199    }
200
201    fn set_state(&mut self, state: isize) {
202        self.data_mut().set_state(state);
203    }
204
205    /// Adds a listener for syntax and prediction diagnostics.
206    ///
207    /// Recognizers start with one [`ConsoleErrorListener`]. Call
208    /// [`Self::remove_error_listeners`] before adding a replacement when
209    /// diagnostics should not also be written to stderr.
210    fn add_error_listener<L>(&mut self, listener: L)
211    where
212        Self: Sized,
213        L: for<'a> ErrorListener<dyn Recognizer + 'a> + Send + 'static,
214    {
215        self.data_mut().add_error_listener(listener);
216    }
217
218    /// Removes every error listener, including the default console listener.
219    fn remove_error_listeners(&mut self) {
220        self.data_mut().remove_error_listeners();
221    }
222
223    /// Sends one diagnostic to every registered error listener.
224    fn notify_error_listeners(&self, event: SyntaxErrorEvent<'_>)
225    where
226        Self: Sized,
227    {
228        self.data().notify_error_listeners(self, &event);
229    }
230
231    fn sempred(&mut self, _rule_index: usize, _pred_index: usize) -> bool {
232        true
233    }
234
235    fn action(&mut self, _rule_index: usize, _action_index: usize) {}
236}
237
238#[cfg(test)]
239#[allow(clippy::disallowed_methods)] // `insta` assertion macros unwrap internal I/O.
240mod tests {
241    use std::mem::size_of;
242
243    use super::*;
244    use crate::errors::AntlrError;
245    use crate::generated::GrammarMetadata;
246
247    static SHARED_METADATA: GrammarMetadata = GrammarMetadata::new(
248        "Shared.g4",
249        &["start"],
250        &[None, Some("'x'")],
251        &[None, Some("X")],
252        &[None, None],
253        &["DEFAULT_TOKEN_CHANNEL", "HIDDEN"],
254        &["DEFAULT_MODE"],
255        &[],
256    );
257
258    #[derive(Clone, Debug, Eq, PartialEq)]
259    struct RecordedError {
260        grammar_file_name: String,
261        offending_text: Option<String>,
262        line: usize,
263        column: usize,
264        span: Option<std::ops::Range<usize>>,
265        message: String,
266        error: Option<AntlrError>,
267    }
268
269    #[derive(Clone, Debug)]
270    struct RecordingErrorListener {
271        errors: Arc<Mutex<Vec<RecordedError>>>,
272    }
273
274    impl<R> ErrorListener<R> for RecordingErrorListener
275    where
276        R: Recognizer + ?Sized,
277    {
278        fn syntax_error(&mut self, recognizer: &R, event: &SyntaxErrorEvent<'_>) {
279            self.errors
280                .lock()
281                .expect("recorded errors lock")
282                .push(RecordedError {
283                    grammar_file_name: recognizer.grammar_file_name().to_owned(),
284                    offending_text: event
285                        .offending
286                        .and_then(|token| token.text().map(str::to_owned)),
287                    line: event.line,
288                    column: event.column,
289                    span: event.span.clone(),
290                    message: event.message.to_owned(),
291                    error: event.error.cloned(),
292                });
293        }
294    }
295
296    #[derive(Clone, Debug)]
297    struct TestRecognizer {
298        data: RecognizerData,
299    }
300
301    impl Recognizer for TestRecognizer {
302        fn data(&self) -> &RecognizerData {
303            &self.data
304        }
305
306        fn data_mut(&mut self) -> &mut RecognizerData {
307            &mut self.data
308        }
309    }
310
311    fn test_recognizer() -> TestRecognizer {
312        TestRecognizer {
313            data: RecognizerData::new(
314                "Test.g4",
315                Vocabulary::new(
316                    std::iter::empty::<Option<&str>>(),
317                    std::iter::empty::<Option<&str>>(),
318                    std::iter::empty::<Option<&str>>(),
319                ),
320            ),
321        }
322    }
323
324    #[test]
325    fn recognizers_replace_the_default_console_error_listener() {
326        let mut recognizer = test_recognizer();
327        assert!(recognizer.data.console_error_listener);
328        assert!(recognizer.data.error_listeners.is_empty());
329
330        recognizer.remove_error_listeners();
331        assert!(!recognizer.data.console_error_listener);
332        assert!(recognizer.data.error_listeners.is_empty());
333
334        let errors = Arc::new(Mutex::new(Vec::new()));
335        recognizer.add_error_listener(RecordingErrorListener {
336            errors: Arc::clone(&errors),
337        });
338        let error = AntlrError::ParserError {
339            line: 3,
340            column: 5,
341            message: "unexpected token".to_owned(),
342            offending: None,
343        };
344        recognizer.notify_error_listeners(SyntaxErrorEvent {
345            offending: None,
346            line: 3,
347            column: 5,
348            span: Some(17..27),
349            message: "unexpected token",
350            error: Some(&error),
351        });
352
353        insta::assert_debug_snapshot!(
354            "recognizers_replace_the_default_console_error_listener",
355            *errors.lock().expect("recorded errors lock")
356        );
357    }
358
359    #[test]
360    fn recognizer_data_remains_send_and_sync() {
361        fn assert_send_and_sync<T: Send + Sync>() {}
362
363        assert_send_and_sync::<RecognizerData>();
364    }
365
366    #[test]
367    fn recognizer_data_keeps_shared_metadata_out_of_line() {
368        assert!(size_of::<RecognizerData>() < size_of::<RecognizerMetadata>());
369    }
370
371    #[test]
372    fn cloned_recognizers_can_reconfigure_their_listener_lists_independently() {
373        let mut original = test_recognizer();
374        let clone = original.clone();
375
376        original.remove_error_listeners();
377
378        assert!(!original.data.console_error_listener);
379        assert!(original.data.error_listeners.is_empty());
380        assert!(clone.data.console_error_listener);
381        assert!(clone.data.error_listeners.is_empty());
382    }
383
384    #[test]
385    fn generated_recognizers_share_metadata_but_not_instance_state() {
386        let mut first = SHARED_METADATA.recognizer_data();
387        let second = SHARED_METADATA.recognizer_data();
388
389        assert!(std::ptr::eq(first.rule_names(), second.rule_names()));
390        assert!(std::ptr::eq(first.vocabulary(), second.vocabulary()));
391        assert!(first.console_error_listener);
392        assert!(second.console_error_listener);
393
394        first.set_state(7);
395        first.remove_error_listeners();
396
397        assert_eq!(first.state(), 7);
398        assert_eq!(second.state(), -1);
399        assert!(!first.console_error_listener);
400        assert!(first.error_listeners.is_empty());
401        assert!(second.console_error_listener);
402        assert!(second.error_listeners.is_empty());
403    }
404
405    #[test]
406    fn customizing_shared_metadata_detaches_only_that_recognizer() {
407        let customized = SHARED_METADATA
408            .recognizer_data()
409            .with_rule_names(["replacement"]);
410        let shared = SHARED_METADATA.recognizer_data();
411
412        assert_eq!(customized.rule_names(), ["replacement"]);
413        assert_eq!(shared.rule_names(), ["start"]);
414        assert!(!std::ptr::eq(customized.rule_names(), shared.rule_names()));
415    }
416}