Finite state machines in rust; bendns fork to add types.
other changes
bendn 5 months ago
parent b3826f8 · commit 297c4d5
-rw-r--r--doc-example/src/lib.rs2
-rw-r--r--rust-fsm-dsl/src/lib.rs82
-rw-r--r--rust-fsm-dsl/src/parser.rs15
-rw-r--r--rust-fsm-dsl/src/variant.rs123
-rw-r--r--rust-fsm/src/lib.rs10
-rw-r--r--rust-fsm/tests/circuit_breaker_dsl.rs4
-rw-r--r--rust-fsm/tests/circuit_breaker_dsl_custom_types.rs4
-rw-r--r--rust-fsm/tests/simple.rs2
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,