diff --git a/Cargo.toml b/Cargo.toml index 1bbe48e..79e2280 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "regexsolver" -version = "0.2.2" +version = "0.3.0" edition = "2021" authors = ["Alexandre van Beurden"] repository = "https://github.com/RegexSolver/regexsolver" diff --git a/src/execution_profile.rs b/src/execution_profile.rs index 2da9161..2ae8e2b 100644 --- a/src/execution_profile.rs +++ b/src/execution_profile.rs @@ -218,7 +218,10 @@ mod tests { }; ThreadLocalParams::init_profile(&execution_profile); - assert_eq!(EngineError::OperationTimeOutError, term.generate_strings(100).unwrap_err()); + assert_eq!( + EngineError::OperationTimeOutError, + term.generate_strings(100).unwrap_err() + ); let run_duration = SystemTime::now() .duration_since(start_time) @@ -244,7 +247,39 @@ mod tests { }; ThreadLocalParams::init_profile(&execution_profile); - assert_eq!(EngineError::OperationTimeOutError, term1.difference(&term2).unwrap_err()); + assert_eq!( + EngineError::OperationTimeOutError, + term1.difference(&term2).unwrap_err() + ); + + let run_duration = SystemTime::now() + .duration_since(start_time) + .expect("Time went backwards") + .as_millis(); + + println!("{run_duration}"); + assert!(run_duration <= execution_profile.execution_timeout + 50); + Ok(()) + } + + #[test] + fn test_execution_timeout_intersection() -> Result<(), String> { + let term1 = Term::from_regex(".*abc.*def.*qdqd.*qsdsqdsqdz.*abc.*def.*qdqd.*qsdsqdsqdz.*abc.*def.*qdqd.*qsdsqdsqdz.*abc.*def.*qdqd.*qsdsqdsqdz.*abc.*def.*qdqd.*qsdsqdsqdz.*abc.*def.*qdqd.*qsdsqdsqdz.*abc.*def.*qdqd.*qsdsqdsqdz").unwrap(); + let term2 = Term::from_regex(".*abc.*def.*qdsqd.*sqdsqd.*qsdsqdsqdz.*abc.*def.*qdqd.*qsdsqdsqdz.*abc.*def.*qdqd.*qsdsqdsqdz.*abc.*def.*qdsqd.*sqdsqd.*qsdsqdsqdz.*abc.*def.*qdsqd.*sqdsqd.*qsdsqdsqdz.*abc.*def.*qdqd.*qsdsqdsqdz.*abc.*def.*qdqd.*qsdsqdsqdz.*abc.*def.*qdqd.*qsdsqdsqdz.*abc.*def.*qdqd.*qsdsqdsqdz.*abc.*def.*qdqd.*qsdsqdsqdz.*abc.*def.*qdqd.*qsdsqdsqdz.*abc.*def.*qdqd.*qsdsqdsqdz").unwrap(); + + let start_time = SystemTime::now(); + let execution_profile = ExecutionProfile { + max_number_of_states: 8192, + start_execution_time: Some(start_time), + execution_timeout: 100, + max_number_of_terms: 50, + }; + ThreadLocalParams::init_profile(&execution_profile); + + assert_eq!( + EngineError::OperationTimeOutError, + term1.intersection(&[term2]).unwrap_err() + ); let run_duration = SystemTime::now() .duration_since(start_time) diff --git a/src/fast_automaton/builder.rs b/src/fast_automaton/builder.rs index 771ac0f..b6cf50b 100644 --- a/src/fast_automaton/builder.rs +++ b/src/fast_automaton/builder.rs @@ -1,3 +1,5 @@ +use condition::converter::ConditionConverter; + use crate::error::EngineError; use super::*; @@ -29,11 +31,7 @@ impl FastAutomaton { let mut automaton: FastAutomaton = Self::new_empty(); automaton.spanning_set = SpanningSet::new_total(); automaton.accept(automaton.start_state); - automaton.add_transition_to( - 0, - 0, - &Condition::total(&automaton.spanning_set), - ); + automaton.add_transition_to(0, 0, &Condition::total(&automaton.spanning_set)); automaton } @@ -69,14 +67,12 @@ impl FastAutomaton { if new_spanning_set == &self.spanning_set { return Ok(()); } + let condition_converter = ConditionConverter::new(&self.spanning_set, new_spanning_set)?; for from_state in &self.transitions_vec() { for to_state in self.transitions_from_state(from_state) { match self.transitions[*from_state].entry(to_state) { Entry::Occupied(mut o) => { - o.insert( - o.get() - .project_to(&self.spanning_set, new_spanning_set)?, - ); + o.insert(condition_converter.convert(o.get())?); } Entry::Vacant(_) => {} }; diff --git a/src/fast_automaton/condition/converter.rs b/src/fast_automaton/condition/converter.rs new file mode 100644 index 0000000..89bb123 --- /dev/null +++ b/src/fast_automaton/condition/converter.rs @@ -0,0 +1,168 @@ +use ahash::HashMapExt; +use nohash_hasher::IntMap; + +use crate::{error::EngineError, fast_automaton::spanning_set::SpanningSet}; + +use super::Condition; + +/// Converter to project [`Condition`] on a [`SpanningSet`]. +pub struct ConditionConverter<'a, 'b> { + from_spanning_set: &'a SpanningSet, + to_spanning_set: &'b SpanningSet, + equivalence_map: Vec>, +} + +impl<'a, 'b> ConditionConverter<'a, 'b> { + /// Build a converter to project [`Condition`] from `from_spanning_set` to `to_spanning_set`. + /// + /// Currently this method does not check that the provided [`SpanningSet`] are actually convertible. + pub fn new( + from_spanning_set: &'a SpanningSet, + to_spanning_set: &'b SpanningSet, + ) -> Result { + let mut to_base_map = + IntMap::with_capacity(to_spanning_set.spanning_ranges_with_rest_len()); + for (i, base) in to_spanning_set + .get_spanning_ranges_with_rest() + .into_iter() + .enumerate() + { + to_base_map.insert(i, base); + } + + let mut equivalence_map: Vec> = + Vec::with_capacity(from_spanning_set.get_number_of_spanning_ranges() + 1); + for from_base in from_spanning_set.get_spanning_ranges_with_rest().iter() { + let mut index = Vec::with_capacity(1); + for (i, to_base) in &to_base_map { + if from_base == to_base || from_base.has_intersection(to_base) { + index.push(*i); + } + } + index.iter().for_each(|i| { + to_base_map.remove(i); + }); + equivalence_map.push(index); + } + + Ok(ConditionConverter { + from_spanning_set, + to_spanning_set, + equivalence_map, + }) + } + + /// Project the given [`Condition`] from `from_spanning_set` to `to_spanning_set`. + /// + /// If `from_spanning_set` is not convertible to `to_spanning_set` or if the given [`Condition`] is not based on `from_spanning_set`, + /// the resulting [`Condition`] will not have any relevance. + pub fn convert(&self, condition: &Condition) -> Result { + let mut new_condition = Condition::empty(self.to_spanning_set); + for (from_index, to_indexes) in self.equivalence_map.iter().enumerate() { + if let Some(has) = condition.0.get(from_index) { + if has && !to_indexes.is_empty() { + to_indexes.iter().for_each(|&to_index| { + new_condition.0.set(to_index, true); + }); + } + } else { + return Err(EngineError::ConditionIndexOutOfBound); + } + } + + Ok(new_condition) + } + + /// Returns `from_spanning_set`. + pub fn get_from_spanning_set(&self) -> &'a SpanningSet { + self.from_spanning_set + } + + /// Returns `to_spanning_set`. + pub fn get_to_spanning_set(&self) -> &'b SpanningSet { + self.to_spanning_set + } +} + +#[cfg(test)] +mod tests { + use regex_charclass::{char::Char, irange::range::AnyRange}; + + use crate::Range; + + use super::*; + + fn get_from_spanning_set() -> SpanningSet { + let ranges = vec![ + Range::new_from_range(Char::new('\0')..=Char::new('\u{2}')), + Range::new_from_range(Char::new('\u{4}')..=Char::new('\u{6}')), + Range::new_from_range(Char::new('\u{9}')..=Char::new('\u{9}')), + ]; + + SpanningSet::compute_spanning_set(&ranges) + } + + fn get_to_spanning_set() -> SpanningSet { + let ranges = vec![ + Range::new_from_range(Char::new('\0')..=Char::new('\u{1}')), + Range::new_from_range(Char::new('\u{2}')..=Char::new('\u{2}')), + Range::new_from_range(Char::new('\u{4}')..=Char::new('\u{6}')), + Range::new_from_range(Char::new('\u{9}')..=Char::new('\u{9}')), + Range::new_from_range(Char::new('\u{20}')..=Char::new('\u{22}')), + ]; + + SpanningSet::compute_spanning_set(&ranges) + } + + #[test] + fn test_convert() -> Result<(), String> { + let from_spanning_set = get_from_spanning_set(); + let to_spanning_set = get_to_spanning_set(); + + let converter = ConditionConverter::new(&from_spanning_set, &to_spanning_set).unwrap(); + + let empty = Condition::empty(&from_spanning_set); + assert!(converter.convert(&empty).unwrap().is_empty()); + + let total = Condition::total(&from_spanning_set); + assert!(converter.convert(&total).unwrap().is_total()); + + let range = Range::new_from_range(Char::new('\0')..=Char::new('\u{2}')); + let condition = Condition::from_range(&range, &from_spanning_set).unwrap(); + assert_eq!( + range, + converter + .convert(&condition) + .unwrap() + .to_range(&to_spanning_set) + .unwrap() + ); + + let range = Range::new_from_range(Char::new('\u{4}')..=Char::new('\u{6}')); + let condition = Condition::from_range(&range, &from_spanning_set).unwrap(); + assert_eq!( + range, + converter + .convert(&condition) + .unwrap() + .to_range(&to_spanning_set) + .unwrap() + ); + + let range = Range::new_from_ranges(&[ + AnyRange::from(Char::new('\u{4}')..=Char::new('\u{6}')), + AnyRange::from(Char::new('\u{9}')..=Char::new('\u{9}')), + ]); + let condition = Condition::from_range(&range, &from_spanning_set).unwrap(); + assert_eq!( + range, + converter + .convert(&condition) + .unwrap() + .to_range(&to_spanning_set) + .unwrap() + ); + + Ok(()) + } +} diff --git a/src/fast_automaton/condition/fast_bit_vec/mod.rs b/src/fast_automaton/condition/fast_bit_vec/mod.rs index dd23995..bbf4376 100644 --- a/src/fast_automaton/condition/fast_bit_vec/mod.rs +++ b/src/fast_automaton/condition/fast_bit_vec/mod.rs @@ -123,7 +123,7 @@ impl FastBitVec { (!0) >> ((64 - bits % 64) % 64) } - pub fn get_hot_bits(&self) -> Vec { + pub fn get_bits(&self) -> Vec { let mut hot_bits = Vec::with_capacity(self.n); for i in 0..self.n { hot_bits.push(self.get(i).unwrap()); diff --git a/src/fast_automaton/condition/mod.rs b/src/fast_automaton/condition/mod.rs index 000f53e..da9c2b8 100644 --- a/src/fast_automaton/condition/mod.rs +++ b/src/fast_automaton/condition/mod.rs @@ -7,6 +7,7 @@ use regex_charclass::{char::Char, CharacterClass}; use crate::error::EngineError; use super::spanning_set::SpanningSet; +pub mod converter; mod fast_bit_vec; /// Contains the condition of a transition in a [`crate::FastAutomaton`] @@ -51,7 +52,11 @@ impl Condition { let mut cond = Self::empty(spanning_set); - for (i, base) in spanning_set.get_spanning_ranges_with_rest().iter().enumerate() { + for (i, base) in spanning_set + .get_spanning_ranges_with_rest() + .iter() + .enumerate() + { if range.contains_all(base) { cond.0.set(i, true); } @@ -67,7 +72,11 @@ impl Condition { pub fn to_range(&self, spanning_set: &SpanningSet) -> Result { let mut range = Range::empty(); - for (i, base) in spanning_set.get_spanning_ranges_with_rest().iter().enumerate() { + for (i, base) in spanning_set + .get_spanning_ranges_with_rest() + .iter() + .enumerate() + { if let Some(has) = self.0.get(i) { if has { range = range.union(base); @@ -80,19 +89,6 @@ impl Condition { Ok(range) } - pub fn project_to( - &self, - current_spanning_set: &SpanningSet, - new_spanning_set: &SpanningSet, - ) -> Result { - if current_spanning_set == new_spanning_set { - Ok(self.clone()) - } else { - let range = self.to_range(current_spanning_set)?; - Self::from_range(&range, new_spanning_set) - } - } - #[inline] pub fn union(&self, cond: &Condition) -> Self { let mut new_cond = self.clone(); @@ -155,20 +151,21 @@ impl Condition { Ok(self.to_range(spanning_set)?.get_cardinality()) } - pub fn get_hot_bits(&self) -> Vec { - self.0.get_hot_bits() + pub fn get_bits(&self) -> Vec { + self.0.get_bits() } } #[cfg(test)] mod tests { + use converter::ConditionConverter; use regex_charclass::irange::range::AnyRange; use super::*; fn get_spanning_set() -> SpanningSet { let ranges = vec![ - Range::new_from_range(Char::new('\0')..=Char::new('\u{2}')), + Range::new_from_range(Char::new('\u{0}')..=Char::new('\u{2}')), Range::new_from_range(Char::new('\u{4}')..=Char::new('\u{6}')), Range::new_from_range(Char::new('\u{9}')..=Char::new('\u{9}')), ]; @@ -196,17 +193,11 @@ mod tests { let empty = Condition::empty(&spanning_set); //println!("{empty}"); assert!(empty.is_empty()); - assert_eq!( - vec![false, false, false, false], - empty.get_hot_bits() - ); + assert_eq!(vec![false, false, false, false], empty.get_bits()); let total = Condition::total(&spanning_set); //println!("{total}"); assert!(total.is_total()); - assert_eq!( - vec![true, true, true, true], - total.get_hot_bits() - ); + assert_eq!(vec![true, true, true, true], total.get_bits()); assert_eq!(Range::empty(), empty.to_range(&spanning_set).unwrap()); assert_eq!(Range::total(), total.to_range(&spanning_set).unwrap()); @@ -234,19 +225,13 @@ mod tests { empty, Condition::from_range(&Range::empty(), &spanning_set).unwrap() ); - assert_eq!( - vec![false], - empty.get_hot_bits() - ); + assert_eq!(vec![false], empty.get_bits()); assert_eq!( total, Condition::from_range(&Range::total(), &spanning_set).unwrap() ); - assert_eq!( - vec![true], - total.get_hot_bits() - ); + assert_eq!(vec![true], total.get_bits()); assert_eq!(empty, total.complement()); assert_eq!(total, empty.complement()); @@ -282,19 +267,27 @@ mod tests { let ranges = vec![ Range::new_from_range(Char::new('\u{0}')..=Char::new('\u{1}')), - Range::new_from_range(Char::new('\u{2}')..=Char::new('\u{3}')), - Range::new_from_range(Char::new('\u{4}')..=Char::new('\u{5}')), - Range::new_from_range(Char::new('\u{6}')..=Char::new('\u{7}')), + Range::new_from_range(Char::new('\u{2}')..=Char::new('\u{2}')), + Range::new_from_range(Char::new('\u{4}')..=Char::new('\u{6}')), + Range::new_from_range(Char::new('\u{5}')..=Char::new('\u{6}')), Range::new_from_range(Char::new('\u{9}')..=Char::new('\u{9}')), ]; let new_spanning_set = SpanningSet::compute_spanning_set(&ranges); + let condition_converter = + ConditionConverter::new(¤t_spanning_set, &new_spanning_set).unwrap(); for range in get_test_cases_range() { - assert_project_to(&range, ¤t_spanning_set, &new_spanning_set); + assert_project_to( + &range, + ¤t_spanning_set, + &new_spanning_set, + &condition_converter, + ); assert_project_to( &range.complement(), ¤t_spanning_set, &new_spanning_set, + &condition_converter, ); } @@ -303,15 +296,25 @@ mod tests { fn assert_project_to( range: &Range, - currently_used_characters: &SpanningSet, - newly_used_characters: &SpanningSet, + currently_used_spanning_set: &SpanningSet, + newly_used_spanning_set: &SpanningSet, + condition_converter: &ConditionConverter, ) { - let condition = Condition::from_range(range, currently_used_characters).unwrap(); - let projected_condition = condition - .project_to(currently_used_characters, newly_used_characters) - .unwrap(); + let condition = Condition::from_range(range, currently_used_spanning_set).unwrap(); + let projected_condition = condition_converter.convert(&condition).unwrap(); + + assert_eq!( + range, + &condition.to_range(currently_used_spanning_set).unwrap() + ); + assert_eq!( + range, + &projected_condition + .to_range(newly_used_spanning_set) + .unwrap() + ); - let expected_condition = Condition::from_range(range, newly_used_characters).unwrap(); + let expected_condition = Condition::from_range(range, newly_used_spanning_set).unwrap(); assert_eq!(expected_condition, projected_condition); } diff --git a/src/fast_automaton/generate.rs b/src/fast_automaton/generate.rs index b78c11a..638ba11 100644 --- a/src/fast_automaton/generate.rs +++ b/src/fast_automaton/generate.rs @@ -6,82 +6,6 @@ use ahash::AHashSet; use super::*; impl FastAutomaton { - /*pub fn generate_strings(&self, number: usize) -> Result, EngineError> { - if self.is_empty() { - return Ok(AHashSet::new()); - } - - let bases = self - .get_bases()? - .into_iter() - .map(|c| { - let range = c.to_range(self.get_spanning_set()).unwrap(); - (c, range) - }) - .collect::>(); - - let capacity = cmp::min(number, 1000); - let mut strings = AHashSet::with_capacity(capacity); - let mut pq = BinaryHeap::with_capacity(capacity); - let mut visited = AHashSet::with_capacity(capacity); - - pq.push(Reverse((String::new(), vec![self.start_state]))); - if self.is_accepted(&self.start_state) { - strings.insert("".to_string()); - if strings.len() >= number { - return Ok(strings); - } - } - - while let Some(Reverse((string, states))) = pq.pop() { - if !visited.insert((states.clone(), string.clone())) { - continue; - } - - for (base, range) in &bases { - for from_state in &states { - let mut accepted = false; - let mut next_states = Vec::with_capacity(self.get_number_of_states()); - for (to_state, condition) in - self.transitions_from_state_enumerate_iter(&from_state) - { - if condition.has_intersection(base) { - next_states.push(*to_state); - if self.is_accepted(to_state) { - accepted = true; - } - } - } - if !next_states.is_empty() { - next_states.shrink_to_fit(); - next_states.sort_unstable(); - - let mut c = 0; - for char in range.iter() { - let mut new_string = String::with_capacity(string.capacity() + 1); - new_string.push_str(&string); - new_string.push(char.to_char()); - //println!("char {char}, new_string {new_string}"); - if accepted { - strings.insert(new_string.clone()); - if strings.len() >= number { - return Ok(strings); - } - } - pq.push(Reverse((new_string, next_states.clone()))); - c += 1; - if c >= number { - break; - } - } - } - } - } - } - - Ok(strings) - }*/ - pub fn generate_strings(&self, number: usize) -> Result, EngineError> { if self.is_empty() { return Ok(AHashSet::new()); @@ -100,7 +24,6 @@ impl FastAutomaton { worklist.push_back((vec![], self.start_state)); while let Some((ranges, state)) = worklist.pop_front() { - execution_profile.assert_not_timed_out()?; if self.accept_states.contains(&state) { if ranges.is_empty() { strings.insert(String::new()); @@ -108,6 +31,7 @@ impl FastAutomaton { let mut end = false; let mut ranges_iter: Vec<_> = ranges.iter().map(|range| range.iter()).collect(); while strings.len() < number { + execution_profile.assert_not_timed_out()?; let mut string = vec![]; for i in 0..ranges.len() { if let Some(character) = ranges_iter[i].next() { @@ -134,6 +58,7 @@ impl FastAutomaton { } } for (to_state, cond) in self.transitions_from_state_enumerate_iter(&state) { + execution_profile.assert_not_timed_out()?; let range = match ranges_cache.entry(cond) { Entry::Occupied(o) => o.get().clone(), Entry::Vacant(v) => { diff --git a/src/fast_automaton/operation/alternation.rs b/src/fast_automaton/operation/alternation.rs index 8c33246..06c386e 100644 --- a/src/fast_automaton/operation/alternation.rs +++ b/src/fast_automaton/operation/alternation.rs @@ -1,5 +1,7 @@ use std::hash::BuildHasherDefault; +use condition::converter::ConditionConverter; + use crate::error::EngineError; use super::*; @@ -29,7 +31,7 @@ impl FastAutomaton { &mut self, other: &FastAutomaton, new_states: &mut IntMap, - spanning_set: &SpanningSet, + condition_converter: &ConditionConverter, ) -> Result, EngineError> { let mut imcomplete_states = IntSet::with_capacity(other.out_degree(other.start_state) + 1); let self_start_state_in_degree = self.in_degree(self.start_state); @@ -64,7 +66,7 @@ impl FastAutomaton { for (other_to_state, cond) in other.transitions_from_state_enumerate_vec(&other.start_state) { - let cond = cond.project_to(&other.spanning_set, spanning_set)?; + let cond = condition_converter.convert(&cond)?; let to_state = match new_states.entry(other_to_state) { Entry::Occupied(o) => *o.get(), Entry::Vacant(v) => { @@ -145,6 +147,7 @@ impl FastAutomaton { let new_spanning_set = &self.spanning_set.merge(&other.spanning_set); self.apply_new_spanning_set(new_spanning_set)?; + let condition_converter = ConditionConverter::new(&other.spanning_set, new_spanning_set)?; let mut new_states: IntMap = IntMap::with_capacity_and_hasher( other.get_number_of_states(), @@ -152,7 +155,7 @@ impl FastAutomaton { ); let imcomplete_states = - self.prepare_start_states(other, &mut new_states, new_spanning_set)?; + self.prepare_start_states(other, &mut new_states, &condition_converter)?; self.prepare_accept_states(other, &mut new_states, &imcomplete_states); for from_state in other.transitions_iter() { @@ -165,7 +168,7 @@ impl FastAutomaton { } }; for (to_state, condition) in other.transitions_from_state_enumerate_iter(&from_state) { - let new_condition = condition.project_to(&other.spanning_set, new_spanning_set)?; + let new_condition = condition_converter.convert(condition)?; let new_to_state = match new_states.entry(*to_state) { Entry::Occupied(o) => *o.get(), Entry::Vacant(v) => { diff --git a/src/fast_automaton/operation/concatenate.rs b/src/fast_automaton/operation/concatenate.rs index 03556ba..3741e01 100644 --- a/src/fast_automaton/operation/concatenate.rs +++ b/src/fast_automaton/operation/concatenate.rs @@ -1,5 +1,7 @@ use std::hash::BuildHasherDefault; +use condition::converter::ConditionConverter; + use crate::error::EngineError; use super::*; @@ -126,6 +128,7 @@ impl FastAutomaton { let new_spanning_set = &self.spanning_set.merge(&other.spanning_set); self.apply_new_spanning_set(new_spanning_set)?; + let condition_converter = ConditionConverter::new(&other.spanning_set, new_spanning_set)?; let mut new_states: IntMap = IntMap::with_capacity_and_hasher( other.get_number_of_states(), @@ -195,8 +198,7 @@ impl FastAutomaton { } } }; - let projected_condition = - condition.project_to(&other.spanning_set, new_spanning_set)?; + let projected_condition = condition_converter.convert(condition)?; for new_from_state in new_from_states.iter() { for new_to_state in new_to_states.iter() { self.add_transition_to( diff --git a/src/fast_automaton/operation/intersection.rs b/src/fast_automaton/operation/intersection.rs index c0eaf7a..96007e6 100644 --- a/src/fast_automaton/operation/intersection.rs +++ b/src/fast_automaton/operation/intersection.rs @@ -1,3 +1,5 @@ +use condition::converter::ConditionConverter; + use crate::{error::EngineError, execution_profile::ThreadLocalParams}; use super::*; @@ -12,9 +14,14 @@ impl FastAutomaton { return Ok(self.clone()); } let execution_profile = ThreadLocalParams::get_execution_profile(); - + let new_spanning_set = self.spanning_set.merge(&other.spanning_set); + let condition_converter_self_to_new = + ConditionConverter::new(&self.spanning_set, &new_spanning_set)?; + let condition_converter_other_to_new = + ConditionConverter::new(&other.spanning_set, &new_spanning_set)?; + let mut new_automaton = FastAutomaton::new_empty(); let mut worklist = VecDeque::with_capacity(self.get_number_of_states() + other.get_number_of_states()); @@ -36,8 +43,10 @@ impl FastAutomaton { new_automaton.accept(p.0); } - let transitions_1 = self.get_projected_transitions(p.1, &new_spanning_set)?; - let transitions_2 = other.get_projected_transitions(p.2, &new_spanning_set)?; + let transitions_1 = + self.get_projected_transitions(p.1, &condition_converter_self_to_new)?; + let transitions_2 = + other.get_projected_transitions(p.2, &condition_converter_other_to_new)?; for (n1, condition_1) in transitions_1 { for (n2, condition_2) in &transitions_2 { @@ -74,6 +83,11 @@ impl FastAutomaton { let new_spanning_set = self.spanning_set.merge(&other.spanning_set); + let condition_converter_self_to_new = + ConditionConverter::new(&self.spanning_set, &new_spanning_set)?; + let condition_converter_other_to_new = + ConditionConverter::new(&other.spanning_set, &new_spanning_set)?; + let mut new_automaton = FastAutomaton::new_empty(); let mut worklist = VecDeque::with_capacity(self.get_number_of_states() + other.get_number_of_states()); @@ -95,8 +109,10 @@ impl FastAutomaton { return Ok(true); } - let transitions_1 = self.get_projected_transitions(p.1, &new_spanning_set)?; - let transitions_2 = other.get_projected_transitions(p.2, &new_spanning_set)?; + let transitions_1 = + self.get_projected_transitions(p.1, &condition_converter_self_to_new)?; + let transitions_2 = + other.get_projected_transitions(p.2, &condition_converter_other_to_new)?; for (n1, condition_1) in transitions_1 { for (n2, condition_2) in &transitions_2 { @@ -124,16 +140,14 @@ impl FastAutomaton { fn get_projected_transitions( &self, state: State, - new_spanning_set: &SpanningSet, + condition_converter: &ConditionConverter, ) -> Result, EngineError> { let transitions_1: Result, EngineError> = self .transitions_from_state_enumerate_iter(&state) - .map( - |(&s, c)| match c.project_to(&self.spanning_set, new_spanning_set) { - Ok(condition) => Ok((s, condition)), - Err(err) => Err(err), - }, - ) + .map(|(&s, c)| match condition_converter.convert(c) { + Ok(condition) => Ok((s, condition)), + Err(err) => Err(err), + }) .collect(); transitions_1 diff --git a/src/fast_automaton/spanning_set/mod.rs b/src/fast_automaton/spanning_set/mod.rs index ecfe92f..f3e0dff 100644 --- a/src/fast_automaton/spanning_set/mod.rs +++ b/src/fast_automaton/spanning_set/mod.rs @@ -1,10 +1,10 @@ use std::slice::Iter; use ahash::AHashSet; -use serde::{Deserialize, Serialize}; use regex_charclass::{char::Char, irange::RangeSet}; +use serde::{Deserialize, Serialize}; -/// Contains a set of [`RangeSet`] that span all the transition of a [`crate::FastAutomaton`]. +/// Contains a set of [`RangeSet`] that span all the transition of a [`crate::FastAutomaton`]. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct SpanningSet(Vec>, RangeSet); @@ -17,6 +17,14 @@ impl SpanningSet { SpanningSet(vec![RangeSet::total()], RangeSet::empty()) } + pub fn is_empty(&self) -> bool { + self.0.is_empty() && self.1.is_total() + } + + pub fn is_total(&self) -> bool { + self.0.len() == 1 && self.0[0].is_total() && self.1.is_empty() + } + pub(crate) fn spanning_ranges_with_rest_len(&self) -> usize { if self.1.is_empty() { self.0.len()