Finite state machines in rust; bendns fork to add types.
| -rw-r--r-- | rust-fsm-dsl/src/lib.rs | 43 | ||||
| -rw-r--r-- | rust-fsm-dsl/src/variant.rs | 18 | ||||
| -rw-r--r-- | rust-fsm/src/lib.rs | 29 | ||||
| -rw-r--r-- | rust-fsm/tests/circuit_breaker_dsl.rs | 2 | ||||
| -rw-r--r-- | rust-fsm/tests/circuit_breaker_dsl_custom_types.rs | 2 |
5 files changed, 39 insertions, 55 deletions
diff --git a/rust-fsm-dsl/src/lib.rs b/rust-fsm-dsl/src/lib.rs index 8bd6e18..1b25fba 100644 --- a/rust-fsm-dsl/src/lib.rs +++ b/rust-fsm-dsl/src/lib.rs @@ -6,7 +6,6 @@ extern crate proc_macro; use proc_macro::TokenStream; use quote::{quote, ToTokens}; -use std::iter::FromIterator; use syn::*; mod parser; mod variant; @@ -24,7 +23,7 @@ struct Transition<'a> { fn attrs_to_token_stream(attrs: Vec<Attribute>) -> proc_macro2::TokenStream { let attrs = attrs.into_iter().map(ToTokens::into_token_stream); - proc_macro2::TokenStream::from_iter(attrs) + attrs.collect() } #[proc_macro] @@ -67,7 +66,6 @@ pub fn state_machine(tokens: TokenStream) -> TokenStream { let mut inputs = vec![]; let mut outputs = vec![]; let mut transition_cases = vec![]; - let mut output_cases = vec![]; #[cfg(feature = "diagram")] let mut mermaid_diagram = format!( @@ -126,24 +124,22 @@ pub fn state_machine(tokens: TokenStream) -> TokenStream { // let input_ = input_value.match_on(); // let final_state_ = final_state.match_on(); + let output_ = output + .as_ref() + .map(|x| { + #[cfg(feature = "diagram")] + mermaid_diagram.push_str(&format!(" [\"{x}\"]")); + let output = x.reduce(); + quote! { ::core::option::Option::Some(Self::Output::#output) } + }) + .unwrap_or(quote! { ::core::option::Option::None }); + // let x = format!("{}, {} {} => {}", initial_, input_, guard, output_); transition_cases.push(quote! { (Self::#initial_, Self::Input::#input_) #guard => { - Some(Self::#final_) + ::core::result::Result::Ok((Self::#final_, #output_)) } }); - if let Some(output_value) = output { - let output_ = output_value.reduce(); - output_cases.push(quote! { - (Self::#initial_, Self::Input::#input_) #guard => { - Some(Self::Output::#output_) - } - }); - - #[cfg(feature = "diagram")] - mermaid_diagram.push_str(&format!(" [\"{output_value}\"]")); - } - #[cfg(feature = "diagram")] mermaid_diagram.push('\n'); @@ -191,7 +187,7 @@ pub fn state_machine(tokens: TokenStream) -> TokenStream { }) }); let state_name = state_name.path(); - let output_impl = variant::tokenize(&*outputs, |outputs| { + let output_impl = variant::tokenize(&outputs, |outputs| { output_name.tokenize(|output_name| { // Many attrs and derives may work incorrectly (or simply not work) for empty enums, so we just skip them // altogether if the output alphabet is empty. @@ -231,19 +227,16 @@ pub fn state_machine(tokens: TokenStream) -> TokenStream { type Input = #input_name; type Output = #output_name; - fn transition(self, input: Self::Input) -> Option<Self> { + fn transition(self, input: Self::Input) -> ::core::result::Result< + (Self, ::core::option::Option<Self::Output>), + ::rust_fsm::TransitionImpossibleError<Self, Self::Input> + > { match (self, input) { #(#transition_cases)* - _ => None, + (state, input) => ::core::result::Result::Err(::rust_fsm::TransitionImpossibleError { state, input, }), } } - fn output(self, input: Self::Input) -> Option<Self::Output> { - match (self, input) { - #(#output_cases)* - _ => None, - } - } } }; diff --git a/rust-fsm-dsl/src/variant.rs b/rust-fsm-dsl/src/variant.rs index 86f3b25..681b0a2 100644 --- a/rust-fsm-dsl/src/variant.rs +++ b/rust-fsm-dsl/src/variant.rs @@ -16,8 +16,7 @@ pub fn find_type(of: &Variant, list: &[Variant]) -> Option<Type> { let i = &of.ident; list.iter() .filter(|x| &x.ident == i) - .filter_map(|x| x.field.as_ref().and_then(|x| x.0.clone())) - .next() + .find_map(|x| x.field.as_ref().and_then(|x| x.0.clone())) }) }) } @@ -29,14 +28,11 @@ pub fn tokenize( .into_iter() .map(|x| { let i = &x.ident; - x.field - .as_ref() - .map(|_| { - let y = find_type(x, &*inputs); - y.ok_or(Error::new_spanned(&x.ident, "type never specified")) - .map(|y| quote::quote! {#i(#y)}) - }) - .unwrap_or(Ok(quote::quote! { #i })) + x.field.as_ref().map_or(Ok(quote::quote! { #i }), |_| { + let y = find_type(x, inputs); + y.ok_or(Error::new_spanned(&x.ident, "type never specified")) + .map(|y| quote::quote! {#i(#y)}) + }) }) .collect::<Result<_>>() .map_err(Error::into_compile_error) @@ -89,7 +85,7 @@ impl Eq for Variant {} impl PartialOrd for Variant { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { - self.ident.partial_cmp(&other.ident) + Some(self.cmp(other)) } } impl Ord for Variant { diff --git a/rust-fsm/src/lib.rs b/rust-fsm/src/lib.rs index e894ee9..2012892 100644 --- a/rust-fsm/src/lib.rs +++ b/rust-fsm/src/lib.rs @@ -229,7 +229,7 @@ You can see an example of the Circuit Breaker state machine in the #![cfg_attr(not(feature = "std"), no_std)] -use core::fmt::{self, Debug, Display}; +use core::fmt::{self, Debug}; #[cfg(feature = "std")] use std::error::Error; @@ -250,12 +250,11 @@ pub trait StateMachineImpl: Sized { type Output; /// 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(self, input: Self::Input) -> Option<Self>; - /// 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(self, input: Self::Input) -> Option<Self::Output>; + /// for a given combination of the input and the state. Also gives you the output, if any. + fn transition( + self, + input: Self::Input, + ) -> Result<(Self, Option<Self::Output>), TransitionImpossibleError<Self, Self::Input>>; /// 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. @@ -264,16 +263,12 @@ pub trait StateMachineImpl: Sized { input: Self::Input, ) -> Result<Option<Self::Output>, TransitionImpossibleError<Self, Self::Input>> where - Self::Input: Clone, Self: Clone, { - self.clone() - .transition(input.clone()) - .ok_or_else(|| TransitionImpossibleError { - state: self.clone(), - input: input.clone(), - }) - .map(|state| std::mem::replace(self, state).output(input)) + self.clone().transition(input).map(|(s, output)| { + *self = s; + output + }) } } @@ -281,8 +276,8 @@ pub trait StateMachineImpl: Sized { /// An error type that represents that the state transition is impossible given /// the current combination of state and input. pub struct TransitionImpossibleError<S, I> { - state: S, - input: I, + pub state: S, + pub input: I, } impl<S: Debug, I: Debug> fmt::Display for TransitionImpossibleError<S, I> { diff --git a/rust-fsm/tests/circuit_breaker_dsl.rs b/rust-fsm/tests/circuit_breaker_dsl.rs index 61b82fc..0e58486 100644 --- a/rust-fsm/tests/circuit_breaker_dsl.rs +++ b/rust-fsm/tests/circuit_breaker_dsl.rs @@ -49,7 +49,7 @@ fn circit_breaker_dsl() { std::thread::sleep(Duration::new(1, 0)); let mut lock = machine_try.lock().unwrap(); let res = lock.consume(Result::Successful); - assert!(matches!(res, Err(TransitionImpossibleError))); + assert!(matches!(res, Err(_))); assert!(matches!(*lock, CircuitBreaker::Open)); }); diff --git a/rust-fsm/tests/circuit_breaker_dsl_custom_types.rs b/rust-fsm/tests/circuit_breaker_dsl_custom_types.rs index 72c5f34..bdf6829 100644 --- a/rust-fsm/tests/circuit_breaker_dsl_custom_types.rs +++ b/rust-fsm/tests/circuit_breaker_dsl_custom_types.rs @@ -64,7 +64,7 @@ fn circit_breaker_dsl() { std::thread::sleep(Duration::new(1, 0)); let mut lock = machine_try.lock().unwrap(); let res = lock.consume(Input::Successful); - assert!(matches!(res, Err(TransitionImpossibleError))); + assert!(matches!(res, Err(_))); assert!(matches!(*lock, State::Open)); }); |