use super::state_set::StateSet;
use super::types::StateId;
use super::{NFAChar, NFA};
use rustc_hash::FxHashMap;
pub type DFAStateChar = Vec<StateId>;
type DFAStateId = u32;
#[derive(Debug, Clone)]
pub struct LazyDFAChar {
nfa: NFAChar,
cache: FxHashMap<(DFAStateId, char), DFAStateId>,
state_to_id: FxHashMap<DFAStateChar, DFAStateId>,
id_to_state: Vec<DFAStateChar>,
initial_state_id: DFAStateId,
accepting_cache: FxHashMap<DFAStateId, bool>,
}
impl LazyDFAChar {
pub fn new(nfa: NFAChar) -> Self {
let initial_closure = nfa.epsilon_closure_single(nfa.start());
let initial_state = Self::set_to_state(&initial_closure);
let mut state_to_id = FxHashMap::default();
state_to_id.insert(initial_state.clone(), 0);
let id_to_state = vec![initial_state];
Self {
nfa,
cache: FxHashMap::default(),
state_to_id,
id_to_state,
initial_state_id: 0,
accepting_cache: FxHashMap::default(),
}
}
fn set_to_state(states: &StateSet) -> DFAStateChar {
let mut vec: Vec<StateId> = states.iter().collect();
vec.sort_unstable();
vec
}
fn state_to_set(state: &DFAStateChar) -> StateSet {
state.iter().copied().collect()
}
#[inline]
fn get_or_create_id(&mut self, state: DFAStateChar) -> DFAStateId {
if let Some(&id) = self.state_to_id.get(&state) {
return id;
}
let id = self.id_to_state.len() as DFAStateId;
self.state_to_id.insert(state.clone(), id);
self.id_to_state.push(state);
id
}
#[inline]
fn get_state(&self, id: DFAStateId) -> &DFAStateChar {
&self.id_to_state[id as usize]
}
#[inline]
pub fn initial_state(&self) -> &DFAStateChar {
self.get_state(self.initial_state_id)
}
#[inline]
fn is_accepting_id(&mut self, state_id: DFAStateId) -> bool {
if let Some(&accepting) = self.accepting_cache.get(&state_id) {
return accepting;
}
let state = self.get_state(state_id);
let accepting = state.iter().any(|&s| self.nfa.is_final(s));
self.accepting_cache.insert(state_id, accepting);
accepting
}
pub fn is_accepting(&mut self, state: &DFAStateChar) -> bool {
if let Some(&state_id) = self.state_to_id.get(state) {
self.is_accepting_id(state_id)
} else {
state.iter().any(|&s| self.nfa.is_final(s))
}
}
#[inline]
fn transition_id(&mut self, state_id: DFAStateId, c: char) -> DFAStateId {
let cache_key = (state_id, c);
if let Some(&next_id) = self.cache.get(&cache_key) {
return next_id;
}
let state = self.get_state(state_id).clone(); let current_set = Self::state_to_set(&state);
let mut next_set = StateSet::new();
for nfa_state in current_set.iter() {
for trans in self.nfa.transitions_from(nfa_state) {
if trans.label.matches(c) && trans.label.consumes_input() {
next_set.insert(trans.to);
}
}
}
let next_closure = self.nfa.epsilon_closure(&next_set);
let next_state = Self::set_to_state(&next_closure);
let next_id = self.get_or_create_id(next_state);
self.cache.insert(cache_key, next_id);
next_id
}
pub fn transition(&mut self, state: &DFAStateChar, c: char) -> DFAStateChar {
let state_id = if let Some(&id) = self.state_to_id.get(state) {
id
} else {
self.get_or_create_id(state.clone())
};
let next_id = self.transition_id(state_id, c);
self.get_state(next_id).clone()
}
pub fn accepts(&mut self, input: &str) -> bool {
let mut current_id = self.initial_state_id;
for c in input.chars() {
current_id = self.transition_id(current_id, c);
if self.get_state(current_id).is_empty() {
return false;
}
}
self.is_accepting_id(current_id)
}
#[inline]
pub fn cache_size(&self) -> usize {
self.cache.len()
}
#[inline]
pub fn state_count(&self) -> usize {
self.id_to_state.len()
}
pub fn clear_cache(&mut self) {
self.cache.clear();
self.accepting_cache.clear();
let initial = self.id_to_state[0].clone();
self.state_to_id.clear();
self.state_to_id.insert(initial.clone(), 0);
self.id_to_state.clear();
self.id_to_state.push(initial);
}
pub fn cache_stats(&self) -> CacheStats {
CacheStats {
transition_cache_size: self.cache.len(),
accepting_cache_size: self.accepting_cache.len(),
}
}
}
pub type DFAState = Vec<StateId>;
#[derive(Debug, Clone)]
pub struct LazyDFA {
nfa: NFA,
cache: FxHashMap<(DFAStateId, u8), DFAStateId>,
state_to_id: FxHashMap<DFAState, DFAStateId>,
id_to_state: Vec<DFAState>,
initial_state_id: DFAStateId,
accepting_cache: FxHashMap<DFAStateId, bool>,
}
impl LazyDFA {
pub fn new(nfa: NFA) -> Self {
let initial_closure = nfa.epsilon_closure_single(nfa.start());
let initial_state = Self::set_to_state(&initial_closure);
let mut state_to_id = FxHashMap::default();
state_to_id.insert(initial_state.clone(), 0);
let id_to_state = vec![initial_state];
Self {
nfa,
cache: FxHashMap::default(),
state_to_id,
id_to_state,
initial_state_id: 0,
accepting_cache: FxHashMap::default(),
}
}
fn set_to_state(states: &StateSet) -> DFAState {
let mut vec: Vec<StateId> = states.iter().collect();
vec.sort_unstable();
vec
}
fn state_to_set(state: &DFAState) -> StateSet {
state.iter().copied().collect()
}
#[inline]
fn get_or_create_id(&mut self, state: DFAState) -> DFAStateId {
if let Some(&id) = self.state_to_id.get(&state) {
return id;
}
let id = self.id_to_state.len() as DFAStateId;
self.state_to_id.insert(state.clone(), id);
self.id_to_state.push(state);
id
}
#[inline]
fn get_state(&self, id: DFAStateId) -> &DFAState {
&self.id_to_state[id as usize]
}
#[inline]
pub fn initial_state(&self) -> &DFAState {
self.get_state(self.initial_state_id)
}
#[inline]
fn is_accepting_id(&mut self, state_id: DFAStateId) -> bool {
if let Some(&accepting) = self.accepting_cache.get(&state_id) {
return accepting;
}
let state = self.get_state(state_id);
let accepting = state.iter().any(|&s| self.nfa.is_final(s));
self.accepting_cache.insert(state_id, accepting);
accepting
}
pub fn is_accepting(&mut self, state: &DFAState) -> bool {
if let Some(&state_id) = self.state_to_id.get(state) {
self.is_accepting_id(state_id)
} else {
state.iter().any(|&s| self.nfa.is_final(s))
}
}
#[inline]
fn transition_id(&mut self, state_id: DFAStateId, b: u8) -> DFAStateId {
let cache_key = (state_id, b);
if let Some(&next_id) = self.cache.get(&cache_key) {
return next_id;
}
let state = self.get_state(state_id).clone();
let current_set = Self::state_to_set(&state);
let mut next_set = StateSet::new();
for nfa_state in current_set.iter() {
for trans in self.nfa.transitions_from(nfa_state) {
if trans.label.matches(b) && trans.label.consumes_input() {
next_set.insert(trans.to);
}
}
}
let next_closure = self.nfa.epsilon_closure(&next_set);
let next_state = Self::set_to_state(&next_closure);
let next_id = self.get_or_create_id(next_state);
self.cache.insert(cache_key, next_id);
next_id
}
pub fn transition(&mut self, state: &DFAState, b: u8) -> DFAState {
let state_id = if let Some(&id) = self.state_to_id.get(state) {
id
} else {
self.get_or_create_id(state.clone())
};
let next_id = self.transition_id(state_id, b);
self.get_state(next_id).clone()
}
pub fn accepts(&mut self, input: &[u8]) -> bool {
let mut current_id = self.initial_state_id;
for &b in input {
current_id = self.transition_id(current_id, b);
if self.get_state(current_id).is_empty() {
return false;
}
}
self.is_accepting_id(current_id)
}
#[inline]
pub fn cache_size(&self) -> usize {
self.cache.len()
}
#[inline]
pub fn state_count(&self) -> usize {
self.id_to_state.len()
}
pub fn clear_cache(&mut self) {
self.cache.clear();
self.accepting_cache.clear();
let initial = self.id_to_state[0].clone();
self.state_to_id.clear();
self.state_to_id.insert(initial.clone(), 0);
self.id_to_state.clear();
self.id_to_state.push(initial);
}
pub fn cache_stats(&self) -> CacheStats {
CacheStats {
transition_cache_size: self.cache.len(),
accepting_cache_size: self.accepting_cache.len(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CacheStats {
pub transition_cache_size: usize,
pub accepting_cache_size: usize,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::phonetic::nfa::compiler::{compile, compile_bytes};
use crate::phonetic::regex::{parse, parse_bytes};
#[test]
fn test_lazy_dfa_simple() {
let nfa = compile(&parse("abc").expect("test fixture: parse must be Ok"))
.expect("test fixture: compile must be Ok");
let mut dfa = LazyDFAChar::new(nfa);
assert!(dfa.accepts("abc"));
assert!(!dfa.accepts("ab"));
assert!(!dfa.accepts("abcd"));
assert!(!dfa.accepts("xyz"));
}
#[test]
fn test_lazy_dfa_alternation() {
let nfa = compile(&parse("cat|dog").expect("test fixture: parse must be Ok"))
.expect("test fixture: compile must be Ok");
let mut dfa = LazyDFAChar::new(nfa);
assert!(dfa.accepts("cat"));
assert!(dfa.accepts("dog"));
assert!(!dfa.accepts("ca"));
assert!(!dfa.accepts("do"));
assert!(!dfa.accepts("catdog"));
}
#[test]
fn test_lazy_dfa_star() {
let nfa = compile(&parse("a*").expect("test fixture: parse must be Ok"))
.expect("test fixture: compile must be Ok");
let mut dfa = LazyDFAChar::new(nfa);
assert!(dfa.accepts(""));
assert!(dfa.accepts("a"));
assert!(dfa.accepts("aa"));
assert!(dfa.accepts("aaa"));
assert!(!dfa.accepts("b"));
assert!(!dfa.accepts("ab"));
}
#[test]
fn test_lazy_dfa_plus() {
let nfa = compile(&parse("a+").expect("test fixture: parse must be Ok"))
.expect("test fixture: compile must be Ok");
let mut dfa = LazyDFAChar::new(nfa);
assert!(!dfa.accepts(""));
assert!(dfa.accepts("a"));
assert!(dfa.accepts("aa"));
assert!(dfa.accepts("aaa"));
assert!(!dfa.accepts("b"));
}
#[test]
fn test_lazy_dfa_char_class() {
let nfa = compile(&parse("[aeiou]+").expect("test fixture: parse must be Ok"))
.expect("test fixture: compile must be Ok");
let mut dfa = LazyDFAChar::new(nfa);
assert!(dfa.accepts("a"));
assert!(dfa.accepts("aeiou"));
assert!(dfa.accepts("oui"));
assert!(!dfa.accepts(""));
assert!(!dfa.accepts("xyz"));
}
#[test]
fn test_lazy_dfa_complex() {
let nfa = compile(&parse("(ph|f)one").expect("test fixture: parse must be Ok"))
.expect("test fixture: compile must be Ok");
let mut dfa = LazyDFAChar::new(nfa);
assert!(dfa.accepts("phone"));
assert!(dfa.accepts("fone"));
assert!(!dfa.accepts("bone"));
assert!(!dfa.accepts("phon"));
}
#[test]
fn test_lazy_dfa_caching() {
let nfa = compile(&parse("test").expect("test fixture: parse must be Ok"))
.expect("test fixture: compile must be Ok");
let mut dfa = LazyDFAChar::new(nfa);
assert!(dfa.accepts("test"));
let stats1 = dfa.cache_stats();
assert!(stats1.transition_cache_size > 0);
assert!(dfa.accepts("test"));
let stats2 = dfa.cache_stats();
assert_eq!(stats1.transition_cache_size, stats2.transition_cache_size);
}
#[test]
fn test_lazy_dfa_cache_clear() {
let nfa = compile(&parse("test").expect("test fixture: parse must be Ok"))
.expect("test fixture: compile must be Ok");
let mut dfa = LazyDFAChar::new(nfa);
assert!(dfa.accepts("test"));
assert!(dfa.cache_size() > 0);
dfa.clear_cache();
assert_eq!(dfa.cache_size(), 0);
assert!(dfa.accepts("test"));
}
#[test]
fn test_lazy_dfa_bytes() {
let nfa = compile_bytes(&parse_bytes(b"hello").expect("test fixture: parse must be Ok"))
.expect("test fixture: compile must be Ok");
let mut dfa = LazyDFA::new(nfa);
assert!(dfa.accepts(b"hello"));
assert!(!dfa.accepts(b"world"));
assert!(!dfa.accepts(b"hell"));
}
#[test]
fn test_lazy_dfa_bytes_alternation() {
let nfa = compile_bytes(&parse_bytes(b"yes|no").expect("test fixture: parse must be Ok"))
.expect("test fixture: compile must be Ok");
let mut dfa = LazyDFA::new(nfa);
assert!(dfa.accepts(b"yes"));
assert!(dfa.accepts(b"no"));
assert!(!dfa.accepts(b"maybe"));
}
#[test]
fn test_lazy_dfa_epsilon_pattern() {
let nfa = compile(&parse("a*").expect("test fixture: parse must be Ok"))
.expect("test fixture: compile must be Ok");
let mut dfa = LazyDFAChar::new(nfa);
assert!(dfa.accepts(""));
assert!(dfa.accepts("a"));
assert!(dfa.accepts("aa"));
}
#[test]
fn test_lazy_dfa_optional() {
let nfa = compile(&parse("colou?r").expect("test fixture: parse must be Ok"))
.expect("test fixture: compile must be Ok");
let mut dfa = LazyDFAChar::new(nfa);
assert!(dfa.accepts("color"));
assert!(dfa.accepts("colour"));
assert!(!dfa.accepts("colr"));
assert!(!dfa.accepts("colouur"));
}
}