From 4f3fe77fab83f54ae5e6dc7fb85f0b307608ca44 Mon Sep 17 00:00:00 2001 From: Martin Broers Date: Tue, 11 Apr 2023 19:25:47 +0200 Subject: [PATCH 1/6] Added on_entry and on_exit states This patch adds functions which are executed when the statemachine transitions from a state A to a state B. First on_exit_state_a is called (which is defined as an empty trait implementation which can be overridden) followed by a call to on_entry_state_b. The on_entry and on_exit functions can be implemented in the Statemachine Context trait implementation or ignored altogether, since the standard implementation is empty. Signed-off-by: Martin Broers --- examples/on_entry_on_exit.rs | 43 ++++++++++++++++++++++++++++++ macros/Cargo.toml | 1 + macros/src/codegen.rs | 37 ++++++++++++++++++++++++- macros/src/parser/mod.rs | 3 +++ macros/src/parser/state_machine.rs | 39 +++++++++++++++++++-------- 5 files changed, 111 insertions(+), 12 deletions(-) create mode 100644 examples/on_entry_on_exit.rs diff --git a/examples/on_entry_on_exit.rs b/examples/on_entry_on_exit.rs new file mode 100644 index 0000000..745aa9f --- /dev/null +++ b/examples/on_entry_on_exit.rs @@ -0,0 +1,43 @@ +//! An example of using state data to propagate events (See issue-17) + +#![deny(missing_docs)] + +use smlang::statemachine; + +statemachine! { + name: OnEntryExample, + transitions: { + *D0 + ToD1 = D1, + D1 + ToD2 = D2, + }, + generate_entry_exit_states: true, +} + +/// Context +pub struct Context { + exited_d0: bool, + entered_d1: bool, +} + +impl OnEntryExampleStateMachineContext for Context { + fn on_exit_d0(&mut self) { + self.exited_d0 = true; + } + fn on_entry_d1(&mut self) { + self.entered_d1 = true; + } +} + +fn main() { + let mut sm = OnEntryExampleStateMachine::new(Context { + exited_d0: false, + entered_d1: false, + }); + + // first event starts the dominos + let _ = sm.process_event(OnEntryExampleEvents::ToD1).unwrap(); + + assert!(matches!(sm.state(), Ok(&OnEntryExampleStates::D1))); + assert!(sm.context().exited_d0); + assert!(sm.context().entered_d1); +} diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 3c77719..2a4ae24 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -13,6 +13,7 @@ readme = "../README.md" [dependencies] quote = "1" proc-macro2 = "1" +string_morph = "0.1.0" [dependencies.syn] features = ["extra-traits", "full"] diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index e1d864d..af5683e 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -19,6 +19,8 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { let state_machine_context_type_name = format_ident!("{sm_name}StateMachineContext", span = sm_name_span); + let generate_entry_exit_states = sm.generate_entry_exit_states; + // Get only the unique states let mut state_list: Vec<_> = sm.states.values().collect(); state_list.sort_by_key(|state| state.to_string()); @@ -252,6 +254,8 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { let mut guard_list = proc_macro2::TokenStream::new(); let mut action_list = proc_macro2::TokenStream::new(); + + let mut entry_list = proc_macro2::TokenStream::new(); for (state, event_mappings) in transitions.iter() { // create the state data token stream let state_data = match sm.state_data.data_types.get(state) { @@ -259,6 +263,18 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { Some(st) => quote! { state_data: &#st, }, None => quote! {}, }; + if generate_entry_exit_states { + let entry_ident = format_ident!("on_entry_{}", string_morph::to_snake_case(state)); + entry_list.extend(quote! { + #[allow(missing_docs)] + fn #entry_ident(&mut self){} + }); + let exit_ident = format_ident!("on_exit_{}", string_morph::to_snake_case(state)); + entry_list.extend(quote! { + #[allow(missing_docs)] + fn #exit_ident(&mut self){} + }); + }; for (event, event_mapping) in event_mappings { for transition in &event_mapping.transitions { @@ -401,7 +417,7 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { .zip(in_states.iter().zip(out_states.iter().zip(guard_action_parameters.iter().zip(guard_action_ref_parameters.iter())))), ) .map( - |(guards, (actions, (_, (out_states, (guard_action_parameters, guard_action_ref_parameters)))))| { + |(guards, (actions, (in_state, (out_states, (guard_action_parameters, guard_action_ref_parameters)))))| { guards .iter() .zip( @@ -413,6 +429,22 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { let streams: Vec = guard.iter() .zip(action.iter().zip(out_state)).map(|(guard, (action,out_state))| { + + let binding = out_state.to_string(); + let out_state_string = &binding.split('(').collect::>()[0]; + let entry_ident = format_ident!("on_entry_{}",string_morph::to_snake_case(out_state_string )); + let binding = in_state.to_string(); + let in_state_string = &binding.split('(').collect::>()[0]; + let exit_ident = format_ident!("on_exit_{}",string_morph::to_snake_case(in_state_string)); + let entry_exit_states = if generate_entry_exit_states { + quote! { + self.context_mut().#exit_ident(); + self.context_mut().#entry_ident(); + } + } else { + quote! { } + }; + let (is_async_action,action_code) = generate_action(action, &temporary_context_call, g_a_param); is_async_state_machine |= is_async_action; if let Some(expr) = guard { // Guarded transition @@ -464,6 +496,7 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { #guard_result self.context.log_guard(stringify!(#guard_expression), &guard_result); if guard_result.map_err(#error_type_name::GuardFailed)? { + #entry_exit_states #action_code let out_state = #states_type_name::#out_state; self.context.log_state_change(&out_state); @@ -474,6 +507,7 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { } else { // Unguarded transition quote!{ #action_code + #entry_exit_states let out_state = #states_type_name::#out_state; self.context.log_state_change(&out_state); self.state = Some(out_state); @@ -561,6 +595,7 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { #guard_error #guard_list #action_list + #entry_list /// Called at the beginning of a state machine's `process_event()`. No-op by /// default but can be overridden in implementations of a state machine's diff --git a/macros/src/parser/mod.rs b/macros/src/parser/mod.rs index ea65567..23d0dd4 100644 --- a/macros/src/parser/mod.rs +++ b/macros/src/parser/mod.rs @@ -56,6 +56,8 @@ pub struct ParsedStateMachine { pub events: HashMap, pub event_data: DataDefinitions, pub states_events_mapping: HashMap>, + + pub generate_entry_exit_states: bool, } // helper function for adding a transition to a transition event map @@ -230,6 +232,7 @@ impl ParsedStateMachine { events, event_data, states_events_mapping, + generate_entry_exit_states: sm.generate_entry_exit_states, }) } } diff --git a/macros/src/parser/state_machine.rs b/macros/src/parser/state_machine.rs index 4e7cd19..dfbb187 100644 --- a/macros/src/parser/state_machine.rs +++ b/macros/src/parser/state_machine.rs @@ -9,6 +9,7 @@ pub struct StateMachine { pub name: Option, pub derive_states: Vec, pub derive_events: Vec, + pub generate_entry_exit_states: bool, } impl StateMachine { @@ -20,6 +21,7 @@ impl StateMachine { name: None, derive_states: Vec::new(), derive_events: Vec::new(), + generate_entry_exit_states: false, } } @@ -78,7 +80,6 @@ impl parse::Parse for StateMachine { if custom_guard_error.value { statemachine.custom_guard_error = true } - } "temporary_context" => { input.parse::()?; @@ -102,48 +103,64 @@ impl parse::Parse for StateMachine { // Store the temporary context type statemachine.temporary_context_type = Some(temporary_context_type); - } - "name" =>{ + "name" => { input.parse::()?; statemachine.name = Some(input.parse::()?); - }, + } "derive_states" => { input.parse::()?; if input.peek(token::Bracket) { let content; bracketed!(content in input); - loop{ + loop { if content.is_empty() { break; }; - let trait_ = content.parse::()?; + let trait_ = content.parse::()?; statemachine.derive_states.push(trait_); if content.parse::().is_err() { break; }; } } - }, + } "derive_events" => { input.parse::()?; let content; bracketed!(content in input); - loop{ + loop { if content.is_empty() { break; }; - let trait_ = content.parse::()?; + let trait_ = content.parse::()?; statemachine.derive_events.push(trait_); if content.parse::().is_err() { break; }; } - }, + } + "generate_entry_exit_states" => { + input.parse::()?; + let generate_entry_exit_states: syn::LitBool = input.parse()?; + if generate_entry_exit_states.value { + statemachine.generate_entry_exit_states = true + } + } keyword => { return Err(parse::Error::new( input.span(), - format!("Unknown keyword {}. Support keywords: [\"name\", \"transitions\", \"temporary_context\", \"custom_guard_error\", \"derive_states\", \"derive_events\"]", keyword) + format!( + "Unknown keyword {}. Support keywords: [\"name\", + \"transitions\", + \"temporary_context\", + \"custom_guard_error\", + \"derive_states\", + \"derive_events\", + \"generate_entry_exit_states\" + ]", + keyword + ), )) } } From 5a751a088589ffc964c9f71f32bc957be36ab239 Mon Sep 17 00:00:00 2001 From: Martin Broers Date: Wed, 12 Apr 2023 11:05:22 +0200 Subject: [PATCH 2/6] Added callback for succesfull transition This patch adds a callback for succesfull transitions. This function can be used to implement a logging service of some sorts for the state machine. Signed-off-by: Martin Broers --- examples/transition_callback.rs | 38 ++++++++++++++++++++++++++++++ macros/src/codegen.rs | 16 ++++++++++++- macros/src/parser/mod.rs | 2 ++ macros/src/parser/state_machine.rs | 12 +++++++++- 4 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 examples/transition_callback.rs diff --git a/examples/transition_callback.rs b/examples/transition_callback.rs new file mode 100644 index 0000000..6722315 --- /dev/null +++ b/examples/transition_callback.rs @@ -0,0 +1,38 @@ +//! An example of using state data to propagate events (See issue-17) + +#![deny(missing_docs)] + +use std::sync::{Arc, Mutex}; + +use smlang::statemachine; + +statemachine! { + generate_transition_callback: true, + transitions: { + *D0 + ToD1 = D1, + D1 + ToD2 = D2, + }, +} + +/// Context +pub struct Context { + transition_called: Arc>, +} + +impl StateMachineContext for Context { + fn transition_callback(&self, _state: &Option) { + *self.transition_called.lock().unwrap() = true; + } +} + +fn main() { + let mut sm = StateMachine::new(Context { + transition_called: Arc::new(Mutex::new(false)), + }); + + // first event starts the dominos + let _ = sm.process_event(Events::ToD1).unwrap(); + + assert!(matches!(sm.state(), Ok(&States::D1))); + assert!(*sm.context().transition_called.lock().unwrap()); +} diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index af5683e..d1e3883 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -20,6 +20,7 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { format_ident!("{sm_name}StateMachineContext", span = sm_name_span); let generate_entry_exit_states = sm.generate_entry_exit_states; + let generate_transition_callback = sm.generate_transition_callback; // Get only the unique states let mut state_list: Vec<_> = sm.states.values().collect(); @@ -586,6 +587,13 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { let derive_states_list = &sm.derive_states; let derive_events_list = &sm.derive_events; + let transition_callback = if generate_transition_callback { + quote!( + self.context().transition_callback(&self.state); + ) + } else { + quote!() + }; // Build the states and events output quote! { /// This trait outlines the guards and actions that need to be implemented for the state @@ -595,7 +603,6 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { #guard_error #guard_list #action_list - #entry_list /// Called at the beginning of a state machine's `process_event()`. No-op by /// default but can be overridden in implementations of a state machine's @@ -616,6 +623,11 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { /// `process_event()`. No-op by default but can be overridden in implementations /// of a state machine's `StateMachineContext` trait. fn log_state_change(&self, new_state: & #states_type_name) {} + + #entry_list + + #[allow(missing_docs)] + fn transition_callback(&self, new_state: &Option<#states_type_name>) {} } /// List of auto-generated states. @@ -714,6 +726,8 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { #states_type_name::#in_states => match event { #(#events_type_name::#events => { #code_blocks + #transition_callback + #[allow(unreachable_code)] { // none of the guarded or non-guarded transitions occurred, diff --git a/macros/src/parser/mod.rs b/macros/src/parser/mod.rs index 23d0dd4..a4c2ae2 100644 --- a/macros/src/parser/mod.rs +++ b/macros/src/parser/mod.rs @@ -58,6 +58,7 @@ pub struct ParsedStateMachine { pub states_events_mapping: HashMap>, pub generate_entry_exit_states: bool, + pub generate_transition_callback: bool, } // helper function for adding a transition to a transition event map @@ -233,6 +234,7 @@ impl ParsedStateMachine { event_data, states_events_mapping, generate_entry_exit_states: sm.generate_entry_exit_states, + generate_transition_callback: sm.generate_transition_callback, }) } } diff --git a/macros/src/parser/state_machine.rs b/macros/src/parser/state_machine.rs index dfbb187..8dc5674 100644 --- a/macros/src/parser/state_machine.rs +++ b/macros/src/parser/state_machine.rs @@ -10,6 +10,7 @@ pub struct StateMachine { pub derive_states: Vec, pub derive_events: Vec, pub generate_entry_exit_states: bool, + pub generate_transition_callback: bool, } impl StateMachine { @@ -22,6 +23,7 @@ impl StateMachine { derive_states: Vec::new(), derive_events: Vec::new(), generate_entry_exit_states: false, + generate_transition_callback: false, } } @@ -147,6 +149,13 @@ impl parse::Parse for StateMachine { statemachine.generate_entry_exit_states = true } } + "generate_transition_callback" => { + input.parse::()?; + let generate_transition_callback: syn::LitBool = input.parse()?; + if generate_transition_callback.value { + statemachine.generate_transition_callback = true + } + } keyword => { return Err(parse::Error::new( input.span(), @@ -157,7 +166,8 @@ impl parse::Parse for StateMachine { \"custom_guard_error\", \"derive_states\", \"derive_events\", - \"generate_entry_exit_states\" + \"generate_entry_exit_states\", + \"generate_transition_callback\", ]", keyword ), From 2785f10fb724cca4835ddd8c7a0186751967b149 Mon Sep 17 00:00:00 2001 From: Martin Broers Date: Thu, 7 Mar 2024 12:09:03 +0100 Subject: [PATCH 3/6] Added entry and exit functions By using `>` and `<` we can now define entry functions and exit functions on states. These should be defined once and will apply to all transitions interacting with either entering or exiting the state. Signed-off-by: Martin Broers --- README.md | 21 +++++++++++ examples/on_entry_on_exit.rs | 49 +++++++++++++++++++------ macros/src/codegen.rs | 32 ++++++++++++----- macros/src/parser/event.rs | 3 ++ macros/src/parser/mod.rs | 58 +++++++++++++++++++++++++++++- macros/src/parser/state_machine.rs | 15 +++++++- macros/src/parser/transition.rs | 32 ++++++++++++++++- 7 files changed, 187 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 55f72e9..84073df 100644 --- a/README.md +++ b/README.md @@ -260,6 +260,27 @@ statemachine!{ This example is available in `ex3.rs`. +### Using entry and exit functions in transitions + +DSL implementation: + +```rust +statemachine!{ + transitions: { + *State1 + Event1 = State2, + State2 < exit_state_2 + Event2 = State1, + State1 > enter_state_3 + Event3 = State3, + State2 + Event3 = State3, + } +} +``` +For all transitions entering State3, the function `enter_state_3` will be +called. For all transitions exiting State2, the function `exit_state_2` will be +called, in the right order, so first the `exit` function prior to the `entry` +function. + +An example is available in `on_entry_on_exit`. + ## Helpers ### Auto-derive certain traits for states and events diff --git a/examples/on_entry_on_exit.rs b/examples/on_entry_on_exit.rs index 745aa9f..f01a564 100644 --- a/examples/on_entry_on_exit.rs +++ b/examples/on_entry_on_exit.rs @@ -6,38 +6,65 @@ use smlang::statemachine; statemachine! { name: OnEntryExample, + generate_entry_exit_states: true, transitions: { - *D0 + ToD1 = D1, - D1 + ToD2 = D2, + *D0 > exit_d0 + ToD1 = D1, + D0 + ToD3 = D3, + D1 < enter_d1 + ToD2 = D2, + D2 + ToD1 = D1, + D1 + ToD0 = D0, }, - generate_entry_exit_states: true, } /// Context pub struct Context { - exited_d0: bool, - entered_d1: bool, + exited_d0: i32, + entered_d1: i32, } impl OnEntryExampleStateMachineContext for Context { fn on_exit_d0(&mut self) { - self.exited_d0 = true; + self.exited_d0 += 1; } fn on_entry_d1(&mut self) { - self.entered_d1 = true; + self.entered_d1 += 1; } } fn main() { let mut sm = OnEntryExampleStateMachine::new(Context { - exited_d0: false, - entered_d1: false, + exited_d0: 0, + entered_d1: 0, }); // first event starts the dominos let _ = sm.process_event(OnEntryExampleEvents::ToD1).unwrap(); assert!(matches!(sm.state(), Ok(&OnEntryExampleStates::D1))); - assert!(sm.context().exited_d0); - assert!(sm.context().entered_d1); + assert_eq!(sm.context().exited_d0, 1); + assert_eq!(sm.context().entered_d1, 1); + + let _ = sm.process_event(OnEntryExampleEvents::ToD2).unwrap(); + + assert!(matches!(sm.state(), Ok(&OnEntryExampleStates::D2))); + assert_eq!(sm.context().exited_d0, 1); + assert_eq!(sm.context().entered_d1, 1); + + let _ = sm.process_event(OnEntryExampleEvents::ToD1).unwrap(); + + assert!(matches!(sm.state(), Ok(&OnEntryExampleStates::D1))); + assert_eq!(sm.context().exited_d0, 1); + assert_eq!(sm.context().entered_d1, 2); + + let _ = sm.process_event(OnEntryExampleEvents::ToD0).unwrap(); + + assert!(matches!(sm.state(), Ok(&OnEntryExampleStates::D0))); + assert_eq!(sm.context().exited_d0, 1); + assert_eq!(sm.context().entered_d1, 2); + + let _ = sm.process_event(OnEntryExampleEvents::ToD3).unwrap(); + + assert!(matches!(sm.state(), Ok(&OnEntryExampleStates::D3))); + assert_eq!(sm.context().exited_d0, 2); + assert_eq!(sm.context().entered_d1, 2); } diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index d1e3883..895fba4 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -22,6 +22,9 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { let generate_entry_exit_states = sm.generate_entry_exit_states; let generate_transition_callback = sm.generate_transition_callback; + let entry_fns = &sm.entry_functions; + let exit_fns = &sm.exit_functions; + // Get only the unique states let mut state_list: Vec<_> = sm.states.values().collect(); state_list.sort_by_key(|state| state.to_string()); @@ -114,11 +117,6 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { }) .collect(); - // println!("sm: {:#?}", sm); - // println!("in_states: {:#?}", in_states); - // println!("events: {:#?}", events); - // println!("transitions: {:#?}", transitions); - // Map guards, actions and output states into code blocks let guards: Vec> = transitions .values() @@ -257,6 +255,21 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { let mut action_list = proc_macro2::TokenStream::new(); let mut entry_list = proc_macro2::TokenStream::new(); + + let mut entries_exits = proc_macro2::TokenStream::new(); + for ident in entry_fns.values() { + entries_exits.extend(quote! { + #[allow(missing_docs)] + fn #ident(&mut self){} + }); + } + for ident in exit_fns.values() { + entries_exits.extend(quote! { + #[allow(missing_docs)] + fn #ident(&mut self){} + }); + } + for (state, event_mappings) in transitions.iter() { // create the state data token stream let state_data = match sm.state_data.data_types.get(state) { @@ -497,10 +510,10 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { #guard_result self.context.log_guard(stringify!(#guard_expression), &guard_result); if guard_result.map_err(#error_type_name::GuardFailed)? { - #entry_exit_states #action_code let out_state = #states_type_name::#out_state; self.context.log_state_change(&out_state); + #entry_exit_states self.state = Some(out_state); return self.state() } @@ -508,9 +521,9 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { } else { // Unguarded transition quote!{ #action_code - #entry_exit_states let out_state = #states_type_name::#out_state; self.context.log_state_change(&out_state); + #entry_exit_states self.state = Some(out_state); return self.state(); } @@ -603,6 +616,9 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { #guard_error #guard_list #action_list + #entry_list + #entries_exits + /// Called at the beginning of a state machine's `process_event()`. No-op by /// default but can be overridden in implementations of a state machine's @@ -624,8 +640,6 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { /// of a state machine's `StateMachineContext` trait. fn log_state_change(&self, new_state: & #states_type_name) {} - #entry_list - #[allow(missing_docs)] fn transition_callback(&self, new_state: &Option<#states_type_name>) {} } diff --git a/macros/src/parser/event.rs b/macros/src/parser/event.rs index 3f06e77..d90d898 100644 --- a/macros/src/parser/event.rs +++ b/macros/src/parser/event.rs @@ -20,6 +20,9 @@ pub struct Transition { pub guard: Option, pub action: Option, pub out_state: Ident, + + pub entry_fn: Option, + pub exit_fn: Option, } impl parse::Parse for Event { diff --git a/macros/src/parser/mod.rs b/macros/src/parser/mod.rs index a4c2ae2..b244038 100644 --- a/macros/src/parser/mod.rs +++ b/macros/src/parser/mod.rs @@ -20,6 +20,13 @@ use syn::{parse, Ident, Type}; use transition::StateTransition; pub type TransitionMap = HashMap>; +#[derive(Debug, Clone)] +pub struct EntryIdent { + pub ident: Ident, + pub state: Vec, + pub is_async: bool, +} + #[derive(Debug, Clone)] pub struct AsyncIdent { pub ident: Ident, @@ -59,6 +66,8 @@ pub struct ParsedStateMachine { pub generate_entry_exit_states: bool, pub generate_transition_callback: bool, + pub entry_functions: HashMap, + pub exit_functions: HashMap, } // helper function for adding a transition to a transition event map @@ -66,11 +75,16 @@ fn add_transition( transition: &StateTransition, transition_map: &mut TransitionMap, state_data: &DataDefinitions, + entry_fns: &HashMap, + exit_fns: &HashMap, ) -> Result<(), parse::Error> { let p = transition_map .get_mut(&transition.in_state.ident.to_string()) .unwrap(); + let entry_fn = entry_fns.get(&transition.in_state.ident.clone()); + let exit_fn = exit_fns.get(&transition.in_state.ident.clone()); + match p.entry(transition.event.ident.to_string()) { hash_map::Entry::Vacant(entry) => { let mapping = EventMapping { @@ -80,6 +94,8 @@ fn add_transition( guard: transition.guard.clone(), action: transition.action.clone(), out_state: transition.out_state.ident.clone(), + entry_fn: entry_fn.cloned(), + exit_fn: exit_fn.cloned(), }], }; entry.insert(mapping); @@ -90,6 +106,8 @@ fn add_transition( guard: transition.guard.clone(), action: transition.action.clone(), out_state: transition.out_state.ident.clone(), + entry_fn: entry_fn.cloned(), + exit_fn: exit_fn.cloned(), }); } } @@ -138,6 +156,33 @@ impl ParsedStateMachine { let mut event_data = DataDefinitions::new(); let mut states_events_mapping = TransitionMap::new(); + let mut states_with_exit_function = HashMap::new(); + let mut states_with_entry_function = HashMap::new(); + + fn add_entry(map: &mut HashMap, vec: &Vec) -> parse::Result<()> { + for identifier in vec { + for input_state in &identifier.state { + if let Some(existing_identifier) = + map.insert(input_state.ident.clone(), identifier.ident.clone()) + { + if identifier.ident != existing_identifier { + println!( + "entry_state: {:?}, state.ident: {:?}", + identifier.ident, input_state.ident + ); + return Err(parse::Error::new( + Span::call_site(), + "Different entry or exit functions defined for state", + )); + } + } + } + } + Ok(()) + } + add_entry(&mut states_with_entry_function, &sm.entries)?; + add_entry(&mut states_with_exit_function, &sm.exits)?; + for transition in sm.transitions.iter() { // Collect states let in_state_name = transition.in_state.ident.to_string(); @@ -203,6 +248,8 @@ impl ParsedStateMachine { &wildcard_transition, &mut states_events_mapping, &state_data, + &states_with_entry_function, + &states_with_exit_function, )?; transition_added = true; @@ -217,7 +264,13 @@ impl ParsedStateMachine { )); } } else { - add_transition(transition, &mut states_events_mapping, &state_data)?; + add_transition( + transition, + &mut states_events_mapping, + &state_data, + &states_with_entry_function, + &states_with_exit_function, + )?; } } @@ -235,6 +288,9 @@ impl ParsedStateMachine { states_events_mapping, generate_entry_exit_states: sm.generate_entry_exit_states, generate_transition_callback: sm.generate_transition_callback, + + entry_functions: states_with_entry_function, + exit_functions: states_with_exit_function, }) } } diff --git a/macros/src/parser/state_machine.rs b/macros/src/parser/state_machine.rs index 8dc5674..48f9e9c 100644 --- a/macros/src/parser/state_machine.rs +++ b/macros/src/parser/state_machine.rs @@ -1,4 +1,7 @@ -use super::transition::{StateTransition, StateTransitions}; +use super::{ + transition::{StateTransition, StateTransitions}, + EntryIdent, +}; use syn::{braced, bracketed, parse, spanned::Spanned, token, Ident, Token, Type}; #[derive(Debug)] @@ -11,6 +14,8 @@ pub struct StateMachine { pub derive_events: Vec, pub generate_entry_exit_states: bool, pub generate_transition_callback: bool, + pub entries: Vec, + pub exits: Vec, } impl StateMachine { @@ -24,6 +29,8 @@ impl StateMachine { derive_events: Vec::new(), generate_entry_exit_states: false, generate_transition_callback: false, + entries: Vec::new(), + exits: Vec::new(), } } @@ -38,6 +45,12 @@ impl StateMachine { }; self.transitions.push(transition); } + if let Some(entry) = transitions.entry { + self.entries.push(entry); + } + if let Some(exit) = transitions.exit { + self.exits.push(exit); + } } } diff --git a/macros/src/parser/transition.rs b/macros/src/parser/transition.rs index fe5a248..efe2ef1 100644 --- a/macros/src/parser/transition.rs +++ b/macros/src/parser/transition.rs @@ -1,7 +1,7 @@ -use super::event::Event; use super::input_state::InputState; use super::output_state::OutputState; use super::AsyncIdent; +use super::{event::Event, EntryIdent}; use proc_macro2::TokenStream; use quote::quote; use std::fmt; @@ -22,6 +22,8 @@ pub struct StateTransitions { pub event: Event, pub guard: Option, pub action: Option, + pub entry: Option, + pub exit: Option, pub out_state: OutputState, } @@ -49,6 +51,32 @@ impl parse::Parse for StateTransitions { } } + // Possible extry function + let entry = if input.parse::().is_ok() { + let is_async = input.parse::().is_ok(); + let entry_function: Ident = input.parse()?; + Some(EntryIdent { + ident: entry_function, + state: in_states.clone(), + is_async, + }) + } else { + None + }; + let exit = if input.parse::]>().is_ok() { + let is_async = input.parse::().is_ok(); + let exit_function: Ident = match input.parse() { + Ok(v) => v, + Err(e) => panic!("Could not parse exit token: {:?}", e), + }; + Some(EntryIdent { + ident: exit_function, + state: in_states.clone(), + is_async, + }) + } else { + None + }; // Event let event: Event = input.parse()?; @@ -81,6 +109,8 @@ impl parse::Parse for StateTransitions { guard, action, out_state, + entry, + exit, }) } } From a1276501995edc2ef38fe8bd64d761eb71a625a2 Mon Sep 17 00:00:00 2001 From: Martin Broers Date: Sun, 23 Jun 2024 14:23:12 +0200 Subject: [PATCH 4/6] Added generic on_entry_on_exit flag Signed-off-by: Martin Broers --- README.md | 5 ++ examples/on_entry_on_exit_generic.rs | 70 ++++++++++++++++++++++++++++ macros/src/codegen.rs | 13 +++--- macros/src/parser/event.rs | 3 -- macros/src/parser/mod.rs | 23 +-------- 5 files changed, 82 insertions(+), 32 deletions(-) create mode 100644 examples/on_entry_on_exit_generic.rs diff --git a/README.md b/README.md index 84073df..31251fc 100644 --- a/README.md +++ b/README.md @@ -281,6 +281,11 @@ function. An example is available in `on_entry_on_exit`. +There is also a generic flag available, `generate_on_entry_on_exit`, which will +generate for all states in the statemachine an entry and an exit function. If +they are not used, they will be optimized away by the compiler. An example be +found in `on_entry_on_exit_generic`. + ## Helpers ### Auto-derive certain traits for states and events diff --git a/examples/on_entry_on_exit_generic.rs b/examples/on_entry_on_exit_generic.rs new file mode 100644 index 0000000..bde15e5 --- /dev/null +++ b/examples/on_entry_on_exit_generic.rs @@ -0,0 +1,70 @@ +//! An example of using state data to propagate events (See issue-17) + +#![deny(missing_docs)] + +use smlang::statemachine; + +statemachine! { + name: OnEntryExample, + generate_entry_exit_states: true, + transitions: { + *D0 < exit_d0 + ToD1 = D1, + D0 + ToD3 = D3, + D1 + ToD2 = D2, + D2 + ToD1 = D1, + D1 + ToD0 = D0, + }, +} + +/// Context +pub struct Context { + exited_d0: i32, + entered_d1: i32, +} + +impl OnEntryExampleStateMachineContext for Context { + fn on_exit_d0(&mut self) { + self.exited_d0 += 1; + } + fn on_entry_d1(&mut self) { + self.entered_d1 += 1; + } +} + +fn main() { + let mut sm = OnEntryExampleStateMachine::new(Context { + exited_d0: 0, + entered_d1: 0, + }); + + // first event starts the dominos + let _ = sm.process_event(OnEntryExampleEvents::ToD1).unwrap(); + + assert!(matches!(sm.state(), Ok(&OnEntryExampleStates::D1))); + assert_eq!(sm.context().exited_d0, 1); + assert_eq!(sm.context().entered_d1, 1); + + let _ = sm.process_event(OnEntryExampleEvents::ToD2).unwrap(); + + assert!(matches!(sm.state(), Ok(&OnEntryExampleStates::D2))); + assert_eq!(sm.context().exited_d0, 1); + assert_eq!(sm.context().entered_d1, 1); + + let _ = sm.process_event(OnEntryExampleEvents::ToD1).unwrap(); + + assert!(matches!(sm.state(), Ok(&OnEntryExampleStates::D1))); + assert_eq!(sm.context().exited_d0, 1); + assert_eq!(sm.context().entered_d1, 2); + + let _ = sm.process_event(OnEntryExampleEvents::ToD0).unwrap(); + + assert!(matches!(sm.state(), Ok(&OnEntryExampleStates::D0))); + assert_eq!(sm.context().exited_d0, 1); + assert_eq!(sm.context().entered_d1, 2); + + let _ = sm.process_event(OnEntryExampleEvents::ToD3).unwrap(); + + assert!(matches!(sm.state(), Ok(&OnEntryExampleStates::D3))); + assert_eq!(sm.context().exited_d0, 2); + assert_eq!(sm.context().entered_d1, 2); +} diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index 895fba4..b742db3 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -22,8 +22,8 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { let generate_entry_exit_states = sm.generate_entry_exit_states; let generate_transition_callback = sm.generate_transition_callback; - let entry_fns = &sm.entry_functions; - let exit_fns = &sm.exit_functions; + let mut entry_fns = sm.entry_functions.clone(); + let mut exit_fns = sm.exit_functions.clone(); // Get only the unique states let mut state_list: Vec<_> = sm.states.values().collect(); @@ -254,8 +254,6 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { let mut guard_list = proc_macro2::TokenStream::new(); let mut action_list = proc_macro2::TokenStream::new(); - let mut entry_list = proc_macro2::TokenStream::new(); - let mut entries_exits = proc_macro2::TokenStream::new(); for ident in entry_fns.values() { entries_exits.extend(quote! { @@ -279,15 +277,17 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { }; if generate_entry_exit_states { let entry_ident = format_ident!("on_entry_{}", string_morph::to_snake_case(state)); - entry_list.extend(quote! { + entries_exits.extend(quote! { #[allow(missing_docs)] fn #entry_ident(&mut self){} }); + entry_fns.insert(format_ident!("{}", state), entry_ident); let exit_ident = format_ident!("on_exit_{}", string_morph::to_snake_case(state)); - entry_list.extend(quote! { + entries_exits.extend(quote! { #[allow(missing_docs)] fn #exit_ident(&mut self){} }); + exit_fns.insert(format_ident!("{}", state), exit_ident); }; for (event, event_mapping) in event_mappings { @@ -616,7 +616,6 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { #guard_error #guard_list #action_list - #entry_list #entries_exits diff --git a/macros/src/parser/event.rs b/macros/src/parser/event.rs index d90d898..3f06e77 100644 --- a/macros/src/parser/event.rs +++ b/macros/src/parser/event.rs @@ -20,9 +20,6 @@ pub struct Transition { pub guard: Option, pub action: Option, pub out_state: Ident, - - pub entry_fn: Option, - pub exit_fn: Option, } impl parse::Parse for Event { diff --git a/macros/src/parser/mod.rs b/macros/src/parser/mod.rs index b244038..418b9ba 100644 --- a/macros/src/parser/mod.rs +++ b/macros/src/parser/mod.rs @@ -75,16 +75,11 @@ fn add_transition( transition: &StateTransition, transition_map: &mut TransitionMap, state_data: &DataDefinitions, - entry_fns: &HashMap, - exit_fns: &HashMap, ) -> Result<(), parse::Error> { let p = transition_map .get_mut(&transition.in_state.ident.to_string()) .unwrap(); - let entry_fn = entry_fns.get(&transition.in_state.ident.clone()); - let exit_fn = exit_fns.get(&transition.in_state.ident.clone()); - match p.entry(transition.event.ident.to_string()) { hash_map::Entry::Vacant(entry) => { let mapping = EventMapping { @@ -94,8 +89,6 @@ fn add_transition( guard: transition.guard.clone(), action: transition.action.clone(), out_state: transition.out_state.ident.clone(), - entry_fn: entry_fn.cloned(), - exit_fn: exit_fn.cloned(), }], }; entry.insert(mapping); @@ -106,8 +99,6 @@ fn add_transition( guard: transition.guard.clone(), action: transition.action.clone(), out_state: transition.out_state.ident.clone(), - entry_fn: entry_fn.cloned(), - exit_fn: exit_fn.cloned(), }); } } @@ -166,10 +157,6 @@ impl ParsedStateMachine { map.insert(input_state.ident.clone(), identifier.ident.clone()) { if identifier.ident != existing_identifier { - println!( - "entry_state: {:?}, state.ident: {:?}", - identifier.ident, input_state.ident - ); return Err(parse::Error::new( Span::call_site(), "Different entry or exit functions defined for state", @@ -248,8 +235,6 @@ impl ParsedStateMachine { &wildcard_transition, &mut states_events_mapping, &state_data, - &states_with_entry_function, - &states_with_exit_function, )?; transition_added = true; @@ -264,13 +249,7 @@ impl ParsedStateMachine { )); } } else { - add_transition( - transition, - &mut states_events_mapping, - &state_data, - &states_with_entry_function, - &states_with_exit_function, - )?; + add_transition(transition, &mut states_events_mapping, &state_data)?; } } From 82351af41e03dc604f0abc7ac8f1ad42d70610b7 Mon Sep 17 00:00:00 2001 From: Martin Broers Date: Thu, 27 Jun 2024 20:42:44 +0200 Subject: [PATCH 5/6] Removed on_entry flag Also the parse will not parse ">" and "<" states anymore, since all on_entry and on_exit functions will be autogenerated. Signed-off-by: Martin Broers --- README.md | 24 +--------- examples/on_entry_on_exit.rs | 70 ---------------------------- examples/on_entry_on_exit_generic.rs | 3 +- macros/src/codegen.rs | 57 +++++++--------------- macros/src/parser/mod.rs | 37 --------------- macros/src/parser/state_machine.rs | 25 +--------- macros/src/parser/transition.rs | 33 +------------ 7 files changed, 23 insertions(+), 226 deletions(-) delete mode 100644 examples/on_entry_on_exit.rs diff --git a/README.md b/README.md index 31251fc..3eb87a0 100644 --- a/README.md +++ b/README.md @@ -262,28 +262,8 @@ This example is available in `ex3.rs`. ### Using entry and exit functions in transitions -DSL implementation: - -```rust -statemachine!{ - transitions: { - *State1 + Event1 = State2, - State2 < exit_state_2 + Event2 = State1, - State1 > enter_state_3 + Event3 = State3, - State2 + Event3 = State3, - } -} -``` -For all transitions entering State3, the function `enter_state_3` will be -called. For all transitions exiting State2, the function `exit_state_2` will be -called, in the right order, so first the `exit` function prior to the `entry` -function. - -An example is available in `on_entry_on_exit`. - -There is also a generic flag available, `generate_on_entry_on_exit`, which will -generate for all states in the statemachine an entry and an exit function. If -they are not used, they will be optimized away by the compiler. An example be +The statemachine will create for all states an `on_entry_` and `on_exit_` function. +If the are not used, they will be optimized away by the compiler. An example be found in `on_entry_on_exit_generic`. ## Helpers diff --git a/examples/on_entry_on_exit.rs b/examples/on_entry_on_exit.rs deleted file mode 100644 index f01a564..0000000 --- a/examples/on_entry_on_exit.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! An example of using state data to propagate events (See issue-17) - -#![deny(missing_docs)] - -use smlang::statemachine; - -statemachine! { - name: OnEntryExample, - generate_entry_exit_states: true, - transitions: { - *D0 > exit_d0 + ToD1 = D1, - D0 + ToD3 = D3, - D1 < enter_d1 + ToD2 = D2, - D2 + ToD1 = D1, - D1 + ToD0 = D0, - }, -} - -/// Context -pub struct Context { - exited_d0: i32, - entered_d1: i32, -} - -impl OnEntryExampleStateMachineContext for Context { - fn on_exit_d0(&mut self) { - self.exited_d0 += 1; - } - fn on_entry_d1(&mut self) { - self.entered_d1 += 1; - } -} - -fn main() { - let mut sm = OnEntryExampleStateMachine::new(Context { - exited_d0: 0, - entered_d1: 0, - }); - - // first event starts the dominos - let _ = sm.process_event(OnEntryExampleEvents::ToD1).unwrap(); - - assert!(matches!(sm.state(), Ok(&OnEntryExampleStates::D1))); - assert_eq!(sm.context().exited_d0, 1); - assert_eq!(sm.context().entered_d1, 1); - - let _ = sm.process_event(OnEntryExampleEvents::ToD2).unwrap(); - - assert!(matches!(sm.state(), Ok(&OnEntryExampleStates::D2))); - assert_eq!(sm.context().exited_d0, 1); - assert_eq!(sm.context().entered_d1, 1); - - let _ = sm.process_event(OnEntryExampleEvents::ToD1).unwrap(); - - assert!(matches!(sm.state(), Ok(&OnEntryExampleStates::D1))); - assert_eq!(sm.context().exited_d0, 1); - assert_eq!(sm.context().entered_d1, 2); - - let _ = sm.process_event(OnEntryExampleEvents::ToD0).unwrap(); - - assert!(matches!(sm.state(), Ok(&OnEntryExampleStates::D0))); - assert_eq!(sm.context().exited_d0, 1); - assert_eq!(sm.context().entered_d1, 2); - - let _ = sm.process_event(OnEntryExampleEvents::ToD3).unwrap(); - - assert!(matches!(sm.state(), Ok(&OnEntryExampleStates::D3))); - assert_eq!(sm.context().exited_d0, 2); - assert_eq!(sm.context().entered_d1, 2); -} diff --git a/examples/on_entry_on_exit_generic.rs b/examples/on_entry_on_exit_generic.rs index bde15e5..608869b 100644 --- a/examples/on_entry_on_exit_generic.rs +++ b/examples/on_entry_on_exit_generic.rs @@ -6,9 +6,8 @@ use smlang::statemachine; statemachine! { name: OnEntryExample, - generate_entry_exit_states: true, transitions: { - *D0 < exit_d0 + ToD1 = D1, + *D0 + ToD1 = D1, D0 + ToD3 = D3, D1 + ToD2 = D2, D2 + ToD1 = D1, diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index b742db3..bef2d4c 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -19,12 +19,8 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { let state_machine_context_type_name = format_ident!("{sm_name}StateMachineContext", span = sm_name_span); - let generate_entry_exit_states = sm.generate_entry_exit_states; let generate_transition_callback = sm.generate_transition_callback; - let mut entry_fns = sm.entry_functions.clone(); - let mut exit_fns = sm.exit_functions.clone(); - // Get only the unique states let mut state_list: Vec<_> = sm.states.values().collect(); state_list.sort_by_key(|state| state.to_string()); @@ -255,18 +251,6 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { let mut action_list = proc_macro2::TokenStream::new(); let mut entries_exits = proc_macro2::TokenStream::new(); - for ident in entry_fns.values() { - entries_exits.extend(quote! { - #[allow(missing_docs)] - fn #ident(&mut self){} - }); - } - for ident in exit_fns.values() { - entries_exits.extend(quote! { - #[allow(missing_docs)] - fn #ident(&mut self){} - }); - } for (state, event_mappings) in transitions.iter() { // create the state data token stream @@ -275,20 +259,17 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { Some(st) => quote! { state_data: &#st, }, None => quote! {}, }; - if generate_entry_exit_states { - let entry_ident = format_ident!("on_entry_{}", string_morph::to_snake_case(state)); - entries_exits.extend(quote! { - #[allow(missing_docs)] - fn #entry_ident(&mut self){} - }); - entry_fns.insert(format_ident!("{}", state), entry_ident); - let exit_ident = format_ident!("on_exit_{}", string_morph::to_snake_case(state)); - entries_exits.extend(quote! { - #[allow(missing_docs)] - fn #exit_ident(&mut self){} - }); - exit_fns.insert(format_ident!("{}", state), exit_ident); - }; + + let entry_ident = format_ident!("on_entry_{}", string_morph::to_snake_case(state)); + entries_exits.extend(quote! { + #[allow(missing_docs)] + fn #entry_ident(&mut self){} + }); + let exit_ident = format_ident!("on_exit_{}", string_morph::to_snake_case(state)); + entries_exits.extend(quote! { + #[allow(missing_docs)] + fn #exit_ident(&mut self){} + }); for (event, event_mapping) in event_mappings { for transition in &event_mapping.transitions { @@ -446,19 +427,17 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { let binding = out_state.to_string(); let out_state_string = &binding.split('(').collect::>()[0]; - let entry_ident = format_ident!("on_entry_{}",string_morph::to_snake_case(out_state_string )); let binding = in_state.to_string(); let in_state_string = &binding.split('(').collect::>()[0]; - let exit_ident = format_ident!("on_exit_{}",string_morph::to_snake_case(in_state_string)); - let entry_exit_states = if generate_entry_exit_states { - quote! { + + let entry_ident = format_ident!("on_entry_{}", string_morph::to_snake_case(out_state_string)); + let exit_ident = format_ident!("on_exit_{}", string_morph::to_snake_case(in_state_string)); + + let entry_exit_states = + quote! { self.context_mut().#exit_ident(); self.context_mut().#entry_ident(); - } - } else { - quote! { } - }; - + }; let (is_async_action,action_code) = generate_action(action, &temporary_context_call, g_a_param); is_async_state_machine |= is_async_action; if let Some(expr) = guard { // Guarded transition diff --git a/macros/src/parser/mod.rs b/macros/src/parser/mod.rs index 418b9ba..dbb01aa 100644 --- a/macros/src/parser/mod.rs +++ b/macros/src/parser/mod.rs @@ -20,13 +20,6 @@ use syn::{parse, Ident, Type}; use transition::StateTransition; pub type TransitionMap = HashMap>; -#[derive(Debug, Clone)] -pub struct EntryIdent { - pub ident: Ident, - pub state: Vec, - pub is_async: bool, -} - #[derive(Debug, Clone)] pub struct AsyncIdent { pub ident: Ident, @@ -64,10 +57,7 @@ pub struct ParsedStateMachine { pub event_data: DataDefinitions, pub states_events_mapping: HashMap>, - pub generate_entry_exit_states: bool, pub generate_transition_callback: bool, - pub entry_functions: HashMap, - pub exit_functions: HashMap, } // helper function for adding a transition to a transition event map @@ -147,29 +137,6 @@ impl ParsedStateMachine { let mut event_data = DataDefinitions::new(); let mut states_events_mapping = TransitionMap::new(); - let mut states_with_exit_function = HashMap::new(); - let mut states_with_entry_function = HashMap::new(); - - fn add_entry(map: &mut HashMap, vec: &Vec) -> parse::Result<()> { - for identifier in vec { - for input_state in &identifier.state { - if let Some(existing_identifier) = - map.insert(input_state.ident.clone(), identifier.ident.clone()) - { - if identifier.ident != existing_identifier { - return Err(parse::Error::new( - Span::call_site(), - "Different entry or exit functions defined for state", - )); - } - } - } - } - Ok(()) - } - add_entry(&mut states_with_entry_function, &sm.entries)?; - add_entry(&mut states_with_exit_function, &sm.exits)?; - for transition in sm.transitions.iter() { // Collect states let in_state_name = transition.in_state.ident.to_string(); @@ -265,11 +232,7 @@ impl ParsedStateMachine { events, event_data, states_events_mapping, - generate_entry_exit_states: sm.generate_entry_exit_states, generate_transition_callback: sm.generate_transition_callback, - - entry_functions: states_with_entry_function, - exit_functions: states_with_exit_function, }) } } diff --git a/macros/src/parser/state_machine.rs b/macros/src/parser/state_machine.rs index 48f9e9c..5933f62 100644 --- a/macros/src/parser/state_machine.rs +++ b/macros/src/parser/state_machine.rs @@ -1,7 +1,4 @@ -use super::{ - transition::{StateTransition, StateTransitions}, - EntryIdent, -}; +use super::transition::{StateTransition, StateTransitions}; use syn::{braced, bracketed, parse, spanned::Spanned, token, Ident, Token, Type}; #[derive(Debug)] @@ -12,10 +9,7 @@ pub struct StateMachine { pub name: Option, pub derive_states: Vec, pub derive_events: Vec, - pub generate_entry_exit_states: bool, pub generate_transition_callback: bool, - pub entries: Vec, - pub exits: Vec, } impl StateMachine { @@ -27,10 +21,7 @@ impl StateMachine { name: None, derive_states: Vec::new(), derive_events: Vec::new(), - generate_entry_exit_states: false, generate_transition_callback: false, - entries: Vec::new(), - exits: Vec::new(), } } @@ -45,12 +36,6 @@ impl StateMachine { }; self.transitions.push(transition); } - if let Some(entry) = transitions.entry { - self.entries.push(entry); - } - if let Some(exit) = transitions.exit { - self.exits.push(exit); - } } } @@ -155,13 +140,6 @@ impl parse::Parse for StateMachine { }; } } - "generate_entry_exit_states" => { - input.parse::()?; - let generate_entry_exit_states: syn::LitBool = input.parse()?; - if generate_entry_exit_states.value { - statemachine.generate_entry_exit_states = true - } - } "generate_transition_callback" => { input.parse::()?; let generate_transition_callback: syn::LitBool = input.parse()?; @@ -179,7 +157,6 @@ impl parse::Parse for StateMachine { \"custom_guard_error\", \"derive_states\", \"derive_events\", - \"generate_entry_exit_states\", \"generate_transition_callback\", ]", keyword diff --git a/macros/src/parser/transition.rs b/macros/src/parser/transition.rs index efe2ef1..e7031ef 100644 --- a/macros/src/parser/transition.rs +++ b/macros/src/parser/transition.rs @@ -1,7 +1,7 @@ +use super::event::Event; use super::input_state::InputState; use super::output_state::OutputState; use super::AsyncIdent; -use super::{event::Event, EntryIdent}; use proc_macro2::TokenStream; use quote::quote; use std::fmt; @@ -22,8 +22,6 @@ pub struct StateTransitions { pub event: Event, pub guard: Option, pub action: Option, - pub entry: Option, - pub exit: Option, pub out_state: OutputState, } @@ -50,33 +48,6 @@ impl parse::Parse for StateTransitions { } } } - - // Possible extry function - let entry = if input.parse::().is_ok() { - let is_async = input.parse::().is_ok(); - let entry_function: Ident = input.parse()?; - Some(EntryIdent { - ident: entry_function, - state: in_states.clone(), - is_async, - }) - } else { - None - }; - let exit = if input.parse::]>().is_ok() { - let is_async = input.parse::().is_ok(); - let exit_function: Ident = match input.parse() { - Ok(v) => v, - Err(e) => panic!("Could not parse exit token: {:?}", e), - }; - Some(EntryIdent { - ident: exit_function, - state: in_states.clone(), - is_async, - }) - } else { - None - }; // Event let event: Event = input.parse()?; @@ -109,8 +80,6 @@ impl parse::Parse for StateTransitions { guard, action, out_state, - entry, - exit, }) } } From fa4f609965afaaab19f568c4c516cb84d5c727c6 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Sun, 30 Jun 2024 13:51:11 +0200 Subject: [PATCH 6/6] Removing transition_callback, adding documentation, updating CHANGELOG --- CHANGELOG.md | 3 +++ examples/transition_callback.rs | 38 ------------------------------ macros/src/codegen.rs | 26 ++++++-------------- macros/src/parser/mod.rs | 3 --- macros/src/parser/state_machine.rs | 22 +++++------------ 5 files changed, 16 insertions(+), 76 deletions(-) delete mode 100644 examples/transition_callback.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index a57c096..1cc360f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Add hooks to `StateMachineContext` for logging events, guards, actions, and state changes - Add support multiple guarded transitions for a triggering event - Add support for guard boolean expressions in the state machine declaration +- There are now `on_entry_` and `on_entry_` functions +defined to allow handling entry and exit from all state machine states. These have a default empty +implementation. ### Fixed diff --git a/examples/transition_callback.rs b/examples/transition_callback.rs deleted file mode 100644 index 6722315..0000000 --- a/examples/transition_callback.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! An example of using state data to propagate events (See issue-17) - -#![deny(missing_docs)] - -use std::sync::{Arc, Mutex}; - -use smlang::statemachine; - -statemachine! { - generate_transition_callback: true, - transitions: { - *D0 + ToD1 = D1, - D1 + ToD2 = D2, - }, -} - -/// Context -pub struct Context { - transition_called: Arc>, -} - -impl StateMachineContext for Context { - fn transition_callback(&self, _state: &Option) { - *self.transition_called.lock().unwrap() = true; - } -} - -fn main() { - let mut sm = StateMachine::new(Context { - transition_called: Arc::new(Mutex::new(false)), - }); - - // first event starts the dominos - let _ = sm.process_event(Events::ToD1).unwrap(); - - assert!(matches!(sm.state(), Ok(&States::D1))); - assert!(*sm.context().transition_called.lock().unwrap()); -} diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs index bef2d4c..6664a8b 100644 --- a/macros/src/codegen.rs +++ b/macros/src/codegen.rs @@ -19,8 +19,6 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { let state_machine_context_type_name = format_ident!("{sm_name}StateMachineContext", span = sm_name_span); - let generate_transition_callback = sm.generate_transition_callback; - // Get only the unique states let mut state_list: Vec<_> = sm.states.values().collect(); state_list.sort_by_key(|state| state.to_string()); @@ -261,14 +259,15 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { }; let entry_ident = format_ident!("on_entry_{}", string_morph::to_snake_case(state)); + let state_name = format!("[{}::{}]", states_type_name, state); entries_exits.extend(quote! { - #[allow(missing_docs)] - fn #entry_ident(&mut self){} + #[doc = concat!("Called on entry to ", #state_name)] + fn #entry_ident(&mut self) {} }); let exit_ident = format_ident!("on_exit_{}", string_morph::to_snake_case(state)); entries_exits.extend(quote! { - #[allow(missing_docs)] - fn #exit_ident(&mut self){} + #[doc = concat!("Called on exit from ", #state_name)] + fn #exit_ident(&mut self) {} }); for (event, event_mapping) in event_mappings { @@ -426,9 +425,9 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { .zip(action.iter().zip(out_state)).map(|(guard, (action,out_state))| { let binding = out_state.to_string(); - let out_state_string = &binding.split('(').collect::>()[0]; + let out_state_string = &binding.split('(').next().unwrap(); let binding = in_state.to_string(); - let in_state_string = &binding.split('(').collect::>()[0]; + let in_state_string = &binding.split('(').next().unwrap(); let entry_ident = format_ident!("on_entry_{}", string_morph::to_snake_case(out_state_string)); let exit_ident = format_ident!("on_exit_{}", string_morph::to_snake_case(in_state_string)); @@ -579,13 +578,6 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { let derive_states_list = &sm.derive_states; let derive_events_list = &sm.derive_events; - let transition_callback = if generate_transition_callback { - quote!( - self.context().transition_callback(&self.state); - ) - } else { - quote!() - }; // Build the states and events output quote! { /// This trait outlines the guards and actions that need to be implemented for the state @@ -617,9 +609,6 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { /// `process_event()`. No-op by default but can be overridden in implementations /// of a state machine's `StateMachineContext` trait. fn log_state_change(&self, new_state: & #states_type_name) {} - - #[allow(missing_docs)] - fn transition_callback(&self, new_state: &Option<#states_type_name>) {} } /// List of auto-generated states. @@ -718,7 +707,6 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream { #states_type_name::#in_states => match event { #(#events_type_name::#events => { #code_blocks - #transition_callback #[allow(unreachable_code)] { diff --git a/macros/src/parser/mod.rs b/macros/src/parser/mod.rs index dbb01aa..ea65567 100644 --- a/macros/src/parser/mod.rs +++ b/macros/src/parser/mod.rs @@ -56,8 +56,6 @@ pub struct ParsedStateMachine { pub events: HashMap, pub event_data: DataDefinitions, pub states_events_mapping: HashMap>, - - pub generate_transition_callback: bool, } // helper function for adding a transition to a transition event map @@ -232,7 +230,6 @@ impl ParsedStateMachine { events, event_data, states_events_mapping, - generate_transition_callback: sm.generate_transition_callback, }) } } diff --git a/macros/src/parser/state_machine.rs b/macros/src/parser/state_machine.rs index 5933f62..85f4402 100644 --- a/macros/src/parser/state_machine.rs +++ b/macros/src/parser/state_machine.rs @@ -9,7 +9,6 @@ pub struct StateMachine { pub name: Option, pub derive_states: Vec, pub derive_events: Vec, - pub generate_transition_callback: bool, } impl StateMachine { @@ -21,7 +20,6 @@ impl StateMachine { name: None, derive_states: Vec::new(), derive_events: Vec::new(), - generate_transition_callback: false, } } @@ -140,24 +138,16 @@ impl parse::Parse for StateMachine { }; } } - "generate_transition_callback" => { - input.parse::()?; - let generate_transition_callback: syn::LitBool = input.parse()?; - if generate_transition_callback.value { - statemachine.generate_transition_callback = true - } - } keyword => { return Err(parse::Error::new( input.span(), format!( - "Unknown keyword {}. Support keywords: [\"name\", - \"transitions\", - \"temporary_context\", - \"custom_guard_error\", - \"derive_states\", - \"derive_events\", - \"generate_transition_callback\", + "Unknown keyword {}. Support keywords: [\"name\", \ + \"transitions\", \ + \"temporary_context\", \ + \"custom_guard_error\", \ + \"derive_states\", \ + \"derive_events\" ]", keyword ),