use std::fmt;
use std::sync::{Arc, Mutex};
use crate::errors::{AntlrError, ConsoleErrorListener, ErrorListener};
use crate::token::TokenView;
use crate::vocabulary::Vocabulary;
#[derive(Clone)]
struct ErrorListenerSlot(Arc<Mutex<dyn for<'a> ErrorListener<dyn Recognizer + 'a> + Send>>);
impl ErrorListenerSlot {
fn new<L>(listener: L) -> Self
where
L: for<'a> ErrorListener<dyn Recognizer + 'a> + Send + 'static,
{
Self(Arc::new(Mutex::new(listener)))
}
#[allow(clippy::too_many_arguments)] fn syntax_error(
&self,
recognizer: &(dyn Recognizer + '_),
offending: Option<TokenView<'_>>,
line: usize,
column: usize,
message: &str,
error: Option<&AntlrError>,
) {
self.0
.lock()
.expect("error listener lock poisoned")
.syntax_error(recognizer, offending, line, column, message, error);
}
}
impl fmt::Debug for ErrorListenerSlot {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("ErrorListener")
}
}
#[derive(Clone, Debug)]
pub(crate) struct RecognizerMetadata {
grammar_file_name: String,
rule_names: Vec<String>,
channel_names: Vec<String>,
mode_names: Vec<String>,
vocabulary: Vocabulary,
}
impl RecognizerMetadata {
pub(crate) fn from_static(
grammar_file_name: &'static str,
rule_names: &'static [&'static str],
channel_names: &'static [&'static str],
mode_names: &'static [&'static str],
vocabulary: Vocabulary,
) -> Self {
Self {
grammar_file_name: grammar_file_name.to_owned(),
rule_names: rule_names.iter().map(|name| (*name).to_owned()).collect(),
channel_names: channel_names
.iter()
.map(|name| (*name).to_owned())
.collect(),
mode_names: mode_names.iter().map(|name| (*name).to_owned()).collect(),
vocabulary,
}
}
}
#[derive(Clone, Debug)]
pub struct RecognizerData {
metadata: Arc<RecognizerMetadata>,
state: isize,
console_error_listener: bool,
error_listeners: Vec<ErrorListenerSlot>,
}
impl RecognizerData {
pub fn new(grammar_file_name: impl Into<String>, vocabulary: Vocabulary) -> Self {
Self {
metadata: Arc::new(RecognizerMetadata {
grammar_file_name: grammar_file_name.into(),
rule_names: Vec::new(),
channel_names: Vec::new(),
mode_names: Vec::new(),
vocabulary,
}),
state: -1,
console_error_listener: true,
error_listeners: Vec::new(),
}
}
pub(crate) const fn from_shared(metadata: Arc<RecognizerMetadata>) -> Self {
Self {
metadata,
state: -1,
console_error_listener: true,
error_listeners: Vec::new(),
}
}
#[must_use]
pub fn with_rule_names(
mut self,
rule_names: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
Arc::make_mut(&mut self.metadata).rule_names =
rule_names.into_iter().map(Into::into).collect();
self
}
#[must_use]
pub fn with_channel_names(
mut self,
channel_names: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
Arc::make_mut(&mut self.metadata).channel_names =
channel_names.into_iter().map(Into::into).collect();
self
}
#[must_use]
pub fn with_mode_names(
mut self,
mode_names: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
Arc::make_mut(&mut self.metadata).mode_names =
mode_names.into_iter().map(Into::into).collect();
self
}
#[must_use]
pub fn rule_names(&self) -> &[String] {
&self.metadata.rule_names
}
#[must_use]
pub fn vocabulary(&self) -> &Vocabulary {
&self.metadata.vocabulary
}
pub const fn state(&self) -> isize {
self.state
}
pub const fn set_state(&mut self, state: isize) {
self.state = state;
}
fn add_error_listener<L>(&mut self, listener: L)
where
L: for<'a> ErrorListener<dyn Recognizer + 'a> + Send + 'static,
{
self.error_listeners.push(ErrorListenerSlot::new(listener));
}
fn remove_error_listeners(&mut self) {
self.console_error_listener = false;
self.error_listeners.clear();
}
#[allow(clippy::too_many_arguments)] fn notify_error_listeners(
&self,
recognizer: &dyn Recognizer,
offending: Option<TokenView<'_>>,
line: usize,
column: usize,
message: &str,
error: Option<&AntlrError>,
) {
if self.console_error_listener {
ConsoleErrorListener.syntax_error(recognizer, offending, line, column, message, error);
}
for listener in &self.error_listeners {
listener.syntax_error(recognizer, offending, line, column, message, error);
}
}
}
pub trait Recognizer {
fn data(&self) -> &RecognizerData;
fn data_mut(&mut self) -> &mut RecognizerData;
fn grammar_file_name(&self) -> &str {
&self.data().metadata.grammar_file_name
}
fn rule_names(&self) -> &[String] {
&self.data().metadata.rule_names
}
fn channel_names(&self) -> &[String] {
&self.data().metadata.channel_names
}
fn mode_names(&self) -> &[String] {
&self.data().metadata.mode_names
}
fn vocabulary(&self) -> &Vocabulary {
&self.data().metadata.vocabulary
}
fn state(&self) -> isize {
self.data().state()
}
fn set_state(&mut self, state: isize) {
self.data_mut().set_state(state);
}
fn add_error_listener<L>(&mut self, listener: L)
where
Self: Sized,
L: for<'a> ErrorListener<dyn Recognizer + 'a> + Send + 'static,
{
self.data_mut().add_error_listener(listener);
}
fn remove_error_listeners(&mut self) {
self.data_mut().remove_error_listeners();
}
fn notify_error_listeners(
&self,
offending: Option<TokenView<'_>>,
line: usize,
column: usize,
message: &str,
error: Option<&AntlrError>,
) where
Self: Sized,
{
self.data()
.notify_error_listeners(self, offending, line, column, message, error);
}
fn sempred(&mut self, _rule_index: usize, _pred_index: usize) -> bool {
true
}
fn action(&mut self, _rule_index: usize, _action_index: usize) {}
}
#[cfg(test)]
#[allow(clippy::disallowed_methods)] mod tests {
use std::mem::size_of;
use super::*;
use crate::generated::GrammarMetadata;
static SHARED_METADATA: GrammarMetadata = GrammarMetadata::new(
"Shared.g4",
&["start"],
&[None, Some("'x'")],
&[None, Some("X")],
&[None, None],
&["DEFAULT_TOKEN_CHANNEL", "HIDDEN"],
&["DEFAULT_MODE"],
&[],
);
#[derive(Clone, Debug, Eq, PartialEq)]
struct RecordedError {
grammar_file_name: String,
offending_text: Option<String>,
line: usize,
column: usize,
message: String,
error: Option<AntlrError>,
}
#[derive(Clone, Debug)]
struct RecordingErrorListener {
errors: Arc<Mutex<Vec<RecordedError>>>,
}
impl<R> ErrorListener<R> for RecordingErrorListener
where
R: Recognizer + ?Sized,
{
fn syntax_error(
&mut self,
recognizer: &R,
offending: Option<TokenView<'_>>,
line: usize,
column: usize,
message: &str,
error: Option<&AntlrError>,
) {
self.errors
.lock()
.expect("recorded errors lock")
.push(RecordedError {
grammar_file_name: recognizer.grammar_file_name().to_owned(),
offending_text: offending.and_then(|token| token.text().map(str::to_owned)),
line,
column,
message: message.to_owned(),
error: error.cloned(),
});
}
}
#[derive(Clone, Debug)]
struct TestRecognizer {
data: RecognizerData,
}
impl Recognizer for TestRecognizer {
fn data(&self) -> &RecognizerData {
&self.data
}
fn data_mut(&mut self) -> &mut RecognizerData {
&mut self.data
}
}
fn test_recognizer() -> TestRecognizer {
TestRecognizer {
data: RecognizerData::new(
"Test.g4",
Vocabulary::new(
std::iter::empty::<Option<&str>>(),
std::iter::empty::<Option<&str>>(),
std::iter::empty::<Option<&str>>(),
),
),
}
}
#[test]
fn recognizers_replace_the_default_console_error_listener() {
let mut recognizer = test_recognizer();
assert!(recognizer.data.console_error_listener);
assert!(recognizer.data.error_listeners.is_empty());
recognizer.remove_error_listeners();
assert!(!recognizer.data.console_error_listener);
assert!(recognizer.data.error_listeners.is_empty());
let errors = Arc::new(Mutex::new(Vec::new()));
recognizer.add_error_listener(RecordingErrorListener {
errors: Arc::clone(&errors),
});
let error = AntlrError::ParserError {
line: 3,
column: 5,
message: "unexpected token".to_owned(),
offending: None,
};
recognizer.notify_error_listeners(None, 3, 5, "unexpected token", Some(&error));
insta::assert_debug_snapshot!(
"recognizers_replace_the_default_console_error_listener",
*errors.lock().expect("recorded errors lock")
);
}
#[test]
fn recognizer_data_remains_send_and_sync() {
fn assert_send_and_sync<T: Send + Sync>() {}
assert_send_and_sync::<RecognizerData>();
}
#[test]
fn recognizer_data_keeps_shared_metadata_out_of_line() {
assert!(size_of::<RecognizerData>() < size_of::<RecognizerMetadata>());
}
#[test]
fn cloned_recognizers_can_reconfigure_their_listener_lists_independently() {
let mut original = test_recognizer();
let clone = original.clone();
original.remove_error_listeners();
assert!(!original.data.console_error_listener);
assert!(original.data.error_listeners.is_empty());
assert!(clone.data.console_error_listener);
assert!(clone.data.error_listeners.is_empty());
}
#[test]
fn generated_recognizers_share_metadata_but_not_instance_state() {
let mut first = SHARED_METADATA.recognizer_data();
let second = SHARED_METADATA.recognizer_data();
assert!(std::ptr::eq(first.rule_names(), second.rule_names()));
assert!(std::ptr::eq(first.vocabulary(), second.vocabulary()));
assert!(first.console_error_listener);
assert!(second.console_error_listener);
first.set_state(7);
first.remove_error_listeners();
assert_eq!(first.state(), 7);
assert_eq!(second.state(), -1);
assert!(!first.console_error_listener);
assert!(first.error_listeners.is_empty());
assert!(second.console_error_listener);
assert!(second.error_listeners.is_empty());
}
#[test]
fn customizing_shared_metadata_detaches_only_that_recognizer() {
let customized = SHARED_METADATA
.recognizer_data()
.with_rule_names(["replacement"]);
let shared = SHARED_METADATA.recognizer_data();
assert_eq!(customized.rule_names(), ["replacement"]);
assert_eq!(shared.rule_names(), ["start"]);
assert!(!std::ptr::eq(customized.rule_names(), shared.rule_names()));
}
}