rust_automata/
lib.rs

1//! Attribute‑style DSL for defining finite‑state machines.
2//!
3//! See the [rust-automata crate](https://crates.io/crates/rust-automata/) for high-level usage.
4
5pub use rust_automata_macros::state_machine;
6pub use rust_automata_macros::Display;
7
8#[doc(hidden)]
9#[cfg(feature = "mermaid")]
10pub use aquamarine::aquamarine;
11
12pub mod clock;
13#[doc(hidden)]
14mod takeable;
15pub mod timestamp;
16
17use core::fmt::Display;
18use std::hash::Hash;
19use std::marker::PhantomData;
20
21#[doc(hidden)]
22pub use takeable::Takeable;
23
24/// Trait for input/output alphabet. Used for internal enum generation.
25///
26/// All the input structs are enumerated in an internal enum that implements this trait.
27pub trait Alphabet: Display {
28    /// Return a value that represents no input/output.
29    fn nothing() -> Self;
30    /// Check if the alphabet is anything other than `nothing`.
31    fn any(&self) -> bool;
32}
33
34/// Trait for states. Used for internal enum generation.
35///
36/// All the state structs are enumerated in an internal enum that implements this trait.
37pub trait StateTrait: Display {
38    fn failure() -> Self;
39    fn is_failure(&self) -> bool;
40}
41
42// Get id in the enum wrapper. For internal use only.
43#[doc(hidden)]
44pub trait Enumerable<ForEnum> {
45    fn enum_id(&self) -> EnumId<ForEnum>;
46}
47
48// Get id in the wrapped struct. For internal use only.
49#[doc(hidden)]
50pub trait Enumerated<InEnum> {
51    fn enum_id() -> EnumId<InEnum>;
52}
53
54/// For internal use only.
55#[doc(hidden)]
56#[derive(Clone, Copy, Debug, Eq, PartialOrd, Ord, Default)]
57pub struct EnumId<ForEnum> {
58    pub id: usize,
59    _marker: PhantomData<ForEnum>,
60}
61
62impl<ForEnum> PartialEq for EnumId<ForEnum> {
63    fn eq(&self, other: &Self) -> bool {
64        self.id == other.id
65    }
66}
67
68impl<ForEnum> Hash for EnumId<ForEnum> {
69    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
70        self.id.hash(state);
71    }
72}
73
74impl<ForEnum> EnumId<ForEnum> {
75    pub fn new(id: usize) -> Self {
76        EnumId {
77            id,
78            _marker: PhantomData,
79        }
80    }
81}
82
83/// Describe any possible deterministic finite state  machine/transducer.
84///
85/// This is just a formal definition that may be inconvenient to be used in practical programming,
86/// but it is used throughout this library for more practical things.
87///
88/// For internal use only.
89#[doc(hidden)]
90pub trait StateMachineImpl {
91    /// The input alphabet.
92    type Input: Alphabet;
93    /// The set of possible states.
94    type State: StateTrait + Enumerable<Self::State>;
95    /// The output alphabet.
96    type Output: Alphabet;
97    /// The initial state. May be needed to be supplied manually by the user.
98    type InitialState: Enumerated<Self::State> + Into<Self::State>;
99    /// The transition function that takes ownership of the current state and returns
100    /// a new state along with any output based on the provided input.
101    fn transition(
102        &mut self,
103        state: Takeable<Self::State>,
104        input: Self::Input,
105    ) -> (Takeable<Self::State>, Self::Output);
106    /// Check if a transition is possible. If yes, return the output enum id.
107    fn can_transition(
108        &self,
109        state: &Self::State,
110        input: EnumId<Self::Input>,
111    ) -> Option<EnumId<Self::Output>>;
112}
113
114/// Encapsulates the state and other SM data and expose transition functions.
115pub struct StateMachine<T: StateMachineImpl> {
116    state: Takeable<T::State>,
117    data: T,
118}
119
120impl<T> StateMachine<T>
121where
122    T: StateMachineImpl,
123{
124    /// Create a new instance of this wrapper which encapsulates the initial state.
125    pub fn new(data: T, initial_state: T::InitialState) -> Self {
126        Self {
127            state: Takeable::new(initial_state.into()),
128            data,
129        }
130    }
131
132    /// Only change the state, do not accept any input and do not produce any output.
133    pub fn step(&mut self) {
134        let _: T::Output = self.relay::<T::Input, T::Output>(T::Input::nothing());
135    }
136
137    /// Produce an output, given no input.
138    pub fn produce<O: From<T::Output>>(&mut self) -> O {
139        self.relay::<T::Input, O>(T::Input::nothing())
140    }
141
142    /// Consume an input, do not care about the output.
143    pub fn consume<I: Into<T::Input>>(&mut self, input: I) {
144        let _: T::Output = self.relay::<I, T::Output>(input);
145    }
146
147    /// Consume an input, produce an output.
148    pub fn relay<I: Into<T::Input>, O: From<T::Output>>(&mut self, input: I) -> O {
149        let enum_input = input.into();
150        let from_str = self.state.as_ref().to_string();
151        let input_str = enum_input.to_string();
152
153        // Take ownership of the current state
154        let current_state = std::mem::replace(&mut self.state, Takeable::new(T::State::failure()));
155
156        // Call transition with owned state
157        let (next_state, output) = self.data.transition(current_state, enum_input);
158
159        // Update state with the result
160        self.state = next_state;
161
162        if self.state.is_failure() {
163            panic!(
164                "Invalid transition from {} using input {}",
165                from_str, input_str
166            );
167        }
168
169        O::from(output)
170    }
171
172    pub fn can_step(&mut self) -> bool {
173        let enum_input = EnumId::new(0);
174        let enum_state = self.state.as_ref();
175        let actual_output = self.data.can_transition(enum_state, enum_input);
176        actual_output.is_some()
177    }
178
179    pub fn can_produce<O>(&mut self) -> bool
180    where
181        O: Enumerated<T::Output>,
182    {
183        let enum_input = EnumId::new(0);
184        let enum_state = self.state.as_ref();
185        let actual_output = self.data.can_transition(enum_state, enum_input);
186        let expected_enum = O::enum_id();
187        match actual_output {
188            Some(enum_output) => enum_output == expected_enum,
189            None => false,
190        }
191    }
192
193    pub fn can_consume<I>(&mut self) -> bool
194    where
195        I: Enumerated<T::Input>,
196    {
197        let enum_input = I::enum_id();
198        let enum_state = self.state.as_ref();
199        let actual_output = self.data.can_transition(enum_state, enum_input);
200        actual_output.is_some()
201    }
202
203    pub fn can_relay<I, O>(&mut self) -> bool
204    where
205        I: Enumerated<T::Input>,
206        O: Enumerated<T::Output>,
207    {
208        let enum_input = I::enum_id();
209        let enum_state = self.state.as_ref();
210        let actual_output = self.data.can_transition(enum_state, enum_input);
211        let expected_enum = O::enum_id();
212        match actual_output {
213            Some(enum_output) => enum_output == expected_enum,
214            None => false,
215        }
216    }
217
218    /// Returns the current state.
219    pub fn state(&self) -> &T::State {
220        &self.state
221    }
222
223    pub fn data(&self) -> &T {
224        &self.data
225    }
226}