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 ¤t_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(¤t_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 ¤t_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}