Finite state machines in rust; bendns fork to add types.
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs183
1 files changed, 183 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..fe08170
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,183 @@
+//! A framework for building finite state machines in Rust
+//!
+//! The `rust-fsm` crate provides a simple and universal framework for building
+//! state machines in Rust with minimum effort.
+//!
+//! The essential part of this crate is the [`StateMachineImpl`] trait. This
+//! trait allows a developer to provide a strict state machine definition, e.g.
+//! specify its:
+//!
+//! * An input alphabet - a set of entities that the state machine takes as
+//! inputs and performs state transitions based on them.
+//! * Possible states - a set of states this machine could be in.
+//! * An output alphabet - a set of entities that the state machine may output
+//! as results of its work.
+//! * A transition function - a function that changes the state of the state
+//! machine based on its current state and the provided input.
+//! * An output function - a function that outputs something from the output
+//! alphabet based on the current state and the provided inputs.
+//! * The initial state of the machine.
+//!
+//! Note that on the implementation level such abstraction allows build any type
+//! of state machines:
+//!
+//! * A classical state machine by providing only an input alphabet, a set of
+//! states and a transition function.
+//! * A Mealy machine by providing all entities listed above.
+//! * A Moore machine by providing an output function that do not depend on the
+//! provided inputs.
+//!
+//! # Use
+//!
+//! Initially this library was designed to build an easy to use DSL for defining
+//! state machines on top of it. Using the DSL will require to connect an
+//! additional crate `rust-fsm-dsl` (this is due to limitation of the procedural
+//! macros system).
+//!
+//! ## Using the DSL for defining state machines
+//!
+//! The DSL is parsed by the `state_machine` macro. Here is a little example.
+//!
+//! ```rust,ignore
+//! use rust_fsm::*;
+//!
+//! state_machine! {
+//! CircuitBreaker(Closed)
+//!
+//! Closed(Unsuccessful) => Open [SetupTimer],
+//! Open(TimerTriggered) => HalfOpen,
+//! HalfOpen => {
+//! Successful => Closed,
+//! Unsuccessful => Open [SetupTimer]
+//! }
+//! }
+//! ```
+//!
+//! This code sample:
+//!
+//! * Defines a state machine called `CircuitBreaker`;
+//! * Sets the initial state of this state machine to `Closed`;
+//! * Defines state transitions. For example: on receiving the `Successful`
+//! input when in the `HalfOpen` state, the machine must move to the `Closed`
+//! state;
+//! * Defines outputs. For example: on receiving `Unsuccessful` in the
+//! `Closed` state, the machine must output `SetupTimer`.
+//!
+//! This state machine can be used as follows:
+//!
+//! ```rust,ignore
+//! // Initialize the state machine. The state is `Closed` now.
+//! let mut machine: StateMachine<CircuitBreaker> = StateMachine::new();
+//! // Consume the `Successful` input. No state transition is performed.
+//! let _ = machine.consume(&CircuitBreakerInput::Successful);
+//! // Consume the `Unsuccesful` input. The machine is moved to the `Open`
+//! // state. The output is `SetupTimer`.
+//! let output = machine.consume(&CircuitBreakerInput::Unsuccesful).unwrap();
+//! // Check the output
+//! if output == Some(CircuitBreakerOutput::SetupTimer) {
+//! // Set up the timer...
+//! }
+//! // Check the state
+//! if machine.state() == &CircuitBreakerState::Open {
+//! // Do something...
+//! }
+//! ```
+//!
+//! As you can see, the following entities are generated:
+//!
+//! * An empty structure `CircuitBreaker` that implements the `StateMachineImpl`
+//! trait.
+//! * Enums `CircuitBreakerState`, `CircuitBreakerInput` and
+//! `CircuitBreakerOutput` that represent the state, the input alphabet and
+//! the output alphabet respectively.
+//!
+//! Note that if there is no outputs in the specification, the output alphabet
+//! is set to `()`. The set of states and the input alphabet must be non-empty
+//! sets.
+//!
+//! ## Without DSL
+//!
+//! The `state_machine` macro has limited capabilities (for example, a state
+//! cannot carry any additional data), so in certain complex cases a user might
+//! want to write a more complex state machine by hand.
+//!
+//! All you need to do to build a state machine is to implement the
+//! `StateMachineImpl` trait and use it in conjuctions with some of the provided
+//! wrappers (for now there is only `StateMachine`).
+//!
+//! You can see an example of the Circuit Breaker state machine in the
+//! [project repository][repo].
+//!
+//! [repo]: https://github.com/eugene-babichenko/rust-fsm/blob/master/examples/circuit_breaker.rs
+
+#[doc(hidden)]
+pub use rust_fsm_dsl::*;
+
+/// This trait is designed to describe any possible deterministic finite state
+/// machine/transducer. This is just a formal definition that may be
+/// inconvenient to be used in practical programming, but it is used throughout
+/// this library for more practical things.
+pub trait StateMachineImpl {
+ /// The input alphabet.
+ type Input;
+ /// The set of possible states.
+ type State: Copy;
+ /// The output alphabet.
+ type Output;
+ /// The initial state of the machine.
+ const INITIAL_STATE: Self::State;
+ /// The transition fuction that outputs a new state based on the current
+ /// state and the provided input. Outputs `None` when there is no transition
+ /// for a given combination of the input and the state.
+ fn transition(state: &Self::State, input: &Self::Input) -> Option<Self::State>;
+ /// The output function that outputs some value from the output alphabet
+ /// based on the current state and the given input. Outputs `None` when
+ /// there is no output for a given combination of the input and the state.
+ fn output(state: &Self::State, input: &Self::Input) -> Option<Self::Output>;
+}
+
+/// A convenience wrapper around the `StateMachine` trait that encapsulates the
+/// state and transition and output function calls.
+pub struct StateMachine<T: StateMachineImpl> {
+ state: T::State,
+}
+
+impl<T> StateMachine<T>
+where
+ T: StateMachineImpl,
+{
+ /// Create a new instance of this wrapper which encapsulates the initial
+ /// state.
+ pub fn new() -> Self {
+ Self {
+ state: T::INITIAL_STATE,
+ }
+ }
+
+ /// Consumes the provided input, gives an output and performs a state
+ /// transition. If a state transition with the current state and the
+ /// provided input is not allowed, returns an error.
+ pub fn consume(&mut self, input: &T::Input) -> Result<Option<T::Output>, ()> {
+ if let Some(state) = T::transition(&self.state, input) {
+ let output = T::output(&self.state, input);
+ self.state = state;
+ Ok(output)
+ } else {
+ Err(())
+ }
+ }
+
+ /// Returns the current state.
+ pub fn state(&self) -> &T::State {
+ &self.state
+ }
+}
+
+impl<T> Default for StateMachine<T>
+where
+ T: StateMachineImpl,
+{
+ fn default() -> Self {
+ Self::new()
+ }
+}