Finite state machines in rust; bendns fork to add types.
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | examples/circuit_breaker_dsl.rs | 16 | ||||
| -rw-r--r-- | examples/simple.rs | 1 | ||||
| -rw-r--r-- | rust_fsm_dsl/src/lib.rs | 13 | ||||
| -rw-r--r-- | rust_fsm_dsl/src/parser.rs | 34 | ||||
| -rw-r--r-- | src/lib.rs | 2 |
6 files changed, 57 insertions, 11 deletions
@@ -46,6 +46,7 @@ The DSL is parsed by the `state_machine` macro. Here is a little example. use rust_fsm::*; state_machine! { + derive(Debug) CircuitBreaker(Closed) Closed(Unsuccessful) => Open [SetupTimer], @@ -60,6 +61,7 @@ state_machine! { This code sample: * Defines a state machine called `CircuitBreaker`; +* Derives the `Debug` trait for it (the `derive` section is optional); * Sets the initial state of this state machine to `Closed`; * Defines state transitions. For example: on receiving the `Successful` input when in the `HalfOpen` state, the machine must move to the `Closed` diff --git a/examples/circuit_breaker_dsl.rs b/examples/circuit_breaker_dsl.rs index 1410416..7756f5a 100644 --- a/examples/circuit_breaker_dsl.rs +++ b/examples/circuit_breaker_dsl.rs @@ -24,8 +24,8 @@ fn main() { { let mut lock = machine.lock().unwrap(); let res = lock.consume(&CircuitBreakerInput::Unsuccessful).unwrap(); - assert_eq!(res, Some(CircuitBreakerOutput::SetupTimer)); - assert_eq!(lock.state(), &CircuitBreakerState::Open); + assert!(matches!(res, Some(CircuitBreakerOutput::SetupTimer))); + assert!(matches!(lock.state(), &CircuitBreakerState::Open)); } // Set up a timer @@ -34,8 +34,8 @@ fn main() { std::thread::sleep(Duration::new(5, 0)); let mut lock = machine_wait.lock().unwrap(); let res = lock.consume(&CircuitBreakerInput::TimerTriggered).unwrap(); - assert_eq!(res, None); - assert_eq!(lock.state(), &CircuitBreakerState::HalfOpen); + assert!(matches!(res, None)); + assert!(matches!(lock.state(), &CircuitBreakerState::HalfOpen)); }); // Try to pass a request when the circuit breaker is still open @@ -44,8 +44,8 @@ fn main() { std::thread::sleep(Duration::new(1, 0)); let mut lock = machine_try.lock().unwrap(); let res = lock.consume(&CircuitBreakerInput::Successful); - assert_eq!(res, Err(())); - assert_eq!(lock.state(), &CircuitBreakerState::Open); + assert!(matches!(res, Err(()))); + assert!(matches!(lock.state(), &CircuitBreakerState::Open)); }); // Test if the circit breaker was actually closed @@ -53,7 +53,7 @@ fn main() { { let mut lock = machine.lock().unwrap(); let res = lock.consume(&CircuitBreakerInput::Successful).unwrap(); - assert_eq!(res, None); - assert_eq!(lock.state(), &CircuitBreakerState::Closed); + assert!(matches!(res, None)); + assert!(matches!(lock.state(), &CircuitBreakerState::Closed)); } } diff --git a/examples/simple.rs b/examples/simple.rs index 81f8b98..3b4048f 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,6 +1,7 @@ use rust_fsm::*; state_machine! { + derive(Debug) Door(Open) Open(Key) => Closed, diff --git a/rust_fsm_dsl/src/lib.rs b/rust_fsm_dsl/src/lib.rs index efa2b05..04e9594 100644 --- a/rust_fsm_dsl/src/lib.rs +++ b/rust_fsm_dsl/src/lib.rs @@ -24,6 +24,12 @@ struct Transition<'a> { pub fn state_machine(tokens: TokenStream) -> TokenStream { let input = parse_macro_input!(tokens as parser::StateMachineDef); + let derives = if let Some(derives) = input.derives { + quote! { #[derive(#(#derives,)*)] } + } else { + quote! {} + }; + if input.transitions.is_empty() { let output = quote! { compile_error!("rust-fsm: at least one state transition must be provided"); @@ -82,7 +88,7 @@ pub fn state_machine(tokens: TokenStream) -> TokenStream { let (outputs_repr, outputs_type, output_impl) = if !outputs.is_empty() { let outputs_type_name = Ident::new(&format!("{}Output", struct_name), struct_name.span()); let outputs_repr = quote! { - #[derive(Debug, PartialEq)] + #derives #visibility enum #outputs_type_name { #(#outputs),* } @@ -116,14 +122,15 @@ pub fn state_machine(tokens: TokenStream) -> TokenStream { }; let output = quote! { + #derives #visibility struct #struct_name; - #[derive(Debug, PartialEq)] + #derives #visibility enum #states_enum_name { #(#states),* } - #[derive(Debug, PartialEq)] + #derives #visibility enum #inputs_enum_name { #(#inputs),* } diff --git a/rust_fsm_dsl/src/parser.rs b/rust_fsm_dsl/src/parser.rs index 0eb3f7c..696fa84 100644 --- a/rust_fsm_dsl/src/parser.rs +++ b/rust_fsm_dsl/src/parser.rs @@ -5,6 +5,10 @@ use syn::{ Ident, Token, Visibility, }; +mod kw { + syn::custom_keyword!(derive); +} + /// The output of a state transition pub struct Output(Option<Ident>); @@ -101,6 +105,32 @@ impl Parse for TransitionDef { } } +struct Derives { + derives: Option<Vec<Ident>>, +} + +impl Parse for Derives { + fn parse(input: ParseStream) -> Result<Self> { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::derive) { + let kw_derive = input.parse::<kw::derive>()?; + let entries_content; + parenthesized!(entries_content in input); + let entries: Vec<_> = entries_content + .parse_terminated::<_, Token![,]>(Ident::parse)? + .into_iter() + .collect(); + if entries.is_empty() { + return Err(Error::new_spanned(kw_derive, "Derive list cannot be empty")); + } + return Ok(Derives { + derives: Some(entries), + }); + } + Ok(Derives { derives: None }) + } +} + /// Parses the whole state machine definition in the following form (example): /// /// ```rust,ignore @@ -121,10 +151,13 @@ pub struct StateMachineDef { pub name: Ident, pub initial_state: Ident, pub transitions: Vec<TransitionDef>, + pub derives: Option<Vec<Ident>>, } impl Parse for StateMachineDef { fn parse(input: ParseStream) -> Result<Self> { + let Derives { derives } = input.parse()?; + let visibility = input.parse()?; let name = input.parse()?; @@ -142,6 +175,7 @@ impl Parse for StateMachineDef { name, initial_state, transitions, + derives, }) } } @@ -42,6 +42,7 @@ //! use rust_fsm::*; //! //! state_machine! { +//! derive(Debug) //! CircuitBreaker(Closed) //! //! Closed(Unsuccessful) => Open [SetupTimer], @@ -56,6 +57,7 @@ //! This code sample: //! //! * Defines a state machine called `CircuitBreaker`; +//! * Derives the `Debug` trait for it (the `derive` section is optional); //! * Sets the initial state of this state machine to `Closed`; //! * Defines state transitions. For example: on receiving the `Successful` //! input when in the `HalfOpen` state, the machine must move to the `Closed` |