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]
}
| -rw-r--r-- | CHANGELOG.md | 2 | ||||
| -rw-r--r-- | README.md | 6 | ||||
| -rw-r--r-- | rust_fsm/examples/circuit_breaker_dsl.rs | 6 | ||||
| -rw-r--r-- | rust_fsm/src/lib.rs | 6 | ||||
| -rw-r--r-- | rust_fsm_dsl/src/lib.rs | 127 |
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 @@ -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; |