use std::collections::BTreeSet;
use derivative::Derivative;
use itertools::Itertools;
use crate::lex::{Map, TokenSrc};
use crate::Lexicon;
use super::LitSet;
#[derive(Derivative, PartialEq, Clone)]
#[derivative(Default(new = "true", bound = ""))]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TerminalSet<L: Lexicon> {
pub map: Map<L, LitSet>,
e: bool,
}
impl<L: Lexicon> std::fmt::Debug for TerminalSet<L> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut s = f.debug_set();
if self.e {
s.entry(&Epsilon);
}
for (ty, set) in self.map.iter_zip() {
match set.iter() {
None => {
s.entry(&Term(ty, None));
}
Some(lits) => {
for lit in lits {
s.entry(&Term(ty, Some(lit)));
}
}
}
}
s.finish()
}
}
impl<L: Lexicon> std::fmt::Display for TerminalSet<L> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let string = self.to_repr().into_iter().join(", ");
write!(f, "{{{string}}}")
}
}
#[doc(hidden)]
struct Term<L: Lexicon>(L, Option<&'static str>);
#[doc(hidden)]
struct Epsilon;
impl<L: Lexicon> std::fmt::Debug for Term<L> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.1 {
Some(lit) => write!(f, "{:?}:{:?}", self.0, lit),
None => write!(f, "{:?}:*", self.0),
}
}
}
impl std::fmt::Debug for Epsilon {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("<empty>").finish()
}
}
impl<L: Lexicon> TerminalSet<L> {
#[inline]
pub fn clear(&mut self) {
self.e = false;
for set in self.map.iter_mut() {
set.clear();
}
}
#[inline]
pub fn insert_e(&mut self) -> bool {
let changed = !self.e;
self.e = true;
changed
}
#[inline]
pub fn remove_e(&mut self) -> bool {
let changed = self.e;
self.e = false;
changed
}
#[inline]
pub fn contains_e(&self) -> bool {
self.e
}
pub fn insert(&mut self, ty: L, lit: Option<&'static str>) -> bool {
match lit {
Some(lit) => self.map.get_mut(ty).insert(lit),
None => self.map.get_mut(ty).union_universe(),
}
}
pub fn contains(&self, token: Option<TokenSrc<'_, L>>) -> bool {
match token {
None => self.e,
Some(token) => self.map.get(token.ty).contains(token.src),
}
}
pub fn union(&mut self, other: &Self, include_e: bool) -> bool {
let mut changed = if include_e {
if !self.e && other.e {
self.insert_e();
true
} else {
false
}
} else {
false
};
for (set, other_set) in self.map.iter_mut().zip(other.map.iter()) {
let next_changed = set.union(other_set);
changed |= next_changed;
}
changed
}
pub fn intersects(&self, other: &Self, include_e: bool) -> bool {
if include_e && self.e && other.e {
return true;
}
for (set, other_set) in self.map.iter().zip(other.map.iter()) {
if set.intersects(other_set) {
return true;
}
}
false
}
pub fn intersection_repr(&self, other: &Self, include_e: bool) -> BTreeSet<String> {
let mut terminals = BTreeSet::new();
if include_e && self.e && other.e {
terminals.insert("<empty>".to_string());
}
for ((ty, set), other_set) in self.map.iter_zip().zip(other.map.iter()) {
let intersection = set.intersection(other_set);
if intersection.is_empty() {
continue;
}
match intersection.iter() {
Some(lits) => {
for lit in lits {
terminals.insert(format!("\"{lit}\""));
}
}
None => {
terminals.insert(format!("{ty:?}"));
}
};
}
terminals
}
pub fn to_repr(&self) -> BTreeSet<String> {
let mut terminals = BTreeSet::new();
if self.e {
terminals.insert("<empty>".to_string());
}
for (ty, set) in self.map.iter_zip() {
match set.iter() {
Some(lits) => {
for lit in lits {
terminals.insert(format!("\"{lit}\""));
}
}
None => {
terminals.insert(format!("{ty:?}"));
}
}
}
terminals
}
}
#[macro_export]
macro_rules! terminal_set {
() => {
$crate::syntax::TerminalSet::default()
};
($L:ty) => {
$crate::syntax::TerminalSet::<$L>::default()
};
($L:ty { $($ty:ident:$term:tt),* }) => {
{
let mut set = $crate::syntax::TerminalSet::<$L>::default();
$(
$crate::insert_terminal!(set, <$L>::$ty, $term);
)*
set
}
};
($L:ty { e }) => {
{
let mut set = $crate::syntax::TerminalSet::<$L>::default();
set.insert_e();
set
}
};
($L:ty { e , $($ty:ident:$term:tt),* }) => {
{
let mut set = $crate::syntax::TerminalSet::<$L>::default();
$(
$crate::insert_terminal!(set, <$L>::$ty, $term);
)*
set.insert_e();
set
}
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! insert_terminal {
($set:ident, $ty:expr, $lit:literal) => {
$set.insert($ty, Some($lit))
};
($set:ident, $ty:expr, *) => {
$set.insert($ty, None)
};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test::TestTokenType as T;
#[test]
fn insert_epsilon() {
let mut set = TerminalSet::<T>::new();
assert!(!set.contains_e());
assert!(!set.contains(None));
assert!(set.insert_e());
assert!(set.contains_e());
assert!(set.contains(None));
assert!(!set.insert_e());
assert!(set.contains_e());
assert!(set.contains(None));
}
#[test]
fn insert() {
let mut set = TerminalSet::new();
assert!(set.insert(T::A, Some("a")));
assert!(set.contains(Some((T::A, "a").into())));
assert!(!set.insert(T::A, Some("a")));
assert!(set.insert(T::A, None));
assert!(set.contains(Some((T::A, "a").into())));
assert!(set.contains(Some((T::A, "b").into())));
assert!(!set.contains(Some((T::B, "a").into())));
assert!(set.insert(T::B, Some("a")));
assert!(set.contains(Some((T::B, "a").into())));
assert!(!set.contains(Some((T::B, "b").into())));
}
#[test]
fn union_empty() {
let mut set_a = TerminalSet::<T>::new();
let set_b = TerminalSet::<T>::new();
assert_eq!(set_a, set_b);
assert_eq!(set_a, set_a);
set_a.union(&set_b, true);
assert_eq!(set_a, set_b);
assert_eq!(set_a, set_a);
}
#[test]
fn union_disjoint_token() {
let mut set_a = TerminalSet::new();
assert!(set_a.insert(T::A, Some("a")));
assert!(set_a.insert(T::A, Some("b")));
let mut set_b = TerminalSet::new();
assert!(set_b.insert(T::B, None));
let mut expected = TerminalSet::new();
assert!(expected.insert(T::A, Some("a")));
assert!(expected.insert(T::A, Some("b")));
assert!(expected.insert(T::B, None));
assert!(set_a.union(&set_b, true));
assert_eq!(set_a, expected);
assert!(set_b.union(&set_a, true));
assert_eq!(set_b, expected);
}
#[test]
fn union_exclude_e() {
let mut set_a = TerminalSet::new();
assert!(set_a.insert(T::A, Some("a")));
assert!(set_a.insert(T::B, Some("b")));
let mut set_b = set_a.clone();
assert!(set_b.insert_e());
assert_ne!(set_a, set_b);
assert!(!set_a.union(&set_b, false));
assert!(set_a.union(&set_b, true));
}
#[test]
fn union_to_any() {
let mut set_a = TerminalSet::new();
assert!(set_a.insert(T::A, Some("a")));
assert!(set_a.insert(T::B, None));
let mut set_b = TerminalSet::new();
assert!(set_b.insert(T::A, None));
assert!(set_b.insert(T::B, Some("b")));
let mut expected = TerminalSet::new();
assert!(expected.insert(T::A, None));
assert!(expected.insert(T::B, None));
assert!(set_a.union(&set_b, true));
assert_eq!(set_a, expected);
}
}