use super::state_set::StateSet;
use super::types::StateId;
use super::{NFAChar, NFA};
use rustc_hash::FxHashSet;
#[derive(Debug, Clone)]
pub struct IncrementalMatcherChar {
nfa: NFAChar,
current_states: FxHashSet<StateId>,
is_dead: bool,
chars_processed: usize,
}
impl IncrementalMatcherChar {
pub fn new(nfa: NFAChar) -> Self {
let current_states: FxHashSet<StateId> = nfa.epsilon_closure_single(nfa.start()).into();
Self {
nfa,
current_states,
is_dead: false,
chars_processed: 0,
}
}
pub fn feed(&mut self, c: char) -> bool {
if self.is_dead {
return false;
}
let mut next_states = StateSet::new();
for &state in &self.current_states {
for trans in self.nfa.transitions_from(state) {
if trans.label.matches(c) && trans.label.consumes_input() {
next_states.insert(trans.to);
}
}
}
self.current_states = self.nfa.epsilon_closure(&next_states).into();
self.chars_processed += 1;
if self.current_states.is_empty() {
self.is_dead = true;
return false;
}
self.is_accepting()
}
pub fn feed_str(&mut self, s: &str) -> bool {
for c in s.chars() {
self.feed(c);
if self.is_dead {
return false;
}
}
self.is_accepting()
}
#[inline]
pub fn is_accepting(&self) -> bool {
if self.is_dead {
return false;
}
self.current_states.iter().any(|&s| self.nfa.is_final(s))
}
#[inline]
pub fn is_dead(&self) -> bool {
self.is_dead
}
#[inline]
pub fn chars_processed(&self) -> usize {
self.chars_processed
}
#[inline]
pub fn active_state_count(&self) -> usize {
self.current_states.len()
}
pub fn reset(&mut self) {
self.current_states = self.nfa.epsilon_closure_single(self.nfa.start()).into();
self.is_dead = false;
self.chars_processed = 0;
}
#[inline]
pub fn is_alive(&self) -> bool {
!self.is_dead
}
#[inline]
pub fn current_states(&self) -> &FxHashSet<StateId> {
&self.current_states
}
pub fn snapshot(&self) -> MatcherSnapshotChar {
MatcherSnapshotChar {
states: self.current_states.clone(),
is_dead: self.is_dead,
chars_processed: self.chars_processed,
}
}
pub fn restore(&mut self, snapshot: &MatcherSnapshotChar) {
self.current_states = snapshot.states.clone();
self.is_dead = snapshot.is_dead;
self.chars_processed = snapshot.chars_processed;
}
}
#[derive(Debug, Clone)]
pub struct MatcherSnapshotChar {
pub states: FxHashSet<StateId>,
pub is_dead: bool,
pub chars_processed: usize,
}
#[derive(Debug, Clone)]
pub struct IncrementalMatcher {
nfa: NFA,
current_states: FxHashSet<StateId>,
is_dead: bool,
bytes_processed: usize,
}
impl IncrementalMatcher {
pub fn new(nfa: NFA) -> Self {
let current_states: FxHashSet<StateId> = nfa.epsilon_closure_single(nfa.start()).into();
Self {
nfa,
current_states,
is_dead: false,
bytes_processed: 0,
}
}
pub fn feed(&mut self, b: u8) -> bool {
if self.is_dead {
return false;
}
let mut next_states = StateSet::new();
for &state in &self.current_states {
for trans in self.nfa.transitions_from(state) {
if trans.label.matches(b) && trans.label.consumes_input() {
next_states.insert(trans.to);
}
}
}
self.current_states = self.nfa.epsilon_closure(&next_states).into();
self.bytes_processed += 1;
if self.current_states.is_empty() {
self.is_dead = true;
return false;
}
self.is_accepting()
}
pub fn feed_bytes(&mut self, bytes: &[u8]) -> bool {
for &b in bytes {
self.feed(b);
if self.is_dead {
return false;
}
}
self.is_accepting()
}
#[inline]
pub fn is_accepting(&self) -> bool {
if self.is_dead {
return false;
}
self.current_states.iter().any(|&s| self.nfa.is_final(s))
}
#[inline]
pub fn is_dead(&self) -> bool {
self.is_dead
}
#[inline]
pub fn bytes_processed(&self) -> usize {
self.bytes_processed
}
#[inline]
pub fn active_state_count(&self) -> usize {
self.current_states.len()
}
pub fn reset(&mut self) {
self.current_states = self.nfa.epsilon_closure_single(self.nfa.start()).into();
self.is_dead = false;
self.bytes_processed = 0;
}
#[inline]
pub fn is_alive(&self) -> bool {
!self.is_dead
}
#[inline]
pub fn current_states(&self) -> &FxHashSet<StateId> {
&self.current_states
}
pub fn snapshot(&self) -> MatcherSnapshot {
MatcherSnapshot {
states: self.current_states.clone(),
is_dead: self.is_dead,
bytes_processed: self.bytes_processed,
}
}
pub fn restore(&mut self, snapshot: &MatcherSnapshot) {
self.current_states = snapshot.states.clone();
self.is_dead = snapshot.is_dead;
self.bytes_processed = snapshot.bytes_processed;
}
}
#[derive(Debug, Clone)]
pub struct MatcherSnapshot {
pub states: FxHashSet<StateId>,
pub is_dead: bool,
pub bytes_processed: usize,
}
#[derive(Debug, Clone)]
pub struct IncrementalProductMatcherChar {
nfa: NFAChar,
word: Vec<char>,
word_pos: usize,
max_distance: u8,
current_states: FxHashSet<(StateId, u8)>,
chars_processed: usize,
is_dead: bool,
}
impl IncrementalProductMatcherChar {
pub fn new(nfa: NFAChar, word: &str, max_distance: u8) -> Self {
let word_chars: Vec<char> = word.chars().collect();
let initial_closure = nfa.epsilon_closure_single(nfa.start());
let mut current_states = FxHashSet::default();
for state in initial_closure.iter() {
current_states.insert((state, 0));
}
let mut to_add = Vec::new();
for &(state, dist) in ¤t_states {
if dist < max_distance {
to_add.push((state, dist + 1)); }
}
for item in to_add {
current_states.insert(item);
}
Self {
nfa,
word: word_chars,
word_pos: 0,
max_distance,
current_states,
chars_processed: 0,
is_dead: false,
}
}
pub fn feed(&mut self, c: char) -> bool {
if self.is_dead {
return false;
}
let mut next_states = FxHashSet::default();
for &(state, dist) in &self.current_states {
let word_char = self.word.get(self.word_pos).copied();
for trans in self.nfa.transitions_from(state) {
if trans.label.matches(c) && trans.label.consumes_input() {
if let Some(wc) = word_char {
if c == wc {
next_states.insert((trans.to, dist));
} else if dist < self.max_distance {
next_states.insert((trans.to, dist + 1));
}
} else if dist < self.max_distance {
next_states.insert((trans.to, dist + 1));
}
}
}
if dist < self.max_distance {
next_states.insert((state, dist + 1));
}
}
let mut with_epsilon = FxHashSet::default();
for &(state, dist) in &next_states {
for closed_state in self.nfa.epsilon_closure_single(state).iter() {
with_epsilon.insert((closed_state, dist));
}
}
self.current_states = with_epsilon;
self.chars_processed += 1;
if self.word_pos < self.word.len() {
self.word_pos += 1;
}
if self.current_states.is_empty() {
self.is_dead = true;
return false;
}
true
}
pub fn is_accepting(&self) -> bool {
if self.is_dead {
return false;
}
let remaining_word = self.word.len().saturating_sub(self.word_pos);
for &(state, dist) in &self.current_states {
if self.nfa.is_final(state) {
let total_dist = dist + remaining_word as u8;
if total_dist <= self.max_distance {
return true;
}
}
}
false
}
#[inline]
pub fn is_dead(&self) -> bool {
self.is_dead
}
#[inline]
pub fn chars_processed(&self) -> usize {
self.chars_processed
}
#[inline]
pub fn word_position(&self) -> usize {
self.word_pos
}
pub fn reset(&mut self) {
let initial_closure = self.nfa.epsilon_closure_single(self.nfa.start());
self.current_states.clear();
for state in initial_closure.iter() {
self.current_states.insert((state, 0));
}
self.word_pos = 0;
self.chars_processed = 0;
self.is_dead = false;
}
pub fn min_current_distance(&self) -> Option<u8> {
self.current_states.iter().map(|&(_, d)| d).min()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::phonetic::nfa::compiler::{compile, compile_bytes};
use crate::phonetic::regex::{parse, parse_bytes};
#[test]
fn test_incremental_simple() {
let nfa = compile(&parse("hello").expect("parse")).expect("compile");
let mut matcher = IncrementalMatcherChar::new(nfa);
assert!(!matcher.is_accepting());
assert!(!matcher.is_dead());
matcher.feed('h');
assert!(!matcher.is_accepting());
assert!(!matcher.is_dead());
matcher.feed('e');
matcher.feed('l');
matcher.feed('l');
matcher.feed('o');
assert!(matcher.is_accepting());
assert_eq!(matcher.chars_processed(), 5);
}
#[test]
fn test_incremental_dead_state() {
let nfa = compile(&parse("abc").expect("parse")).expect("compile");
let mut matcher = IncrementalMatcherChar::new(nfa);
matcher.feed('x'); assert!(matcher.is_dead());
assert!(!matcher.is_accepting());
matcher.feed('a');
assert!(matcher.is_dead());
}
#[test]
fn test_incremental_reset() {
let nfa = compile(&parse("test").expect("parse")).expect("compile");
let mut matcher = IncrementalMatcherChar::new(nfa);
matcher.feed('t');
matcher.feed('e');
assert_eq!(matcher.chars_processed(), 2);
matcher.reset();
assert_eq!(matcher.chars_processed(), 0);
assert!(!matcher.is_dead());
matcher.feed_str("test");
assert!(matcher.is_accepting());
}
#[test]
fn test_incremental_snapshot_restore() {
let nfa = compile(&parse("abc").expect("parse")).expect("compile");
let mut matcher = IncrementalMatcherChar::new(nfa);
matcher.feed('a');
let snapshot = matcher.snapshot();
matcher.feed('x'); assert!(matcher.is_dead());
matcher.restore(&snapshot);
assert!(!matcher.is_dead());
assert_eq!(matcher.chars_processed(), 1);
matcher.feed('b');
matcher.feed('c');
assert!(matcher.is_accepting());
}
#[test]
fn test_incremental_alternation() {
let nfa = compile(&parse("cat|dog").expect("parse")).expect("compile");
let mut matcher = IncrementalMatcherChar::new(nfa);
matcher.feed_str("cat");
assert!(matcher.is_accepting());
matcher.reset();
matcher.feed_str("dog");
assert!(matcher.is_accepting());
matcher.reset();
matcher.feed_str("bat");
assert!(!matcher.is_accepting());
}
#[test]
fn test_incremental_star() {
let nfa = compile(&parse("a*b").expect("parse")).expect("compile");
let mut matcher = IncrementalMatcherChar::new(nfa);
matcher.feed('b');
assert!(matcher.is_accepting());
matcher.reset();
matcher.feed_str("ab");
assert!(matcher.is_accepting());
matcher.reset();
matcher.feed_str("aaab");
assert!(matcher.is_accepting());
}
#[test]
fn test_incremental_bytes() {
let nfa = compile_bytes(&parse_bytes(b"hello").expect("parse")).expect("compile");
let mut matcher = IncrementalMatcher::new(nfa);
assert!(!matcher.is_accepting());
matcher.feed_bytes(b"hello");
assert!(matcher.is_accepting());
assert_eq!(matcher.bytes_processed(), 5);
}
#[test]
fn test_incremental_bytes_dead() {
let nfa = compile_bytes(&parse_bytes(b"abc").expect("parse")).expect("compile");
let mut matcher = IncrementalMatcher::new(nfa);
matcher.feed(b'x');
assert!(matcher.is_dead());
}
#[test]
fn test_incremental_feed_str() {
let nfa = compile(&parse("helloworld").expect("parse")).expect("compile");
let mut matcher = IncrementalMatcherChar::new(nfa);
matcher.feed_str("hello");
assert!(!matcher.is_accepting());
assert!(!matcher.is_dead());
matcher.feed_str("world");
assert!(matcher.is_accepting());
}
#[test]
fn test_incremental_active_states() {
let nfa = compile(&parse("a|ab").expect("parse")).expect("compile");
let mut matcher = IncrementalMatcherChar::new(nfa);
let initial_count = matcher.active_state_count();
assert!(initial_count >= 1);
matcher.feed('a');
assert!(matcher.is_accepting()); }
#[test]
fn test_incremental_product_exact() {
let nfa = compile(&parse("test").expect("parse")).expect("compile");
let mut matcher = IncrementalProductMatcherChar::new(nfa, "test", 2);
matcher.feed('t');
matcher.feed('e');
matcher.feed('s');
matcher.feed('t');
assert!(matcher.is_accepting());
}
#[test]
fn test_incremental_product_within_distance() {
let nfa = compile(&parse("test").expect("parse")).expect("compile");
let mut matcher = IncrementalProductMatcherChar::new(nfa, "test", 2);
matcher.feed('t');
}
#[test]
fn test_incremental_product_dead() {
let nfa = compile(&parse("abc").expect("parse")).expect("compile");
let mut matcher = IncrementalProductMatcherChar::new(nfa, "abc", 1);
matcher.feed('x');
matcher.feed('y');
matcher.feed('z');
}
#[test]
fn test_incremental_product_reset() {
let nfa = compile(&parse("test").expect("parse")).expect("compile");
let mut matcher = IncrementalProductMatcherChar::new(nfa, "test", 1);
matcher.feed('t');
matcher.feed('e');
matcher.reset();
assert_eq!(matcher.chars_processed(), 0);
assert_eq!(matcher.word_position(), 0);
assert!(!matcher.is_dead());
}
}