automafish/
lib.rs

1//! Automafish DFA builder
2//! ======================
3//! **Makes state machines that do things**
4//!
5//! Automafish can be used to optimize state machines defined through states and overlapping
6//! transitions into more effective-to-evaluate deterministic forms.
7//!
8//! In technical terms Automafish takes [nondeterministic] [Moore machines] and creates a
9//! [deterministic state machine] for it through [powerset construction].
10//!
11//! [deterministic state machine]: https://en.wikipedia.org/wiki/Deterministic_finite_automaton
12//! [nondeterministic]: https://en.wikipedia.org/wiki/Nondeterministic_finite_automaton
13//! [Moore machines]: https://en.wikipedia.org/wiki/Moore_machine
14//! [powerset construction]: https://en.wikipedia.org/wiki/Powerset_construction
15//!
16//! # Example
17//!
18//! ```
19//! # // Update README when changing the example here.
20//! # env_logger::init();
21//! use std::iter::FromIterator;
22//! use automafish::{Builder, State, Transition, Criteria, Condition};
23//!
24//! let mut builder : Builder<Condition<char>, &mut dyn FnMut(&mut char)> = Builder::new();
25//!
26//! // Set up an automata that capitalizes first character of a word.
27//! let mut upper_case = |mut c: &mut char| { c.make_ascii_uppercase(); };
28//! let wait_not_space = builder.create_initial_state();
29//! let first_not_space = builder.add_state(State::with_action(&mut upper_case));
30//! let wait_for_space = builder.add_state(State::new());
31//!
32//! builder.add_transition(Transition::new(
33//!     wait_not_space, Condition::Is(vec![' ']), wait_not_space));
34//! builder.add_transition(Transition::new(
35//!     wait_not_space, Condition::Not(vec![' ']), first_not_space));
36//! builder.add_transition(Transition::new(
37//!     first_not_space, Condition::Not(vec![' ']), wait_for_space));
38//! builder.add_transition(Transition::new(
39//!     first_not_space, Condition::Is(vec![' ']), wait_not_space));
40//! builder.add_transition(Transition::new(
41//!     wait_for_space, Condition::Not(vec![' ']), wait_for_space));
42//! builder.add_transition(Transition::new(
43//!     wait_for_space, Condition::Is(vec![' ']), wait_not_space));
44//!
45//! // Set up an automata that counts all exclamation marks.
46//! // This automata modifies a value outside the state machine.
47//! let mut exclamations = 0;
48//! let mut exclamation_counter = |_: &mut char| { exclamations += 1; };
49//! let wait_exclamation = builder.create_initial_state();
50//! let exclamation = builder.add_state(State::with_action(&mut exclamation_counter));
51//!
52//! builder.add_transition(Transition::new(
53//!     wait_exclamation, Condition::Any, wait_exclamation));
54//! builder.add_transition(Transition::new(
55//!     wait_exclamation, Condition::Is(vec!['!']), exclamation));
56//!
57//! // Build the machine.
58//! let mut machine = builder.build();
59//!
60//! // Execute the machine on an input string.
61//! let mut current_state = machine.start();
62//! let mut input : Vec<char> = "hello world! this is rust!".chars().collect();
63//! for i in &mut input {
64//!     current_state = machine.step_and_execute_mut(current_state, i);
65//! }
66//!
67//! let output : String = String::from_iter(input);
68//!
69//! assert_eq!("Hello World! This Is Rust!", output);
70//! assert_eq!(2, exclamations);
71//! ```
72#![warn(missing_docs)]
73
74use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
75use std::hash::Hash;
76use std::sync::atomic::{AtomicUsize, Ordering};
77
78static ID_COUNTER: AtomicUsize = AtomicUsize::new(1);
79
80mod refs {
81    use super::*;
82    use std::ops::{Index, IndexMut};
83    macro_rules! ImplIndex {
84        (<$($generics:tt),*> $idx_ty:ty => $target_ty:ty) => {
85            ImplIndex! { @ ($($generics),*) $idx_ty => $target_ty }
86        };
87        ($idx_ty:ty => $target_ty:ty) => {
88            ImplIndex! { @ () $idx_ty => $target_ty }
89        };
90        ( @ ($($generics:tt),*) $idx_ty:ty => $target_ty:ty) => {
91            impl<$($generics),*> Index<$idx_ty> for Vec<$target_ty> {
92                type Output = $target_ty;
93                fn index(&self, idx: $idx_ty) -> &Self::Output {
94                    &self[idx.idx]
95                }
96            }
97            impl<$($generics),*> IndexMut<$idx_ty> for Vec<$target_ty> {
98                fn index_mut(&mut self, idx: $idx_ty) -> &mut Self::Output {
99                    &mut self[idx.idx]
100                }
101            }
102            impl $idx_ty {
103                #[allow(dead_code)]
104                pub(super) fn new_unchecked(m: usize, idx: usize) -> Self {
105                    Self { m, idx }
106                }
107
108                #[allow(dead_code)]
109                pub(super) fn next_ref<$($generics),*>(m: usize, v: &[$target_ty]) -> Self {
110                    Self { m, idx: v.len() }
111                }
112
113                #[allow(dead_code)]
114                pub(crate) fn uninit() -> Self {
115                    Self { m: usize::MAX, idx: usize::MAX }
116                }
117
118                #[allow(dead_code)]
119                pub(crate) fn assert_same_machine(&self, other: Self) {
120                    if self.m != other.m { panic!("Incompatible machine") }
121                }
122
123                #[allow(dead_code)]
124                pub(crate) fn assert_machine(&self, m: usize) {
125                    if self.m != m { panic!("Wrong machine") }
126                }
127            }
128            impl std::fmt::Debug for $idx_ty {
129                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130                    f.debug_struct(stringify!($idx_ty))
131                        .field("#", &self.idx)
132                        .finish()
133                }
134            }
135        };
136    }
137
138    #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
139    pub struct MachineState {
140        m: usize,
141        idx: usize,
142    }
143
144    #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
145    pub struct StateRef {
146        m: usize,
147        idx: usize,
148    }
149
150    #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
151    pub struct TransitionRef {
152        m: usize,
153        idx: usize,
154    }
155
156    #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
157    pub(super) struct ActionRef {
158        m: usize,
159        idx: usize,
160    }
161
162    #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
163    pub(super) struct MixedTransitionRef {
164        m: usize,
165        idx: usize,
166    }
167
168    #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
169    pub(super) struct MixedStateKey(BTreeSet<StateRef>);
170    impl MixedStateKey {
171        pub fn new<I>(i: I) -> Self
172        where
173            I: IntoIterator<Item = StateRef>,
174        {
175            use std::iter::FromIterator;
176            Self(BTreeSet::from_iter(i))
177        }
178
179        pub fn empty() -> Self {
180            Self(BTreeSet::new())
181        }
182
183        pub fn is_empty(&self) -> bool {
184            self.0.is_empty()
185        }
186
187        pub fn extend(&mut self, new: MixedStateKey) {
188            self.0.extend(new.0);
189        }
190
191        pub fn add(&mut self, new: StateRef) {
192            self.0.insert(new);
193        }
194    }
195
196    impl<'a> IntoIterator for &'a MixedStateKey {
197        type Item = &'a StateRef;
198        type IntoIter = <&'a BTreeSet<StateRef> as IntoIterator>::IntoIter;
199        fn into_iter(self) -> Self::IntoIter {
200            (&self.0).iter()
201        }
202    }
203
204    ImplIndex! { <C> MachineState => FinalState<C> }
205    ImplIndex! { <T> StateRef => State<T> }
206    ImplIndex! { <T> TransitionRef => Transition<T> }
207    ImplIndex! { <T> ActionRef => T }
208    ImplIndex! { <T> MixedTransitionRef => MixedTransition<T> }
209}
210use refs::{ActionRef, MachineState, MixedStateKey, StateRef, TransitionRef};
211
212/// A compiled state machine.
213///
214/// The `StateMachine` can be created with the [`Builder`].
215#[derive(Debug)]
216pub struct StateMachine<TCriteria, TAction> {
217    machine_id: usize,
218    initial_state: MachineState,
219    actions: Vec<TAction>,
220    states: Vec<FinalState<TCriteria>>,
221}
222
223#[derive(Debug)]
224struct FinalState<TCriteria> {
225    actions: Vec<ActionRef>,
226    transitions: Vec<(TCriteria, MachineState)>,
227}
228
229/// A state machine state.
230///
231/// `State` is used to define possible states when building a [`StateMachine`]
232/// with a [`Builder`]. Each state may contain actions that are executed when
233/// the state is reached.
234#[derive(Debug)]
235pub struct State<TAction> {
236    self_ref: StateRef,
237    actions: Vec<TAction>,
238    transitions: Vec<TransitionRef>,
239}
240
241#[derive(Debug)]
242struct MixedState<TCriteria> {
243    states: MixedStateKey,
244    transitions: Vec<MixedTransition<TCriteria>>,
245}
246
247/// A state machine transition.
248///
249/// `Transition` is used to define possible transition between different [`State`]. The transitions
250/// are triggered based on some [`Criteria`] when the state machine is being executed.
251#[derive(Debug)]
252pub struct Transition<TCriteria> {
253    source: StateRef,
254    target: StateRef,
255    criteria: TCriteria,
256}
257
258/// A state machine builder.
259///
260/// The `Builder` allows defining the state machine as a [Nondeterministic Finite Automaton] using
261/// [`Self::add_state`] and [`Self::add_transition`] methods. Once the state machine is described in such a way
262/// the final [`StateMachine`] can be built with the [`Self::build`] method.
263///
264/// [Nondeterministic Finite Automaton]: https://en.wikipedia.org/wiki/Nondeterministic_finite_automaton
265#[derive(Debug)]
266pub struct Builder<TCriteria, TAction> {
267    machine_id: usize,
268    initial_states: Vec<StateRef>,
269    states: Vec<State<TAction>>,
270    transitions: Vec<Transition<TCriteria>>,
271}
272
273#[derive(Debug, Clone, PartialEq, Eq, Hash)]
274struct MixedTransition<TCriteria> {
275    criteria: TCriteria,
276    source: MixedStateKey,
277    target: MixedStateKey,
278}
279
280/// A trait defining transition criteria.
281///
282/// The criteria defines a condition that the [`StateMachine`] input needs to
283/// match for a specific state transition to occur. The input type of the state
284/// machine is indirectly defined by the [`Self::Input`] of the state machine criteria.
285pub trait Criteria: Clone + PartialEq + Eq + Hash {
286    /// Input type for the state machien criteria.
287    type Input;
288
289    /// Checks whether a given input is a match for a specific criteria.
290    fn is_match(&self, input: &Self::Input) -> bool;
291
292    /// Checks if the `Criteria` represents an empty criteria that doesn't match any input.
293    ///
294    /// Recognizing empty `Criteria` allows the [`Builder`] to simplify the final [`StateMachine`].
295    /// Returning `false` in case the `Criteria` _is_ empty does not break the actual state
296    /// processing, but will result in extra states and state transitions being included in the
297    /// final `StateMachine`.
298    fn is_empty(&self) -> bool;
299
300    /// The evaluation order of the `Criteria` defines the order in which the state transitiosn are
301    /// evaluated. Criteria with smaller evaluation order is processed first.
302    ///
303    /// This allows some leeway in how accurate the [`Self::and`] and [`Self::not`] implementations are. See
304    /// [`Self::not`] for more details.
305    fn evaluation_order(&self) -> usize {
306        0
307    }
308
309    /// Combines two `Criteria` together.
310    ///
311    /// The [`Builder`] will use this method when calculating state transitions in the final
312    /// [`StateMachine`] between deterministic states representing several nondeterministic states
313    /// defined in the [`Builder`].
314    ///
315    /// The `Builder` will only invoke this method with `other` being a criteria defined in the
316    /// _original_ state transitions by hand. An output of `and` or `not` is never used as the
317    /// `other` parameter.
318    ///
319    /// # Example
320    ///
321    /// ```
322    /// # use std::collections::BTreeSet;
323    /// # use automafish::Criteria;
324    /// # use std::hash::Hash;
325    /// #[derive(Clone, PartialEq, Eq, Hash)]
326    /// struct OneOf(BTreeSet<u32>);
327    /// impl Criteria for OneOf {
328    /// #   type Input = u32;
329    /// #   fn is_match(&self, input: &Self::Input) -> bool { self.0.contains(input) }
330    /// #   fn is_empty(&self) -> bool { false }
331    /// #   fn not(&self, other: &Self) -> Self { unimplemented!() }
332    /// #   fn any() -> Self { unimplemented!() }
333    ///     fn and(&self, other: &Self) -> Self {
334    ///         OneOf(self.0.intersection(&other.0).copied().collect())
335    ///     }
336    ///
337    /// // ... rest of the required methods.
338    /// }
339    ///
340    /// let small = OneOf(vec![ 1, 2, 3, 4 ].into_iter().collect());
341    /// let even = OneOf(vec![ 2, 4, 6, 8 ].into_iter().collect());
342    ///
343    /// let small_and_even = small.and(&even);
344    ///
345    /// let input = 4;
346    /// assert_eq!(
347    ///     small.is_match(&input) && even.is_match(&input),
348    ///     small_and_even.is_match(&input));
349    /// ```
350    fn and(&self, other: &Self) -> Self;
351
352    /// Calculates a difference criteria that matches `self` but not `other`.
353    ///
354    /// The [`Builder`] will use this method when calculating state transitions in the final
355    /// [`StateMachine`] between deterministic states representing several nondeterministic states
356    /// defined in the [`Builder`].
357    ///
358    /// The `Builder` will only invoke this method with `other` being a criteria defined in the
359    /// _original_ state transitions by hand. An output of `and` or `not` is never used as the
360    /// `other` parameter.
361    ///
362    /// This allows for some simplification in the resulting state. The `Builder` asking for
363    /// criteria such as `a.not(b)` occurs in a situation where there is criteria `b` in some state
364    /// transition that should take priority over the one for which the `a.not(b)` criteria
365    /// applies. This means that as long as `a.not(b)` criteria has a lower evaluation order, it's
366    /// perfectly valid to return `a` as a result of that `not` operation. An example of this would
367    /// be the [`Self::any`] state, which can be returned as is, as long as it has the lowest evaluation
368    /// order.
369    fn not(&self, other: &Self) -> Self;
370
371    /// A default criteria that matches everything.
372    ///
373    /// In general this criteria shouldn't be used for `is_match` unless it appears explicitly in
374    /// the user-specified [`Transition`] definitions. Instead the any-state is used as a building
375    /// block when the [`Builder`] constructs the final transitions for the resulting deterministic
376    /// finite automaton. The final states are `any()` combined with one or more `and(..)` and zero
377    /// or more `not(..)`.
378    fn any() -> Self;
379}
380
381/// Action that may be executed when entering a state.
382///
383/// See [`ActionMut`] if you need an action that may mutate its internal state. The `Action` is
384/// implemented for `Box<Fn(T)>` by default.
385pub trait Action {
386    /// Input type for the action.
387    type Input;
388
389    /// Perform the action in the input.
390    ///
391    /// Note that the `input` takes the input type as a reference to allow multiple actions to be
392    /// executed on it if necessary. This doesn't prevent mutating the input though, since the
393    /// `Input` itself can be defined as `&mut Foo`.
394    ///
395    /// # Examples
396    ///
397    /// ```
398    /// # use automafish::Action;
399    /// struct IncrementValue;
400    /// impl Action for IncrementValue {
401    ///     type Input = u32;
402    ///     fn execute(&self, input: &mut Self::Input) {
403    ///         *input += 1;
404    ///     }
405    /// }
406    /// ```
407    fn execute(&self, input: &mut Self::Input);
408}
409
410/// Action that may be executed when entering a state and may mutate its inner state.
411///
412/// The `ActionMut` is implemented for `Box<FnMut(T)>` by default.
413pub trait ActionMut {
414    /// Input type for the action.
415    type Input;
416
417    /// Perform the action in the input.
418    ///
419    /// Note that the `input` takes the input type as a reference to allow multiple actions to be
420    /// executed on it if necessary. This doesn't prevent mutating the input though, since the
421    /// `Input` itself can be defined as `&mut Foo`.
422    ///
423    /// # Examples
424    ///
425    /// ```
426    /// # use automafish::ActionMut;
427    /// struct IncrementValue<'a> { buffer: &'a mut Vec<u32> }
428    /// impl<'a> ActionMut for IncrementValue<'a> {
429    ///     type Input = u32;
430    ///     fn execute_mut(&mut self, input: &mut Self::Input) {
431    ///         self.buffer.push(*input);
432    ///     }
433    /// }
434    /// ```
435    fn execute_mut(&mut self, input: &mut Self::Input);
436}
437
438impl<TCriteria, TAction> Builder<TCriteria, TAction>
439where
440    TCriteria: Criteria,
441{
442    /// Create a new `Builder`
443    pub fn new() -> Self {
444        Self {
445            machine_id: ID_COUNTER.fetch_add(1, Ordering::Relaxed),
446            initial_states: vec![],
447            states: vec![],
448            transitions: vec![],
449        }
450    }
451
452    /// Create a new initial state.
453    ///
454    /// Each initial state is unique in regards to their transitions. Multiple initial states may
455    /// be used to separate automata that might have different transitions to their initial states.
456    pub fn create_initial_state(&mut self) -> StateRef {
457        let state_ref = StateRef::next_ref(self.machine_id, &self.states);
458        self.states.push(State::new());
459        self.initial_states.push(state_ref);
460        state_ref
461    }
462
463    /// Add a new state to the builder.
464    pub fn add_state(&mut self, state: State<TAction>) -> StateRef {
465        let mut state = state;
466        state.self_ref = StateRef::next_ref(self.machine_id, &self.states);
467        let state_ref = state.self_ref;
468        self.states.push(state);
469        state_ref
470    }
471
472    /// Add a new transition between builder states.
473    pub fn add_transition(&mut self, transition: Transition<TCriteria>) {
474        transition.source.assert_machine(self.machine_id);
475
476        let transition_ref = TransitionRef::next_ref(self.machine_id, &self.transitions);
477        let source = &mut self.states[transition.source];
478        source.transitions.push(transition_ref);
479        self.transitions.push(transition);
480    }
481
482    /// Consume the builder, returning a final [`StateMachine`].
483    pub fn build(self) -> StateMachine<TCriteria, TAction> {
484        // Diagnostic information on the original NFA state counts.
485        let nfa_state_count = self.states.len();
486        let nfa_transition_count = self.transitions.len();
487
488        // Start a collection of all states.
489        let mut all_states: BTreeMap<MixedStateKey, MixedState<TCriteria>> = BTreeMap::new();
490
491        // Insert the initial state to the queue to get things started.
492        let initial_state_key = MixedStateKey::new(self.initial_states);
493        let mut processing_queue = vec![initial_state_key.clone()];
494        all_states.insert(
495            initial_state_key.clone(),
496            MixedState {
497                states: initial_state_key.clone(),
498                transitions: vec![],
499            },
500        );
501
502        // We'll need to keep processing items in the queue until we run out of items to process.
503        // Each step might add multiple new states to the processing_queue, but at some point we
504        // should stop encountering new states as there's only so many possible NFA state
505        // combinations we can get (although that number is 2^n, where n is the original NFA state
506        // count).
507        let mut processing_cursor = 0;
508        while processing_cursor < processing_queue.len() {
509            let current_key = processing_queue[processing_cursor].clone();
510            log::trace!("Processing state {:?}", current_key);
511
512            // Create all expanded transitions.
513            //
514            // We'll start with a dummy any-transition just to bootstrap the `expand_transitions`
515            // algorithm that uses the existing transitions as the base.  Normally this dummy
516            // transition is stopped later since it has no target states - unless of course one of
517            // the user specified transitions is a `any()` transition.
518            let mut expanded_transitions = vec![MixedTransition {
519                criteria: TCriteria::any(),
520                source: current_key.clone(),
521                target: MixedStateKey::empty(),
522            }];
523
524            // Construct the transitions by iterating all the possible transitions from the states
525            // represented by this `MixedState`.
526            for o_ref in &current_key {
527                let original = &self.states[*o_ref];
528                for t_ref in &original.transitions {
529                    let transition = &self.transitions[*t_ref];
530                    expand_transitions(&mut expanded_transitions, transition);
531                }
532            }
533
534            // Merge transitions that use the same criteria. The expansion above might have
535            // resulted in multiple equal criteria through different means (A - B and A - C, when
536            // A, B and C don't overlap). Here we ensure that such transitions result in a combined
537            // mixed state.
538            let mut expanded_transitions_map = HashMap::new();
539            for t in expanded_transitions {
540                let entry = expanded_transitions_map
541                    .entry(t.criteria.clone())
542                    .or_insert(MixedTransition {
543                        criteria: t.criteria,
544                        source: current_key.clone(),
545                        target: MixedStateKey::empty(),
546                    });
547                entry.target.extend(t.target);
548            }
549
550            // Drop the transitions that have empty criteria or targets.
551            let expanded_transitions: Vec<_> = expanded_transitions_map
552                .into_iter()
553                .map(|(_, t)| t)
554                .filter(|t| !t.criteria.is_empty() && !t.target.is_empty())
555                .collect();
556
557            // Now that we have all the possible transitions, we'll need to see if
558            // we found any new target states. These are added to the end of the processing queue
559            // so we'll process them in the future.
560            //
561            // Unless there's a bug somewhere, at some point we'll start exhausting the list of
562            // possible target states and stop adding new ones here, allowing the remaining
563            // processing backlog to run out and this while-loop to terminate.
564            for t in &expanded_transitions {
565                if all_states.contains_key(&t.target) {
566                    continue;
567                }
568
569                // Encountered a state that isn't in the queue yet.
570                all_states.insert(
571                    t.target.clone(),
572                    MixedState {
573                        states: t.target.clone(),
574                        transitions: vec![],
575                    },
576                );
577                processing_queue.push(t.target.clone());
578            }
579
580            all_states.get_mut(&current_key).unwrap().transitions = expanded_transitions;
581            processing_cursor += 1;
582        }
583
584        // All states have been processed. Now we'll just need to construct the final state
585        // machine.
586        let mut machine = StateMachine::<TCriteria, TAction> {
587            machine_id: ID_COUNTER.fetch_add(1, Ordering::Relaxed),
588            initial_state: MachineState::uninit(),
589            actions: vec![],
590            states: vec![],
591        };
592
593        // Resolve all state refs. The actions are defined on the original states, but to avoid
594        // requiring `Clone` on the `TAction`, we'll move them to just one vector which is indexed
595        // by `ActionRef`. However we'll need to calculate the refs beforehand, since we can't move
596        // the actions out of the `self.states` yet since partial moves out of `Vec` are not
597        // supported.
598        let mut actions_to_final: HashMap<(StateRef, usize), ActionRef> = HashMap::new();
599        for s in &self.states {
600            for i in 0..s.actions.len() {
601                let action_ref = ActionRef::new_unchecked(self.machine_id, actions_to_final.len());
602                actions_to_final.insert((s.self_ref, i), action_ref);
603            }
604        }
605
606        // Register all final states on the machine and construct a mapping between mixed state
607        // keys and final machine states. We'll need to resolve the `MachineState` references for
608        // use in the transitions when setting up individual states later.
609        let mut mixed_to_final: BTreeMap<MixedStateKey, MachineState> = BTreeMap::new();
610        for key in all_states.keys() {
611            mixed_to_final.insert(
612                key.clone(),
613                MachineState::next_ref(machine.machine_id, &machine.states),
614            );
615
616            // For now just add a place holder for the state. We'll fill this later once we have
617            // resolved `mixed_to_final` mappings for all states.
618            machine.states.push(FinalState {
619                actions: vec![],
620                transitions: vec![],
621            });
622        }
623
624        // Finally set up the final machine states based on the information resolved earlier.
625        let mut dfa_transition_count = 0;
626        for (mixed_key, mut mixed_state) in all_states {
627            // Reference to the for-now somewhat uninitialized final state.
628            let final_state = &mut machine.states[mixed_to_final[&mixed_key]];
629
630            // Fill in the action refs based on the actions of the original states this final state
631            // represents.
632            for original_ref in &mixed_key {
633                let original_state = &self.states[*original_ref];
634                for i in 0..original_state.actions.len() {
635                    final_state
636                        .actions
637                        .push(actions_to_final[&(*original_ref, i)]);
638                }
639            }
640
641            // Fill in the transitions. Since this is now the place used for the actual evaluation
642            // later, we need to ensure the transitions are sorted by the evaluation order.
643            mixed_state
644                .transitions
645                .sort_by_key(|t| t.criteria.evaluation_order());
646            for t in mixed_state.transitions {
647                final_state
648                    .transitions
649                    .push((t.criteria, mixed_to_final[&t.target]));
650            }
651
652            dfa_transition_count += final_state.transitions.len();
653        }
654
655        // Finally move the actions into the final StateMachine. The order here needs to follow the
656        // same order we used earlier when resolving the `ActionRef`s used to refer to these
657        // actions in the `machine.states`.
658        //
659        // This consumes the builder `self.states`, which is why we had to wait till now to do it.
660        for s in self.states {
661            for a in s.actions {
662                machine.actions.push(a)
663            }
664        }
665
666        let dfa_state_count = machine.states.len();
667        log::trace!(
668            "Built DFA with {} states, {} transitions from NFA of {} states, {} transitions",
669            dfa_state_count,
670            dfa_transition_count,
671            nfa_state_count,
672            nfa_transition_count
673        );
674
675        machine.initial_state = mixed_to_final[&initial_state_key];
676        machine
677    }
678}
679
680impl<TCriteria, TAction> Default for Builder<TCriteria, TAction>
681where
682    TCriteria: Criteria,
683{
684    fn default() -> Self { Self::new() }
685}
686
687fn expand_transitions<TCriteria>(
688    transitions: &mut Vec<MixedTransition<TCriteria>>,
689    transition: &Transition<TCriteria>,
690) where
691    TCriteria: Criteria,
692{
693    // Handle And-case first since here we can perform filtering on the fly
694    // without having to deal with shifting elements in the Vec.
695    // The And-case is more likely to result in empty criteria so dropping
696    // criteria is more likely in this case.
697    let and = transitions
698        .iter()
699        .filter_map(|t| {
700            let criteria = t.criteria.and(&transition.criteria);
701            match !criteria.is_empty() {
702                true => {
703                    let mut new = t.clone();
704                    new.target.add(transition.target);
705                    new.criteria = criteria;
706                    Some(new)
707                }
708                false => None,
709            }
710        })
711        .collect::<Vec<_>>();
712
713    // Handle the Not-case by removing the new criteria from the existing ones.
714    // This doesn't alter the target states.
715    let mut i = 0;
716    while i != transitions.len() {
717        let t = &mut transitions[i];
718        t.criteria = t.criteria.not(&transition.criteria);
719        if t.criteria.is_empty() {
720            transitions.remove(i);
721        } else {
722            i += 1;
723        }
724    }
725
726    transitions.extend(and);
727}
728
729impl<TAction> State<TAction> {
730    /// Create a new state with no action.
731    pub fn new() -> Self {
732        Self::new_impl(vec![])
733    }
734
735    /// Create a new state with an action.
736    pub fn with_action(action: TAction) -> Self {
737        Self::new_impl(vec![action])
738    }
739
740    /// Create a new state with actions.
741    pub fn with_actions<T>(actions: T) -> Self
742    where
743        T: IntoIterator<Item = TAction>,
744    {
745        use std::iter::FromIterator;
746        Self::new_impl(Vec::from_iter(actions))
747    }
748
749    fn new_impl(actions: Vec<TAction>) -> Self {
750        Self {
751            self_ref: StateRef::uninit(),
752            actions,
753            transitions: vec![],
754        }
755    }
756}
757
758impl<TAction> Default for State<TAction> {
759    fn default() -> Self { Self::new() }
760}
761
762impl<TCriteria> Transition<TCriteria> {
763    /// Create a new transition between states.
764    pub fn new(source: StateRef, criteria: TCriteria, target: StateRef) -> Self {
765        source.assert_same_machine(target);
766
767        Self {
768            source,
769            criteria,
770            target,
771        }
772    }
773}
774
775impl<TCriteria, TAction> StateMachine<TCriteria, TAction>
776where
777    TCriteria: Criteria,
778{
779    /// Acquires the initial state of the state machine.
780    pub fn start(&self) -> MachineState {
781        self.initial_state
782    }
783
784    /// Performs a step from the `current` machine state with the given `input`.
785    pub fn step(&self, current: MachineState, input: &TCriteria::Input) -> MachineState {
786        self.next_state(current, input)
787    }
788
789    fn next_state(&self, current: MachineState, input: &TCriteria::Input) -> MachineState {
790        current.assert_machine(self.machine_id);
791
792        let current_state = &self.states[current];
793        for t in &current_state.transitions {
794            if t.0.is_match(&input) {
795                return t.1;
796            }
797        }
798
799        self.start()
800    }
801}
802
803impl<TCriteria, TAction> StateMachine<TCriteria, TAction>
804where
805    TAction: Action,
806{
807    /// Executes the actions of the `current` state with the given `input`.
808    ///
809    /// Use [`Self::execute_mut`] if the current `TAction` is an [`ActionMut`].
810    pub fn execute(&self, current: MachineState, input: &mut TAction::Input) {
811        for a in &self.states[current].actions {
812            self.actions[*a].execute(input);
813        }
814    }
815}
816
817impl<TCriteria, TAction> StateMachine<TCriteria, TAction>
818where
819    TAction: ActionMut,
820{
821    /// Executes the actions of the `current` state with the given `input`.
822    ///
823    /// Use [`Self::execute`] if the current `TAction` is an [`Action`].
824    pub fn execute_mut(&mut self, current: MachineState, input: &mut TAction::Input) {
825        for a in &mut self.states[current].actions {
826            self.actions[*a].execute_mut(input);
827        }
828    }
829}
830
831impl<TCriteria, TAction> StateMachine<TCriteria, TAction>
832where
833    TAction: Action,
834    TCriteria: Criteria<Input = TAction::Input>,
835{
836    /// Performs a step from the current `state` and executes any actions based on the new state.
837    ///
838    /// This method is available only if the `TCriteria` and `TAction` have the same `Input` type
839    /// and is equivalent to:
840    ///
841    /// ```ignore
842    /// let next_state = machine.step(current_state, &input);
843    /// machine.execute(current_state, &mut input);
844    /// ```
845    pub fn step_and_execute(
846        &self,
847        current: MachineState,
848        input: &mut TAction::Input,
849    ) -> MachineState {
850        let next = self.step(current, input);
851        self.execute(next, input);
852        next
853    }
854}
855
856impl<TCriteria, TAction> StateMachine<TCriteria, TAction>
857where
858    TAction: ActionMut,
859    TCriteria: Criteria<Input = TAction::Input>,
860{
861    /// Performs a step from the current `state` and executes any actions based on the new state.
862    ///
863    /// This method is available only if the `TCriteria` and `TAction` have the same `Input` type
864    /// and is equivalent to:
865    ///
866    /// ```ignore
867    /// let next_state = machine.step(current_state, &input);
868    /// machine.execute_mut(current_state, &mut input);
869    /// ```
870    pub fn step_and_execute_mut(
871        &mut self,
872        current: MachineState,
873        input: &mut TAction::Input,
874    ) -> MachineState {
875        let next = self.step(current, input);
876        self.execute_mut(next, input);
877        next
878    }
879}
880
881impl<T> Action for Box<dyn Fn(&mut T)> {
882    type Input = T;
883    fn execute(&self, input: &mut T) {
884        self(input)
885    }
886}
887
888impl<T> ActionMut for Box<dyn FnMut(&mut T)> {
889    type Input = T;
890    fn execute_mut(&mut self, input: &mut T) {
891        self(input)
892    }
893}
894
895impl<'a, T> ActionMut for &'a mut dyn FnMut(&mut T) {
896    type Input = T;
897    fn execute_mut(&mut self, input: &mut T) {
898        self(input)
899    }
900}
901
902/// A basic "is" or "is not" condition criteria.
903#[derive(Clone, PartialEq, Eq, Hash, Debug)]
904pub enum Condition<T> {
905    /// Matches when the input is one of the values in the Vec.
906    Is(Vec<T>),
907
908    /// Matches when the input isn't one of the values in the Vec.
909    Not(Vec<T>),
910
911    /// Matches any value.
912    Any,
913
914    /// Matches no value.
915    None,
916}
917
918impl<T: Clone + Copy + PartialEq + Eq + Hash> Criteria for Condition<T> {
919    type Input = T;
920    fn is_match(&self, i: &Self::Input) -> bool {
921        match self {
922            Condition::Is(c) => c.contains(&i),
923            Condition::Not(c) => !c.contains(&i),
924            Condition::Any => true,
925            Condition::None => false,
926        }
927    }
928
929    fn and(&self, other: &Self) -> Self {
930        if self == other {
931            return self.clone();
932        }
933        match (self, other) {
934            (Condition::Is(i), Condition::Not(n)) | (Condition::Not(n), Condition::Is(i)) => {
935                let new: Vec<T> = i.iter().filter(|c| !n.contains(c)).copied().collect();
936                if new.is_empty() {
937                    Condition::None
938                } else {
939                    Condition::Is(new)
940                }
941            }
942            (Condition::Not(a), Condition::Not(b)) => {
943                let a: HashSet<T> = a.iter().copied().collect();
944                let b: HashSet<T> = b.iter().copied().collect();
945                let intersection: Vec<T> = a.intersection(&b).copied().collect();
946                if intersection.is_empty() {
947                    Condition::Any
948                } else {
949                    Condition::Not(intersection)
950                }
951            }
952            (Condition::Is(_), Condition::Is(_)) => Condition::None,
953            (Condition::None, _) | (_, Condition::None) => Condition::None,
954            (o, Condition::Any) | (Condition::Any, o) => o.clone(),
955        }
956    }
957
958    fn not(&self, other: &Self) -> Self {
959        if self == other {
960            return Condition::None;
961        }
962        match (self, other) {
963            (Condition::Not(n), Condition::Is(i)) => {
964                let mut new_not = n.clone();
965                new_not.extend(i);
966                Condition::Not(new_not)
967            }
968            (Condition::Any, Condition::Is(i)) => Condition::Not(i.clone()),
969            (Condition::None, _) => Condition::None,
970            (o, Condition::None) => o.clone(),
971            (_, Condition::Any) => Condition::None,
972            (o, Condition::Not(n)) => o.and(&Condition::Is(n.clone())),
973            (Condition::Is(i), _) => Condition::Is(i.clone()),
974        }
975    }
976
977    fn is_empty(&self) -> bool {
978        self == &Condition::None
979    }
980    fn any() -> Self {
981        Condition::Any
982    }
983    fn evaluation_order(&self) -> usize {
984        match self {
985            Condition::Any => 1,
986            _ => 0,
987        }
988    }
989}
990
991#[cfg(test)]
992mod test {
993    use super::*;
994    use test_env_log::test;
995
996    #[test]
997    fn testt() {
998        let mut builder = Builder::<Option<BTreeSet<char>>, _>::new();
999        let start = builder.create_initial_state();
1000        let first = builder.add_state(State::with_actions(vec!["S1"]));
1001        let second = builder.add_state(State::with_actions(vec!["S2"]));
1002        let third = builder.add_state(State::with_actions(vec!["S3"]));
1003
1004        use std::iter::FromIterator;
1005        builder.add_transition(Transition::new(start, None, start));
1006        builder.add_transition(Transition::new(
1007            start,
1008            Some(BTreeSet::from_iter("ab".chars())),
1009            first,
1010        ));
1011        builder.add_transition(Transition::new(
1012            first,
1013            Some(BTreeSet::from_iter("c".chars())),
1014            second,
1015        ));
1016        builder.add_transition(Transition::new(second, None, second));
1017        builder.add_transition(Transition::new(
1018            second,
1019            Some(BTreeSet::from_iter("a".chars())),
1020            third,
1021        ));
1022
1023        let machine = builder.build();
1024
1025        let mut next = machine.start();
1026        for mut s in ['a', 'b', 'b', 'c', 'c', 'a', 'a', 'c'].iter().copied() {
1027            next = machine.step(next, &s);
1028            machine.execute(next, &mut s);
1029        }
1030    }
1031
1032    impl Criteria for Option<BTreeSet<char>> {
1033        type Input = char;
1034
1035        fn is_match(&self, input: &Self::Input) -> bool {
1036            match self {
1037                None => true,
1038                Some(set) => set.contains(input),
1039            }
1040        }
1041
1042        fn is_empty(&self) -> bool {
1043            match self {
1044                None => false,
1045                Some(set) => set.len() == 0,
1046            }
1047        }
1048
1049        fn evaluation_order(&self) -> usize {
1050            match self {
1051                Some(_) => 0,
1052                None => 1,
1053            }
1054        }
1055
1056        fn and(&self, other: &Self) -> Self {
1057            match (self, other) {
1058                (None, None) => None,
1059                (None, s) | (s, None) => s.clone(),
1060                (Some(a), Some(b)) => Some(a.intersection(b).copied().collect()),
1061            }
1062        }
1063
1064        fn not(&self, other: &Self) -> Self {
1065            match (self, other) {
1066                (_, None) => Some(BTreeSet::new()),
1067                (None, _) => None,
1068                (Some(a), Some(b)) => Some(a.difference(b).copied().collect()),
1069            }
1070        }
1071
1072        fn any() -> Self {
1073            None
1074        }
1075    }
1076
1077    impl Action for &'static str {
1078        type Input = char;
1079        fn execute(&self, _: &mut char) {
1080            println!("State: {}", self);
1081        }
1082    }
1083}