Finite state machines in rust; bendns fork to add types.
Allow more compact representation of transitions from the same state
For example, the following HalfOpen(Successful) => Closed, HalfOpen(Unsuccessful) => Open [SetupTimer] is transformed into a simpler form: HalfOpen => { Successful => Closed, Unsuccessful => Open [SetupTimer] }
Yevhenii Babichenko 2019-05-11
parent 83924cc · commit 3646373
-rw-r--r--CHANGELOG.md2
-rw-r--r--README.md6
-rw-r--r--rust_fsm/examples/circuit_breaker_dsl.rs6
-rw-r--r--rust_fsm/src/lib.rs6
-rw-r--r--rust_fsm_dsl/src/lib.rs127
5 files changed, 117 insertions, 30 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 14e601e..6b9bc79 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,8 @@ adheres to [Semantic Versioning][semver].
* More clear naming:
* Renamed the `StateMachineWrapper` structure to `StateMachine`;
* Renamed the `StateMachine` trait to `StateMachineImpl`.
+* Allow to specify multiple transitions from the same state in a more compact
+ form. See the example for the details.
## [0.1.0] - 2019-04-29
### Added
diff --git a/README.md b/README.md
index b84769b..e9e9971 100644
--- a/README.md
+++ b/README.md
@@ -58,8 +58,10 @@ state_machine! {
Closed(Unsuccessful) => Open [SetupTimer],
Open(TimerTriggered) => HalfOpen,
- HalfOpen(Successful) => Closed,
- HalfOpen(Unsuccessful) => Open [SetupTimer],
+ HalfOpen => {
+ Successful => Closed,
+ Unsuccessful => Open [SetupTimer],
+ }
}
```
diff --git a/rust_fsm/examples/circuit_breaker_dsl.rs b/rust_fsm/examples/circuit_breaker_dsl.rs
index 2860601..f1e81d5 100644
--- a/rust_fsm/examples/circuit_breaker_dsl.rs
+++ b/rust_fsm/examples/circuit_breaker_dsl.rs
@@ -13,8 +13,10 @@ state_machine! {
Closed(Unsuccessful) => Open [SetupTimer],
Open(TimerTriggered) => HalfOpen,
- HalfOpen(Successful) => Closed,
- HalfOpen(Unsuccessful) => Open [SetupTimer],
+ HalfOpen => {
+ Successful => Closed,
+ Unsuccessful => Open [SetupTimer]
+ }
}
fn main() {
diff --git a/rust_fsm/src/lib.rs b/rust_fsm/src/lib.rs
index 65c1e13..ca863ef 100644
--- a/rust_fsm/src/lib.rs
+++ b/rust_fsm/src/lib.rs
@@ -56,8 +56,10 @@
//!
//! Closed(Unsuccessful) => Open [SetupTimer],
//! Open(TimerTriggered) => HalfOpen,
-//! HalfOpen(Successful) => Closed,
-//! HalfOpen(Unsuccessful) => Open [SetupTimer],
+//! HalfOpen => {
+//! Successful => Closed,
+//! Unsuccessful => Open [SetupTimer]
+//! }
//! }
//! ```
//!
diff --git a/rust_fsm_dsl/src/lib.rs b/rust_fsm_dsl/src/lib.rs
index fba3122..38dabd2 100644
--- a/rust_fsm_dsl/src/lib.rs
+++ b/rust_fsm_dsl/src/lib.rs
@@ -8,38 +8,46 @@ use proc_macro::TokenStream;
use quote::quote;
use std::collections::HashSet;
use syn::{
- bracketed, parenthesized,
- parse::{Parse, ParseStream, Result},
+ braced, bracketed, parenthesized,
+ parse::{Error, Parse, ParseStream, Result},
parse_macro_input,
- punctuated::Punctuated,
- token::Bracket,
+ token::{Bracket, Paren},
Ident, Token, Visibility,
};
-struct TransitionDef {
- initial_state: Ident,
+struct Output(Option<Ident>);
+
+impl Parse for Output {
+ fn parse(input: ParseStream) -> Result<Self> {
+ if input.lookahead1().peek(Bracket) {
+ let output_content;
+ bracketed!(output_content in input);
+ Ok(Self(Some(output_content.parse()?)))
+ } else {
+ Ok(Self(None))
+ }
+ }
+}
+
+impl Into<Option<Ident>> for Output {
+ fn into(self) -> Option<Ident> {
+ self.0
+ }
+}
+
+struct TransitionEntry {
input_value: Ident,
final_state: Ident,
output: Option<Ident>,
}
-impl Parse for TransitionDef {
+impl Parse for TransitionEntry {
fn parse(input: ParseStream) -> Result<Self> {
- let initial_state = input.parse()?;
- let input_content;
- parenthesized!(input_content in input);
- let input_value = input_content.parse()?;
+ let input_value = input.parse()?;
input.parse::<Token![=>]>()?;
let final_state = input.parse()?;
- let output = if input.lookahead1().peek(Bracket) {
- let output_content;
- bracketed!(output_content in input);
- Some(output_content.parse()?)
- } else {
- None
- };
+ let output = input.parse::<Output>()?.into();
Ok(Self {
- initial_state,
input_value,
final_state,
output,
@@ -47,21 +55,72 @@ impl Parse for TransitionDef {
}
}
+struct TransitionDef {
+ initial_state: Ident,
+ transitions: Vec<TransitionEntry>,
+}
+
+impl Parse for TransitionDef {
+ fn parse(input: ParseStream) -> Result<Self> {
+ let initial_state = input.parse()?;
+ let transitions = if input.lookahead1().peek(Paren) {
+ let input_content;
+ parenthesized!(input_content in input);
+ let input_value = input_content.parse()?;
+ input.parse::<Token![=>]>()?;
+ let final_state = input.parse()?;
+ let output = input.parse::<Output>()?.into();
+
+ vec![TransitionEntry {
+ input_value,
+ final_state,
+ output,
+ }]
+ } else {
+ input.parse::<Token![=>]>()?;
+ let entries_content;
+ braced!(entries_content in input);
+
+ let entries: Vec<_> = entries_content
+ .parse_terminated::<_, Token![,]>(TransitionEntry::parse)?
+ .into_iter()
+ .collect();
+ if entries.is_empty() {
+ return Err(Error::new_spanned(
+ initial_state,
+ "No transitions provided for a compact representation",
+ ));
+ }
+ entries
+ };
+ Ok(Self {
+ initial_state,
+ transitions,
+ })
+ }
+}
+
struct StateMachineDef {
visibility: Visibility,
name: Ident,
initial_state: Ident,
- transitions: Punctuated<TransitionDef, Token![,]>,
+ transitions: Vec<TransitionDef>,
}
impl Parse for StateMachineDef {
fn parse(input: ParseStream) -> Result<Self> {
let visibility = input.parse()?;
let name = input.parse()?;
+
let initial_state_content;
parenthesized!(initial_state_content in input);
let initial_state = initial_state_content.parse()?;
- let transitions = input.parse_terminated(TransitionDef::parse)?;
+
+ let transitions = input
+ .parse_terminated::<_, Token![,]>(TransitionDef::parse)?
+ .into_iter()
+ .collect();
+
Ok(Self {
visibility,
name,
@@ -71,6 +130,13 @@ impl Parse for StateMachineDef {
}
}
+struct Transition<'a> {
+ initial_state: &'a Ident,
+ input_value: &'a Ident,
+ final_state: &'a Ident,
+ output: &'a Option<Ident>,
+}
+
#[proc_macro]
pub fn state_machine(tokens: TokenStream) -> TokenStream {
let input = parse_macro_input!(tokens as StateMachineDef);
@@ -85,13 +151,26 @@ pub fn state_machine(tokens: TokenStream) -> TokenStream {
let struct_name = input.name;
let visibility = input.visibility;
+ let transitions: Vec<_> = input
+ .transitions
+ .iter()
+ .flat_map(|def| {
+ def.transitions.iter().map(move |transition| Transition {
+ initial_state: &def.initial_state,
+ input_value: &transition.input_value,
+ final_state: &transition.final_state,
+ output: &transition.output,
+ })
+ })
+ .collect();
+
let mut states = HashSet::new();
let mut inputs = HashSet::new();
let mut outputs = HashSet::new();
states.insert(&input.initial_state);
- for transition in input.transitions.iter() {
+ for transition in transitions.iter() {
states.insert(&transition.initial_state);
states.insert(&transition.final_state);
inputs.insert(&transition.input_value);
@@ -106,7 +185,7 @@ pub fn state_machine(tokens: TokenStream) -> TokenStream {
let inputs_enum_name = Ident::new(&format!("{}Input", struct_name), struct_name.span());
let mut transition_cases = vec![];
- for transition in input.transitions.iter() {
+ for transition in transitions.iter() {
let initial_state = &transition.initial_state;
let input_value = &transition.input_value;
let final_state = &transition.final_state;
@@ -129,7 +208,7 @@ pub fn state_machine(tokens: TokenStream) -> TokenStream {
let outputs_type = quote! { #outputs_type_name };
let mut output_cases = vec![];
- for transition in input.transitions.iter() {
+ for transition in transitions.iter() {
if let Some(output_value) = &transition.output {
let initial_state = &transition.initial_state;
let input_value = &transition.input_value;