Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support on_entry and on_exit for states #66

Merged
merged 6 commits into from
Jun 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_<snakecase_statename>` and `on_entry_<snakecase_statename>` functions
defined to allow handling entry and exit from all state machine states. These have a default empty
implementation.

### Fixed

Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,12 @@ statemachine!{

This example is available in `ex3.rs`.

### Using entry and exit functions in transitions

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

### Auto-derive certain traits for states and events
Expand Down
69 changes: 69 additions & 0 deletions examples/on_entry_on_exit_generic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//! 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,
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);
}
1 change: 1 addition & 0 deletions macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ readme = "../README.md"
[dependencies]
quote = "1"
proc-macro2 = "1"
string_morph = "0.1.0"

[dependencies.syn]
features = ["extra-traits", "full"]
Expand Down
41 changes: 35 additions & 6 deletions macros/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,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<Vec<_>> = transitions
.values()
Expand Down Expand Up @@ -252,6 +247,9 @@ 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 entries_exits = 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) {
Expand All @@ -260,6 +258,18 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream {
None => quote! {},
};

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! {
#[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! {
#[doc = concat!("Called on exit from ", #state_name)]
fn #exit_ident(&mut self) {}
});

for (event, event_mapping) in event_mappings {
for transition in &event_mapping.transitions {
// get input state lifetimes
Expand Down Expand Up @@ -401,7 +411,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(
Expand All @@ -413,6 +423,20 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream {
let streams: Vec<TokenStream> =
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('(').next().unwrap();
let binding = in_state.to_string();
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));

let entry_exit_states =
quote! {
self.context_mut().#exit_ident();
self.context_mut().#entry_ident();
};
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
Expand Down Expand Up @@ -467,6 +491,7 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream {
#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()
}
Expand All @@ -476,6 +501,7 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream {
#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();
}
Expand Down Expand Up @@ -561,6 +587,8 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream {
#guard_error
#guard_list
#action_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
Expand Down Expand Up @@ -679,6 +707,7 @@ pub fn generate_code(sm: &ParsedStateMachine) -> proc_macro2::TokenStream {
#states_type_name::#in_states => match event {
#(#events_type_name::#events => {
#code_blocks

#[allow(unreachable_code)]
{
// none of the guarded or non-guarded transitions occurred,
Expand Down
29 changes: 18 additions & 11 deletions macros/src/parser/state_machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ impl parse::Parse for StateMachine {
if custom_guard_error.value {
statemachine.custom_guard_error = true
}

}
"temporary_context" => {
input.parse::<Token![:]>()?;
Expand All @@ -102,48 +101,56 @@ impl parse::Parse for StateMachine {

// Store the temporary context type
statemachine.temporary_context_type = Some(temporary_context_type);

}
"name" =>{
"name" => {
input.parse::<Token![:]>()?;
statemachine.name = Some(input.parse::<Ident>()?);
},
}
"derive_states" => {
input.parse::<Token![:]>()?;
if input.peek(token::Bracket) {
let content;
bracketed!(content in input);
loop{
loop {
if content.is_empty() {
break;
};
let trait_ = content.parse::<Ident>()?;
let trait_ = content.parse::<Ident>()?;
statemachine.derive_states.push(trait_);
if content.parse::<Token![,]>().is_err() {
break;
};
}
}
},
}
"derive_events" => {
input.parse::<Token![:]>()?;
let content;
bracketed!(content in input);
loop{
loop {
if content.is_empty() {
break;
};
let trait_ = content.parse::<Ident>()?;
let trait_ = content.parse::<Ident>()?;
statemachine.derive_events.push(trait_);
if content.parse::<Token![,]>().is_err() {
break;
};
}
},
}
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\"
]",
keyword
),
))
}
}
Expand Down
1 change: 0 additions & 1 deletion macros/src/parser/transition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ impl parse::Parse for StateTransitions {
}
}
}

// Event
let event: Event = input.parse()?;

Expand Down
Loading