use std::{collections::{HashMap, HashSet, VecDeque}, iter}; mod token; pub use token::ParsingError; use token::parse; const START_NFA: usize = usize::MAX; const START_DFA: usize = 0; /// A regular expression implemented as a non-deterministic finite automaton. #[derive(Debug)] pub struct RegexpNFA { rules: HashMap<(usize, char), HashSet>, end_states: HashSet, alphabet: Vec } impl RegexpNFA { pub fn new(pattern: &String) -> Result { let r = parse(pattern, 0)?; let pattern_chars = Vec::from_iter(pattern.chars()); let mut rules: HashMap<(usize, char), HashSet> = HashMap::new(); let mut alphabet: HashSet = HashSet::new(); for i in r.list_first() { let c = pattern_chars[i]; alphabet.insert(c); let key = (START_NFA, c); match rules.get_mut(&key) { Some(set) => {set.insert(i);}, None => {rules.insert(key, HashSet::from([i]));} }; } for (i, j) in r.list_neighbours() { let c = pattern_chars[j]; alphabet.insert(c); let key = (i, c); match rules.get_mut(&key) { Some(set) => {set.insert(j);}, None => {rules.insert(key, HashSet::from([j]));} }; } let mut end_states = HashSet::from_iter(r.list_last().into_iter()); if r.is_skippable() { end_states.insert(START_NFA); } let mut alphabet_vec = Vec::from_iter(alphabet.into_iter()); alphabet_vec.sort(); return Ok(RegexpNFA{rules, end_states, alphabet: alphabet_vec}); } /// Decide if a string matches the regexp. pub fn eval(&self, s: String) -> bool { let mut multistate = HashSet::from([START_NFA]); for c in s.chars() { let mut new_multistate = HashSet::new(); for state in multistate { if let Some(x) = self.rules.get(&(state, c)) { new_multistate = new_multistate.union(&x).map(|&y| y).collect(); } else if let Some(x) = self.rules.get(&(state, '.')) { new_multistate = new_multistate.union(&x).map(|&y| y).collect(); } } multistate = new_multistate; } return multistate.iter().any(|x| self.end_states.contains(x)); } /// Convert the non-deterministic finite automaton into a deterministic one. pub fn determinize(&self) -> RegexpDFA { const FAIL: usize = usize::MAX; let alphabet_index: HashMap = self.alphabet.iter().enumerate().map(|(i, c)| (*c, i)).collect(); let n = alphabet_index.len(); let mut compact_rules = vec![FAIL; n]; let mut end_states: HashSet = HashSet::new(); if self.end_states.contains(&START_NFA) {end_states.insert(START_DFA);} // mapping the NFA state subsets -> DFA states let mut index = HashMap::from([(vec![START_NFA], START_DFA)]); let mut stack = vec![vec![START_NFA]]; while !stack.is_empty() { let multistate = stack.pop().unwrap(); let mut new_rules: HashMap> = HashMap::new(); // collect all possible destination states from the multistate for key in self.rules.keys().filter(|(st, _c)| multistate.binary_search(st).is_ok()) { let (_st, c) = key; if !new_rules.contains_key(c) { new_rules.insert(*c, HashSet::new()); } for target in &self.rules[key] { new_rules.get_mut(c).unwrap().insert(*target); } } // build a row for the DFA transition function table for (c, target_set) in new_rules.into_iter() { let mut target_vec = Vec::from_iter(target_set.into_iter()); target_vec.sort(); let is_end = target_vec.iter().any(|st| self.end_states.contains(st)); if !index.contains_key(&target_vec) { let target_new = index.len(); index.insert(target_vec.clone(), target_new); compact_rules.extend(iter::repeat(FAIL).take(n)); stack.push(target_vec.clone()); } compact_rules[index[&multistate]*n + alphabet_index[&c]] = index[&target_vec]; if is_end { end_states.insert(index[&target_vec]); } } } // add a fail state, so the transition function is complete let fail = index.len(); compact_rules = compact_rules.into_iter().map(|st| if st != FAIL {st} else {fail}).collect(); compact_rules.extend(iter::repeat(fail).take(n)); return RegexpDFA::new(compact_rules, end_states, alphabet_index); } } /// A regular expression implemented as a deterministic finite automaton. /// This simplifies support for more features compared to the NFA Regexp. #[derive(Clone)] pub struct RegexpDFA { rules: Vec, end_states: HashSet, alphabet_index: HashMap } impl RegexpDFA { /// Construct a DFA with the provided parameters, or a minimal DFA if the parameters are empty. pub fn new(rules: Vec, end_states: HashSet, alphabet_index: HashMap) -> RegexpDFA { if rules.len() > 0 { return RegexpDFA{rules, end_states, alphabet_index}; } else { // this saves us checking for an empty `alphabet_index` in other methods. return RegexpDFA{ rules: vec![1, 1], end_states, alphabet_index: HashMap::from([('\0', 0)]) }; } } /// Decide if a string matches the regexp. pub fn eval(&self, s: String) -> bool { let n = self.alphabet_index.len(); let mut state = START_DFA; for c in s.chars() { if let Some(ci) = self.alphabet_index.get(&c) { state = self.rules[state*n + ci]; } else { return false; } } return self.end_states.contains(&state); } /// Minimize the automaton by collapsing equivalent states. pub fn reduce(&self) -> RegexpDFA { let partition = self.find_equivalent_states(); return self.collapse_states(partition); } /// Normalize the automaton by relabeling the states in a breadth first search walkthrough order and remove unreachable states. pub fn normalize(&self) -> RegexpDFA { let n = self.alphabet_index.len(); let m = self.rules.len()/n; let fail = m; let mut index: Vec = vec![fail;m]; index[0] = 0; let mut queue = VecDeque::from([START_DFA]); let mut rules = vec![]; let mut k = 1; while !queue.is_empty() { let si = queue.pop_front().unwrap(); let row = &self.rules[si*n..(si+1)*n]; for &sj in row { if sj != fail && index[sj] == fail { index[sj] = k; k += 1; queue.push_back(sj); } } rules.extend(row.iter().map(|&st| index[st])); } let end_states = self.end_states.iter().map(|st| index[*st]).collect(); return RegexpDFA{rules, end_states, alphabet_index: self.alphabet_index.clone()}; } /// Find the shortest string that is accepted by self or `r`, but not both. /// It is expected that the automatons are already reduced and normalized. pub fn find_distinguishing_string(&self, other: &RegexpDFA) -> Option { if self.rules == other.rules && self.end_states == other.end_states { return None; } let r1 = self.expand_alphabet(&other.alphabet_index); let r2 = other.expand_alphabet(&self.alphabet_index); let product = r1.build_product_automaton(&r2); let n = product.alphabet_index.len(); // mapping letter keys -> letters let inverse_alphabet_index: HashMap = HashMap::from_iter(product.alphabet_index.iter().map(|(&k, &v)| (v, k))); // look for an accepting state with a BFS let mut queue = VecDeque::from([(0, "".to_string())]); let mut visited = HashSet::new(); while !queue.is_empty() { let (state, acc) = queue.pop_front().unwrap(); if product.end_states.contains(&state) { return Some(acc); } for (i, target) in product.rules[state*n..(state+1)*n].iter().enumerate() { if !visited.contains(target) { queue.push_back((*target, acc.clone()+&String::from(inverse_alphabet_index[&i]))); visited.insert(target); } } } panic!("Failed to detect the Regexps as equivalent and failed to find a distinguishing string."); } /// Partition states into their equivalence classes with Hopcroft's algorithm. fn find_equivalent_states(&self) -> Vec> { let n = self.alphabet_index.len(); let m = self.rules.len() / n; let mut inverse_rules = vec![HashSet::::new();m*n]; for i in 0..m { for j in 0..n { let target = self.rules[i*n + j]; inverse_rules[target*n + j].insert(i); } } // store state subsets here so we can just pass around their indices let mut set_bag = vec![ self.end_states.clone(), HashSet::from_iter(0..m).difference(&self.end_states).copied().collect() ]; let mut res = HashSet::::from([0, 1]); let mut work = HashSet::::from([0, 1]); while !work.is_empty() { let key = *work.iter().next().unwrap(); work.remove(&key); let target_set = set_bag[key].clone(); for j in 0..n { let source_set = HashSet::::from_iter(target_set.iter().flat_map(|&t| inverse_rules[t*n+j].iter()).copied()); for k in res.clone().into_iter() { let part = &set_bag[k]; let intersection = part.intersection(&source_set).copied().collect::>(); let diff = part.difference(&source_set).copied().collect::>(); if intersection.is_empty() || diff.is_empty() { continue; } res.remove(&k); let ki = set_bag.len(); set_bag.push(intersection); res.insert(ki); let kd = set_bag.len(); set_bag.push(diff); res.insert(kd); if work.contains(&k) { work.remove(&k); work.insert(ki); work.insert(kd); } else if set_bag[ki].len() < set_bag[kd].len() { work.insert(ki); } else { work.insert(kd); } } } } return Vec::from_iter(res.into_iter().map(|k| std::mem::take(&mut set_bag[k]))); } /// Collapse equivalent states from each partition class into a single state. fn collapse_states(&self, partition: Vec>) -> RegexpDFA { let n = self.alphabet_index.len(); let m = self.rules.len()/n; let mut rules = vec![]; // states mapping due to the equivalents collapse let mut eq_mapping: Vec = ((0..m)).collect(); for eq_set in partition.into_iter() { let mut eq_vec = Vec::from_iter(eq_set.into_iter()); eq_vec.sort(); let label = eq_vec[0]; for st in eq_vec.into_iter() { eq_mapping[st] = label; } } // states mapping to keep the rules list compact after the equivalents collapse let mut discard_mapping: Vec = ((0..m)).collect(); let mut discard_count = 0; for si in 0..m { if eq_mapping[si] != si { discard_count += 1; continue; } discard_mapping[si] = si-discard_count; rules.extend(self.rules[si*n..(si+1)*n].iter().map(|&st| eq_mapping[st])); } rules = rules.into_iter().map(|st| discard_mapping[st]).collect(); let end_states = self.end_states.iter().map(|st| discard_mapping[eq_mapping[*st]]).collect(); return RegexpDFA{rules, end_states, alphabet_index: self.alphabet_index.clone()}; } /// Expand the automaton to accommodate union of `self.alphabet_index` and the provided `alphabet_index`. fn expand_alphabet(&self, alphabet_index: &HashMap) -> RegexpDFA { if *alphabet_index == self.alphabet_index { return self.clone(); } let n1 = self.alphabet_index.len(); let m = self.rules.len() / n1; let combined_alphabet: HashSet = HashSet::from_iter(self.alphabet_index.keys().chain(alphabet_index.keys()).copied()); let mut combined_vec = Vec::from_iter(combined_alphabet.into_iter()); combined_vec.sort(); // a new alphabet_index let combined_index = HashMap::from_iter(combined_vec.iter().enumerate().map(|(i, c)| (*c, i))); // a new letter key -> the old letter key let conversion_index: HashMap = HashMap::from_iter(self.alphabet_index.iter().map(|(k, v)| (combined_index[k], *v))); let n2 = combined_vec.len(); // rewrite the rules into a new table, filling blanks with a new fail state let rules: Vec = (0..m*n2).map( |i| { let (j, k) = (i/n2, i%n2); return if conversion_index.contains_key(&k) { self.rules[j*n1 + conversion_index[&k]] } else {m}; } ).chain(std::iter::repeat(m).take(n2)).collect(); return RegexpDFA{rules, end_states: self.end_states.clone(), alphabet_index: combined_index}.reduce().normalize(); } /// Create a new automaton, with a carthesian product of the arguments' states and corresponding transition rules. fn build_product_automaton(&self, other: &RegexpDFA) -> RegexpDFA { let n = self.alphabet_index.len(); let m = other.rules.len() / n; let k = self.rules.len() / n; let mut rules = vec![]; let mut end_states = HashSet::new(); // expand each self state into m new states, one for each of the `other` states for s1 in 0..k { let row1 = &self.rules[s1*n..(s1+1)*n]; for s2 in 0..m { let row2 = &other.rules[s2*n..(s2+1)*n]; rules.extend(row1.iter().zip(row2.iter()).map(|(x, y)| x*m + y)); if (self.end_states.contains(&s1)) ^ (other.end_states.contains(&s2)) { end_states.insert(s1*m + s2); } } } return RegexpDFA{rules, end_states, alphabet_index: self.alphabet_index.clone()}.reduce().normalize(); } }