use crate::error::EngineError;
use ahash::{AHashMap, HashSetExt};
use condition::Condition;
use regex_charclass::CharacterClass;
use spanning_set::SpanningSet;
use std::collections::VecDeque;
use std::collections::hash_map::Entry;
use std::fmt::Display;
use super::*;
pub(crate) type Transitions = IntMap<State, Condition>;
pub type State = usize;
mod analyze;
mod builder;
pub mod condition;
mod convert;
mod generate;
mod operation;
pub mod spanning_set;
pub use generate::{CharacterOrder, GenerationOptions, PathOrder};
const SURROGATES: std::ops::Range<u32> = 0xD800..0xE000;
#[inline]
fn scalar(ch: regex_charclass::char::Char) -> u32 {
let code = ch.to_u32();
if code >= SURROGATES.end {
code - (SURROGATES.end - SURROGATES.start)
} else {
code
}
}
#[inline]
fn from_scalar(index: u32) -> Option<regex_charclass::char::Char> {
regex_charclass::char::Char::from_u32(if index >= SURROGATES.start {
index + (SURROGATES.end - SURROGATES.start)
} else {
index
})
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[must_use = "non-`_mut` operations return a new automaton"]
pub struct FastAutomaton {
transitions: Vec<Transitions>,
transitions_in: IntMap<usize, IntSet<usize>>,
start_state: State,
accept_states: IntSet<State>,
removed_states: IntSet<State>,
spanning_set: SpanningSet,
deterministic: bool,
minimal: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DeterminismLost;
impl std::fmt::Display for DeterminismLost {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"adding the transition would introduce overlapping conditions"
)
}
}
impl std::error::Error for DeterminismLost {}
impl Display for FastAutomaton {
fn fmt(&self, sb: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(sb, "digraph Automaton {{")?;
writeln!(sb, "\trankdir = LR;")?;
for from_state in self.states() {
write!(sb, "\t{from_state}")?;
if self.accept_states.contains(&from_state) {
writeln!(sb, "\t[shape=doublecircle,label=\"{from_state}\"];")?;
} else {
writeln!(sb, "\t[shape=circle,label=\"{from_state}\"];")?;
}
if self.start_state == from_state {
writeln!(sb, "\tinitial [shape=plaintext,label=\"\"];")?;
writeln!(sb, "\tinitial -> {from_state}")?;
}
for (cond, to_state) in self.transitions_from(from_state) {
let label = match cond.to_range(&self.spanning_set) {
Ok(range) => range.to_regex().replace('\\', "\\\\").replace('"', "\\\""),
Err(_) => String::from("<invalid condition>"),
};
writeln!(sb, "\t{from_state} -> {to_state} [label=\"{label}\"]")?;
}
}
write!(sb, "}}")
}
}
impl FastAutomaton {
#[inline]
fn assert_state_exists(&self, state: State) {
if !self.has_state(state) {
panic!("The state {state} does not exist");
}
}
#[inline]
pub fn in_degree(&self, state: State) -> usize {
self.transitions_in.get(&state).map_or(0, IntSet::len)
}
#[inline]
pub fn out_degree(&self, state: State) -> usize {
if !self.has_state(state) {
return 0;
}
self.transitions[state].len()
}
#[inline]
pub fn states(&self) -> impl Iterator<Item = State> + '_ {
(0..self.transitions.len()).filter(|s| !self.removed_states.contains(s))
}
#[inline]
pub fn states_vec(&self) -> Vec<State> {
self.states().collect()
}
#[inline]
pub fn direct_states(&self, state: State) -> impl Iterator<Item = State> + '_ {
self.transitions.get(state).into_iter().flat_map(move |t| {
t.keys()
.copied()
.filter(|s| !self.removed_states.contains(s))
})
}
#[inline]
pub fn direct_states_vec(&self, state: State) -> Vec<State> {
self.direct_states(state).collect()
}
pub fn transitions_to_vec(&self, state: State) -> Vec<(State, Condition)> {
if !self.has_state(state) {
return vec![];
}
let Some(predecessors) = self.transitions_in.get(&state) else {
return vec![];
};
let mut in_transitions = vec![];
for from_state in predecessors {
if !self.has_state(*from_state) {
continue;
}
if let Some(condition) = self.condition(*from_state, state) {
in_transitions.push((*from_state, condition.clone()));
}
}
in_transitions
}
#[inline]
pub fn transitions_from_vec(&self, state: State) -> Vec<(Condition, State)> {
self.transitions
.get(state)
.map(|t| {
t.iter()
.map(|(s, c)| (c.clone(), *s))
.filter(|s| !self.removed_states.contains(&s.1))
.collect()
})
.unwrap_or_default()
}
#[inline]
pub fn transitions_from(&self, state: State) -> impl Iterator<Item = (&Condition, &State)> {
self.transitions.get(state).into_iter().flat_map(move |t| {
t.iter()
.map(|(s, c)| (c, s))
.filter(|s| !self.removed_states.contains(s.1))
})
}
#[inline]
pub fn has_transition(&self, from_state: State, to_state: State) -> bool {
if !self.has_state(from_state) || !self.has_state(to_state) {
return false;
}
self.transitions[from_state].contains_key(&to_state)
}
fn transitions_from_state_set(transitions: &[Transitions], from_state: State) -> Transitions {
transitions[from_state].clone()
}
fn transitions_from_state_enumerate<'a>(
transitions: &'a Transitions,
removed_states: &IntSet<State>,
) -> Vec<(&'a State, &'a Condition)> {
transitions
.iter()
.filter(|s| !removed_states.contains(s.0))
.collect()
}
#[inline]
pub fn number_of_states(&self) -> usize {
self.transitions.len() - self.removed_states.len()
}
#[inline]
pub fn condition(&self, from_state: State, to_state: State) -> Option<&Condition> {
self.transitions
.get(from_state)
.and_then(|t| t.get(&to_state))
}
#[inline]
pub fn start_state(&self) -> State {
self.start_state
}
#[inline]
pub fn accept_states(&self) -> &IntSet<State> {
&self.accept_states
}
#[inline]
pub fn spanning_set(&self) -> &SpanningSet {
&self.spanning_set
}
#[inline]
pub fn is_accepted(&self, state: State) -> bool {
self.accept_states.contains(&state)
}
#[inline]
pub fn is_deterministic(&self) -> bool {
self.deterministic
}
#[inline]
pub fn is_minimal(&self) -> bool {
self.minimal
}
#[inline]
pub fn has_state(&self, state: State) -> bool {
!(state >= self.transitions.len() || self.removed_states.contains(&state))
}
#[tracing::instrument(level = "debug", skip(self, string), fields(states = self.number_of_states(), string_len=string.len()))]
pub fn is_match(&self, string: &str) -> bool {
let mut current: IntSet<State> = IntSet::default();
current.insert(self.start_state);
let mut next: IntSet<State> = IntSet::default();
for c in string.chars() {
if current.is_empty() {
return false;
}
let c_u32 = c as u32;
next.clear();
for &state in ¤t {
for (cond, to_state) in self.transitions_from(state) {
let matches = match cond.has_character(&c_u32, &self.spanning_set) {
Ok(matches) => matches,
Err(error) => {
debug_assert!(
false,
"condition desynchronized from spanning set: {error}"
);
false
}
};
if matches {
next.insert(*to_state);
}
}
}
std::mem::swap(&mut current, &mut next);
}
current.iter().any(|s| self.accept_states.contains(s))
}
#[inline]
pub fn to_dot(&self) -> String {
format!("{self}")
}
#[inline]
pub fn print_dot(&self) {
println!("{self}");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty() -> Result<(), String> {
let automaton = FastAutomaton::new_empty();
assert!(automaton.is_empty());
assert!(!automaton.is_total());
Ok(())
}
#[test]
fn test_total() -> Result<(), String> {
let automaton = FastAutomaton::new_total();
assert!(!automaton.is_empty());
assert!(automaton.is_total());
Ok(())
}
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
#[test]
fn test_traits() -> Result<(), String> {
assert_send::<FastAutomaton>();
assert_sync::<FastAutomaton>();
Ok(())
}
#[test]
fn out_degree_safe_on_unknown_state() {
let a = FastAutomaton::new_total();
assert_eq!(a.out_degree(999), 0);
}
#[test]
fn condition_safe_on_unknown_state() {
let a = FastAutomaton::new_total();
assert!(a.condition(999, 0).is_none());
assert!(a.condition(0, 999).is_none());
}
#[test]
fn direct_states_safe_on_unknown_state() {
let a = FastAutomaton::new_total();
assert_eq!(a.direct_states(999).count(), 0);
assert_eq!(a.transitions_from(999).count(), 0);
assert!(a.transitions_from_vec(999).is_empty());
assert!(a.direct_states_vec(999).is_empty());
}
#[test]
#[should_panic(expected = "does not exist")]
fn remove_states_panics_clearly_on_out_of_range() {
let mut a = FastAutomaton::new_total();
let mut states = IntSet::default();
states.insert(999);
a.remove_states(&states);
}
#[test]
#[should_panic(expected = "does not exist")]
fn remove_states_panics_clearly_on_tombstoned_id() {
let mut a = FastAutomaton::new_empty();
let s1 = a.new_state();
let s2 = a.new_state();
let mut first = IntSet::default();
first.insert(s2);
a.remove_states(&first);
let mut again = IntSet::default();
again.insert(s2);
a.remove_states(&again);
let _ = s1;
}
#[test]
fn remove_states_removes_valid_ids() {
let mut a = FastAutomaton::new_empty();
let s1 = a.new_state();
let s2 = a.new_state();
let s3 = a.new_state();
let mut states = IntSet::default();
states.insert(s1);
states.insert(s3); a.remove_states(&states);
assert!(a.has_state(0));
assert!(a.has_state(s2));
assert!(!a.has_state(s1));
assert!(!a.has_state(s3));
}
}