use crate::{CharRange, error::EngineError, fast_automaton::spanning_set::SpanningSet};
use super::Condition;
pub struct ConditionConverter<'a, 'b> {
from_spanning_set: &'a SpanningSet,
to_spanning_set: &'b SpanningSet,
equivalence_map: Vec<Vec<usize>>,
}
impl<'a, 'b> ConditionConverter<'a, 'b> {
pub fn new(
from_spanning_set: &'a SpanningSet,
to_spanning_set: &'b SpanningSet,
) -> Result<Self, EngineError> {
let mut to_bases: Vec<Option<&CharRange>> = to_spanning_set
.spanning_ranges_with_rest()
.map(Some)
.collect();
let mut equivalence_map: Vec<Vec<usize>> =
Vec::with_capacity(from_spanning_set.spanning_ranges_with_rest_len());
for from_base in from_spanning_set.spanning_ranges_with_rest() {
let mut index = Vec::with_capacity(1);
for (i, slot) in to_bases.iter_mut().enumerate() {
if let Some(to_base) = slot
&& (from_base == *to_base || from_base.has_intersection(to_base))
{
index.push(i);
*slot = None;
}
}
equivalence_map.push(index);
}
Ok(ConditionConverter {
from_spanning_set,
to_spanning_set,
equivalence_map,
})
}
pub fn convert(&self, condition: &Condition) -> Result<Condition, EngineError> {
if condition.0.len() != self.from_spanning_set.spanning_ranges_with_rest_len() {
return Err(EngineError::IncompatibleSpanningSet);
}
let mut new_condition = Condition::empty(self.to_spanning_set);
for (from_index, to_indexes) in self.equivalence_map.iter().enumerate() {
if condition.0.get(from_index) && !to_indexes.is_empty() {
to_indexes.iter().for_each(|&to_index| {
new_condition.0.set(to_index, true);
});
}
}
debug_assert_eq!(
condition
.to_range(self.from_spanning_set)
.expect("the length was checked above"),
new_condition
.to_range(self.to_spanning_set)
.expect("the condition was built over the target spanning set"),
"the projection changed the condition's character range"
);
Ok(new_condition)
}
pub fn from_spanning_set(&self) -> &'a SpanningSet {
self.from_spanning_set
}
pub fn to_spanning_set(&self) -> &'b SpanningSet {
self.to_spanning_set
}
}
#[cfg(test)]
mod tests {
use crate::CharRange;
use regex_charclass::{char::Char, irange::range::AnyRange};
use super::*;
fn from_spanning_set() -> SpanningSet {
let ranges = vec![
CharRange::new_from_range(Char::new('\0')..=Char::new('\u{2}')),
CharRange::new_from_range(Char::new('\u{4}')..=Char::new('\u{6}')),
CharRange::new_from_range(Char::new('\u{9}')..=Char::new('\u{9}')),
];
SpanningSet::compute_spanning_set(&ranges)
}
fn to_spanning_set() -> SpanningSet {
let ranges = vec![
CharRange::new_from_range(Char::new('\0')..=Char::new('\u{1}')),
CharRange::new_from_range(Char::new('\u{2}')..=Char::new('\u{2}')),
CharRange::new_from_range(Char::new('\u{4}')..=Char::new('\u{6}')),
CharRange::new_from_range(Char::new('\u{9}')..=Char::new('\u{9}')),
CharRange::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 = from_spanning_set();
let to_spanning_set = 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 = CharRange::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 = CharRange::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 = CharRange::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(())
}
}