Finite state machines in rust; bendns fork to add types.
mess with api
bendn 5 months ago
parent 6026db1 · commit 633b2e2
-rw-r--r--rust-fsm-dsl/src/lib.rs43
-rw-r--r--rust-fsm-dsl/src/variant.rs18
-rw-r--r--rust-fsm/src/lib.rs29
-rw-r--r--rust-fsm/tests/circuit_breaker_dsl.rs2
-rw-r--r--rust-fsm/tests/circuit_breaker_dsl_custom_types.rs2
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));
});