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