Finite state machines in rust; bendns fork to add types.
other changes
| -rw-r--r-- | doc-example/src/lib.rs | 2 | ||||
| -rw-r--r-- | rust-fsm-dsl/src/lib.rs | 82 | ||||
| -rw-r--r-- | rust-fsm-dsl/src/parser.rs | 15 | ||||
| -rw-r--r-- | rust-fsm-dsl/src/variant.rs | 123 | ||||
| -rw-r--r-- | rust-fsm/src/lib.rs | 10 | ||||
| -rw-r--r-- | rust-fsm/tests/circuit_breaker_dsl.rs | 4 | ||||
| -rw-r--r-- | rust-fsm/tests/circuit_breaker_dsl_custom_types.rs | 4 | ||||
| -rw-r--r-- | rust-fsm/tests/simple.rs | 2 |
8 files changed, 182 insertions, 60 deletions
diff --git a/doc-example/src/lib.rs b/doc-example/src/lib.rs index 397b4a8..c34c52e 100644 --- a/doc-example/src/lib.rs +++ b/doc-example/src/lib.rs @@ -4,7 +4,7 @@ state_machine! { /// A dummy implementation of the Circuit Breaker pattern to demonstrate /// capabilities of its library DSL for defining finite state machines. /// https://martinfowler.com/bliki/CircuitBreaker.html - pub CircuitBreaker: Closed => Result => Action + pub CircuitBreaker => Result => Action Closed => Unsuccessful => Open [SetupTimer], Open => TimerTriggered => HalfOpen, diff --git a/rust-fsm-dsl/src/lib.rs b/rust-fsm-dsl/src/lib.rs index 621402e..df39b74 100644 --- a/rust-fsm-dsl/src/lib.rs +++ b/rust-fsm-dsl/src/lib.rs @@ -12,14 +12,14 @@ mod parser; mod variant; use variant::Variant; -use crate::parser::StateMachineDef; +use crate::{parser::StateMachineDef, variant::Final}; /// The full information about a state transition. Used to unify the /// represantion of the simple and the compact forms. struct Transition<'a> { initial_state: &'a Variant, input_value: &'a Variant, - final_state: &'a Variant, - output: &'a Option<Variant>, + final_state: &'a Final, + output: &'a Option<Final>, } fn attrs_to_token_stream(attrs: Vec<Attribute>) -> proc_macro2::TokenStream { @@ -37,7 +37,6 @@ pub fn state_machine(tokens: TokenStream) -> TokenStream { state_name, input_name, output_name, - initial_state, transitions, attributes, } = parse_macro_input!(tokens as parser::StateMachineDef); @@ -60,21 +59,19 @@ pub fn state_machine(tokens: TokenStream) -> TokenStream { output: &transition.output, }) }); - + // fn id(x: impl std::hash::Hash) -> u64 { + // use std::hash::BuildHasher; + // rustc_hash::FxSeededState::with_seed(5).hash_one(x) + // } let mut states = BTreeSet::new(); let mut inputs = BTreeSet::new(); let mut outputs = BTreeSet::new(); let mut transition_cases = Vec::new(); let mut output_cases = Vec::new(); + use std::fmt::Write; #[cfg(feature = "diagram")] - let mut mermaid_diagram = format!( - "///```mermaid\n///stateDiagram-v2\n/// [*] --> {}\n", - initial_state - ); - - states.insert(&initial_state); - + let mut mermaid_diagram = format!("///```mermaid\n///stateDiagram-v2\n",); for transition in transitions { let Transition { initial_state, @@ -83,36 +80,57 @@ pub fn state_machine(tokens: TokenStream) -> TokenStream { output, } = transition; + // #[cfg(feature = "diagram")] + // writeln!( + // mermaid_diagram, + // "/// {}: {initial_state}", + // id(&initial_state) + // ) + // .unwrap(); + // #[cfg(feature = "diagram")] + // writeln!( + // mermaid_diagram, + // "/// {}: {final_state}", + // id(&final_state) + // ) + // .unwrap(); #[cfg(feature = "diagram")] - mermaid_diagram.push_str(&format!( - "/// {initial_state} --> {final_state}: {input_value}" - )); - - let input_ = input_value.match_on(); - let final_state_ = final_state.match_on(); + write!( + mermaid_diagram, + "/// {initial_state} --> {final_state}: {}", + input_value.match_on() + ) + .unwrap(); + + let initial_ = initial_state.match_on(); + let final_ = final_state.reduce(); + let (input_, guard) = input_value.separate(); + + // let input_ = input_value.match_on(); + // let final_state_ = final_state.match_on(); transition_cases.push(quote! { - (Self::#initial_state, Self::Input::#input_) => { - Some(Self::#final_state_) + (Self::#initial_, Self::Input::#input_) #guard => { + Some(Self::#final_) } }); if let Some(output_value) = output { - let output_value_ = output_value.match_on(); + let output_ = output_value.reduce(); output_cases.push(quote! { - (Self::#initial_state, Self::Input::#input_) => { - Some(Self::Output::#output_value_) + (Self::#initial_, Self::Input::#input_) #guard => { + Some(Self::Output::#output_) } }); #[cfg(feature = "diagram")] - mermaid_diagram.push_str(&format!(" [{output_value}]")); + mermaid_diagram.push_str(&format!(" [\"{output_value}\"]")); } #[cfg(feature = "diagram")] mermaid_diagram.push('\n'); states.insert(initial_state); - states.insert(final_state); + states.insert(Box::leak(Box::new(final_state.clone().variant()))); inputs.insert(input_value); if let Some(ref output) = output { outputs.insert(output); @@ -122,9 +140,15 @@ pub fn state_machine(tokens: TokenStream) -> TokenStream { #[cfg(feature = "diagram")] mermaid_diagram.push_str("///```"); #[cfg(feature = "diagram")] - let mermaid_diagram: proc_macro2::TokenStream = mermaid_diagram.parse().unwrap(); - - let initial_state_name = &initial_state; + let mermaid_diagram: proc_macro2::TokenStream = mermaid_diagram + .replace("::", "#58;#58;") + .replace('(', "#40;") + .replace(')', "#41;") + .replace('[', "#91;") + .replace(']', "#93;") + .replace("Default", "def") + .parse() + .unwrap(); let input_impl = input_name.tokenize(|f| { quote! { @@ -172,7 +196,6 @@ pub fn state_machine(tokens: TokenStream) -> TokenStream { #[cfg(not(feature = "diagram"))] let diagram = quote!(); - let output = quote! { #input_impl #doc @@ -183,7 +206,6 @@ pub fn state_machine(tokens: TokenStream) -> TokenStream { impl ::rust_fsm::StateMachineImpl for #state_name { type Input = #input_name; type Output = #output_name; - const INITIAL_STATE: Self = Self::#initial_state_name; fn transition(self, input: Self::Input) -> Option<Self> { match (self, input) { diff --git a/rust-fsm-dsl/src/parser.rs b/rust-fsm-dsl/src/parser.rs index f1a9b67..81b59e0 100644 --- a/rust-fsm-dsl/src/parser.rs +++ b/rust-fsm-dsl/src/parser.rs @@ -1,3 +1,5 @@ +use crate::variant::Final; + use super::variant::Variant; use proc_macro2::TokenStream; use syn::{ @@ -6,7 +8,7 @@ use syn::{ *, }; /// The output of a state transition -pub struct Output(Option<Variant>); +pub struct Output(Option<Final>); impl Parse for Output { fn parse(input: ParseStream) -> Result<Self> { @@ -20,7 +22,7 @@ impl Parse for Output { } } -impl From<Output> for Option<Variant> { +impl From<Output> for Option<Final> { fn from(output: Output) -> Self { output.0 } @@ -30,8 +32,8 @@ impl From<Output> for Option<Variant> { /// trait is implemented for the compact form. pub struct TransitionEntry { pub input_value: Variant, - pub final_state: Variant, - pub output: Option<Variant>, + pub final_state: Final, + pub output: Option<Final>, } impl Parse for TransitionEntry { @@ -120,7 +122,6 @@ pub struct StateMachineDef { pub state_name: ImplementationRequired, pub input_name: ImplementationRequired, pub output_name: ImplementationRequired, - pub initial_state: Variant, pub transitions: Vec<TransitionDef>, pub attributes: Vec<Attribute>, } @@ -168,9 +169,6 @@ impl Parse for StateMachineDef { .or_else(|_| input.parse::<Path>().map(ImplementationRequired::No)) }; let state_name = i()?; - input.parse::<Token![:]>()?; - let initial_state = input.parse()?; - input.parse::<Token![=>]>()?; let input_name = i()?; input.parse::<Token![=>]>()?; @@ -187,7 +185,6 @@ impl Parse for StateMachineDef { state_name, input_name, output_name, - initial_state, transitions, attributes, }) diff --git a/rust-fsm-dsl/src/variant.rs b/rust-fsm-dsl/src/variant.rs index f9f45f3..a59a5f6 100644 --- a/rust-fsm-dsl/src/variant.rs +++ b/rust-fsm-dsl/src/variant.rs @@ -5,9 +5,8 @@ use syn::{parse::Parse, *}; /// Variant with no discriminator #[derive(Hash, Debug, PartialEq, Eq)] pub struct Variant { - // attrs: Vec<Attribute>, - ident: Ident, - field: Option<(Type, Pat)>, + pub ident: Ident, + field: Option<(Type, Pat, Option<Expr>)>, } impl Parse for Variant { @@ -20,7 +19,15 @@ impl Parse for Variant { parenthesized!(inp in input); let t = inp.parse()?; inp.parse::<Token![=>]>()?; - Some((t, Pat::parse_multi(&inp)?)) + + Some(( + t, + Pat::parse_multi(&inp)?, + inp.lookahead1() + .peek(Token![if]) + .then(|| inp.parse::<Token![if]>().and_then(|_| inp.parse::<Expr>())) + .transpose()?, + )) } else { None }; @@ -46,7 +53,7 @@ impl Ord for Variant { impl ToTokens for Variant { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { self.ident.to_tokens(tokens); - if let Some((t, _)) = &self.field { + if let Some((t, _, _)) = &self.field { tokens.extend(quote::quote! { (#t) }) } } @@ -56,18 +63,118 @@ impl Variant { pub fn match_on(&self) -> proc_macro2::TokenStream { if let Self { ident, - field: Some((_, p)), + field: Some((_, p, g)), } = self { - quote::quote! { #ident(#p) } + let b = g + .as_ref() + .map_or_else(Default::default, |x| quote::quote! { if #x }); + quote::quote! { #ident(#p) #b } } else { self.ident.to_token_stream() } } + pub fn separate(&self) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) { + if let Self { + ident, + field: Some((_, p, g)), + } = self + { + let b = g + .as_ref() + .map(|x| quote::quote! { if #x }) + .unwrap_or_default(); + (quote::quote! { #ident(#p) }, b) + } else { + (self.ident.to_token_stream(), quote::quote! {}) + } + } } impl Display for Variant { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.match_on()) + write!(f, "{}", self.ident) + } +} +/// type and expression +#[derive(Hash, Debug, PartialEq, Eq, Clone)] +pub struct Final { + pub ident: Ident, + field: Option<(Type, Expr)>, +} + +impl Parse for Final { + fn parse(input: parse::ParseStream) -> Result<Self> { + let ident: Ident = input.parse()?; + let field = if input.peek(token::Paren) { + let inp; + parenthesized!(inp in input); + let t = inp.parse()?; + inp.parse::<Token![=>]>()?; + + Some((t, inp.parse()?)) + } else { + None + }; + Ok(Final { + // attrs, + ident, + field, + }) + } +} + +impl PartialOrd for Final { + fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { + self.ident.partial_cmp(&other.ident) + } +} +impl Ord for Final { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.ident.cmp(&other.ident) + } +} + +impl ToTokens for Final { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + self.ident.to_tokens(tokens); + if let Some((v, _)) = &self.field { + tokens.extend(quote::quote! { (#v) }) + } + } +} + +impl Final { + pub fn reduce(&self) -> proc_macro2::TokenStream { + if let Self { + ident, + field: Some((_, v)), + } = self + { + quote::quote! { #ident ( #v ) } + } else { + self.ident.to_token_stream() + } + } + pub fn variant(self) -> Variant { + Variant { + ident: self.ident, + field: self.field.map(|(x, _)| { + ( + x, + Pat::Wild(PatWild { + attrs: vec![], + underscore_token: Default::default(), + }), + None, + ) + }), + } + } +} + +impl Display for Final { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.ident) } } diff --git a/rust-fsm/src/lib.rs b/rust-fsm/src/lib.rs index 4bb192b..3240fdd 100644 --- a/rust-fsm/src/lib.rs +++ b/rust-fsm/src/lib.rs @@ -248,10 +248,6 @@ pub trait StateMachineImpl: Sized { type Input; /// The output alphabet. type Output; - /// The initial state of the machine. - // allow since there is usually no interior mutability because states are enums - #[allow(clippy::declare_interior_mutable_const)] - const INITIAL_STATE: Self; /// 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. @@ -260,6 +256,9 @@ pub trait StateMachineImpl: Sized { /// 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(self, input: Self::Input) -> Option<Self::Output>; + /// 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. fn consume( &mut self, input: Self::Input, @@ -273,9 +272,6 @@ pub trait StateMachineImpl: Sized { .ok_or(TransitionImpossibleError) .map(|state| std::mem::replace(self, state).output(input)) } - fn new() -> Self { - Self::INITIAL_STATE - } } #[derive(Debug, Clone)] diff --git a/rust-fsm/tests/circuit_breaker_dsl.rs b/rust-fsm/tests/circuit_breaker_dsl.rs index 407ac0d..c49787e 100644 --- a/rust-fsm/tests/circuit_breaker_dsl.rs +++ b/rust-fsm/tests/circuit_breaker_dsl.rs @@ -10,7 +10,7 @@ state_machine! { /// capabilities of its library DSL for defining finite state machines. /// https://martinfowler.com/bliki/CircuitBreaker.html #[derive(Clone, Copy)] - pub CircuitBreaker: Closed => Result => Action + pub CircuitBreaker => Result => Action Closed => Unsuccessful => Open [SetupTimer], Open => TimerTriggered => HalfOpen, @@ -22,7 +22,7 @@ state_machine! { #[test] fn circit_breaker_dsl() { - let machine = CircuitBreaker::new(); + let machine = CircuitBreaker::Closed; // Unsuccessful request let machine = Arc::new(Mutex::new(machine)); diff --git a/rust-fsm/tests/circuit_breaker_dsl_custom_types.rs b/rust-fsm/tests/circuit_breaker_dsl_custom_types.rs index 0d1b95e..4b22e08 100644 --- a/rust-fsm/tests/circuit_breaker_dsl_custom_types.rs +++ b/rust-fsm/tests/circuit_breaker_dsl_custom_types.rs @@ -25,7 +25,7 @@ pub enum Output { } state_machine! { - crate::State: Closed => crate::Input => crate::Output + crate::State => crate::Input => crate::Output Closed => Unsuccessful => Open [SetupTimer], Open => TimerTriggered => HalfOpen, @@ -37,7 +37,7 @@ state_machine! { #[test] fn circit_breaker_dsl() { - let machine = State::new(); + let machine = State::Closed; // Unsuccessful request let machine = Arc::new(Mutex::new(machine)); diff --git a/rust-fsm/tests/simple.rs b/rust-fsm/tests/simple.rs index 4ff4389..6f4a7b5 100644 --- a/rust-fsm/tests/simple.rs +++ b/rust-fsm/tests/simple.rs @@ -3,7 +3,7 @@ use rust_fsm::*; state_machine! { #[derive(Debug, Clone, Copy)] #[repr(C)] - Door: Open => Action => __ + Door => Action => __ Open => Key => Closed, Closed => Key => Open, |