use crate::parser::transition::visit_guards;
use crate::parser::{AsyncIdent, ParsedStateMachine};
use proc_macro2::Span;
use std::collections::HashMap;
use syn::parse;
#[derive(PartialEq, Clone)]
struct FunctionSignature {
arguments: Vec<syn::Type>,
result: Option<syn::Type>,
is_async: bool,
}
impl FunctionSignature {
pub fn new(
input_data: Option<&syn::Type>,
event_data: Option<&syn::Type>,
output_data: Option<&syn::Type>,
is_async: bool,
) -> Self {
let mut input_arguments = vec![];
if let Some(datatype) = input_data {
input_arguments.push(datatype.clone());
}
if let Some(datatype) = event_data {
input_arguments.push(datatype.clone());
}
let result = output_data.cloned();
Self {
arguments: input_arguments,
result,
is_async,
}
}
pub fn new_guard(
input_state: Option<&syn::Type>,
event: Option<&syn::Type>,
is_async: bool,
) -> Self {
Self::new(input_state, event, None, is_async)
}
}
fn validate_action_signatures(sm: &ParsedStateMachine) -> Result<(), parse::Error> {
let mut actions = HashMap::new();
let all_transitions = &sm.states_events_mapping;
for (in_state_name, from_transitions) in all_transitions.iter() {
let in_state_data = sm.state_data.data_types.get(in_state_name);
for (out_state_name, event_mapping) in from_transitions.iter() {
let out_state_data = sm.state_data.data_types.get(out_state_name);
let event_data = sm
.event_data
.data_types
.get(&event_mapping.event.to_string());
for transition in &event_mapping.transitions {
let transition_actions = transition
.action
.iter()
.chain(transition.additional_actions.iter())
.collect::<Vec<_>>();
let last_action_index = transition_actions.len().saturating_sub(1);
for (
action_index,
AsyncIdent {
ident: action,
is_async,
},
) in transition_actions.into_iter().enumerate()
{
let action_output_data = if action_index == last_action_index {
out_state_data
} else {
None
};
let signature = FunctionSignature::new(
in_state_data,
event_data,
action_output_data,
*is_async,
);
actions
.entry(action.to_string())
.or_insert_with(|| signature.clone());
if actions.get(&action.to_string()).unwrap() != &signature {
return Err(parse::Error::new(
Span::call_site(),
format!("Action `{}` can only be reused when all input states, events, and output states have the same data", action),
));
}
}
for eval in &transition.eval_actions {
let signature = FunctionSignature::new(
in_state_data,
event_data,
None,
eval.action.is_async,
);
let action = eval.action.ident.to_string();
actions
.entry(action.clone())
.or_insert_with(|| signature.clone());
if actions.get(&action).unwrap() != &signature {
return Err(parse::Error::new(
eval.action.ident.span(),
format!(
"Eval action `{action}` has incompatible input state or event data"
),
));
}
}
}
}
}
Ok(())
}
fn validate_guard_signatures(sm: &ParsedStateMachine) -> Result<(), parse::Error> {
let mut guards = HashMap::new();
let all_transitions = &sm.states_events_mapping;
for (in_state_name, from_transitions) in all_transitions.iter() {
let in_state_data = sm.state_data.data_types.get(in_state_name);
for event_mapping in from_transitions.values() {
let event_data = sm
.event_data
.data_types
.get(&event_mapping.event.to_string());
for transition in &event_mapping.transitions {
if let Some(guard_expression) = &transition.guard {
let res = visit_guards(guard_expression, |guard| {
let signature =
FunctionSignature::new_guard(in_state_data, event_data, guard.is_async);
guards
.entry(guard.ident.to_string())
.or_insert_with(|| signature.clone());
if guards.get(&guard.ident.to_string()).unwrap() != &signature {
return Err(parse::Error::new(
Span::call_site(),
format!("Guard `{}` can only be reused when all input states and events have the same data", guard.ident),
));
}
Ok(())
});
res?;
}
for eval in &transition.eval_actions {
visit_guards(&eval.guard, |guard| {
let signature =
FunctionSignature::new_guard(in_state_data, event_data, guard.is_async);
let name = guard.ident.to_string();
guards
.entry(name.clone())
.or_insert_with(|| signature.clone());
if guards.get(&name).unwrap() != &signature {
return Err(parse::Error::new(
guard.ident.span(),
format!("Eval guard `{name}` has incompatible input state or event data"),
));
}
Ok(())
})?;
}
}
}
}
Ok(())
}
fn validate_unreachable_transitions(sm: &ParsedStateMachine) -> Result<(), parse::Error> {
let all_transitions = &sm.states_events_mapping;
for (in_state, event_mappings) in all_transitions {
for (event, event_mapping) in event_mappings {
if event_mapping.transitions.len() > 1 {
let mut unguarded_count = 0;
for t in &event_mapping.transitions {
if let Some(g) = &t.guard {
if unguarded_count > 0 {
return Err(parse::Error::new(
Span::call_site(),
format!("{} + {}: [{}] : guarded transition is unreachable because it follows an unguarded transition, which handles all cases",
in_state, event, g),
));
}
} else {
unguarded_count += 1;
if unguarded_count > 1 {
return Err(parse::Error::new(
Span::call_site(),
format!("{} + {}: State and event combination specified multiple times, remove duplicates.", in_state, event),
));
}
}
}
}
}
}
Ok(())
}
pub fn validate(sm: &ParsedStateMachine) -> Result<(), parse::Error> {
validate_action_signatures(sm)?;
validate_guard_signatures(sm)?;
validate_unreachable_transitions(sm)?;
Ok(())
}