use std::cmp::Ordering;
use proc_macro2::TokenStream;
use quote::__private::ext::RepToTokensExt;
use quote::format_ident;
use quote::quote;
use quote::ToTokens;
use syn::Ident;
use syn::Type;
use syn::{parse2, Error, ImplItem, Result};
use crate::parse::Fsm;
pub fn expand(fsm: &mut Fsm) -> Result<TokenStream> {
if let Some(trait_) = &fsm.item_impl.trait_ {
let trait_path = &trait_.1;
if let Some(last_trait_segment) = trait_path.segments.last() {
if last_trait_segment.ident != "Fsm" {
return Err(Error::new_spanned(
&last_trait_segment.ident,
"Expected the Fsm trait to be implemented.",
));
}
} else {
return Err(Error::new_spanned(
&trait_path.segments,
"The first generic representing a State enum is required.",
));
}
} else {
return Err(Error::new_spanned(
&fsm.item_impl,
"Expected the Fsm trait to be implemented.",
));
};
let state_enum = &fsm.state_enum;
let command_enum = &fsm.command_enum;
let event_enum = &fsm.event_enum;
let effect_handlers = &fsm.effect_handlers;
let mut entry_matches = Vec::with_capacity(fsm.entry_handlers.len());
for ee in &fsm.entry_handlers {
let state = ident_from_type(&ee.state)?;
let handler = format_ident!("on_entry_{}", state);
let handler = Ident::new(&handler.to_string().to_lowercase(), handler.span());
entry_matches.push(quote!(
#state_enum::#state(s) => Self::#handler(s, se),
));
}
let steps_len = fsm.steps.len();
let mut command_matches = Vec::with_capacity(steps_len);
let mut event_matches = Vec::with_capacity(steps_len);
let mut change_matches = Vec::with_capacity(steps_len);
for s in &fsm.steps {
let from_state = if let Type::Infer(_) = s.from_state() {
None
} else {
Some(ident_from_type(s.from_state())?)
};
let command = if let Some(command) = s.command() {
Some(ident_from_type(command)?)
} else {
None
};
let event = if let Some(event) = s.event() {
Some(ident_from_type(event)?)
} else {
None
};
let (to_state, to_state_num) = if let Some(to_state) = s.to_state() {
match to_state.states.as_slice() {
[single_type] => (Some(ident_from_type(single_type)?), 1),
[first_type, ..] => (Some(ident_from_type(first_type)?), to_state.states.len()),
_ => panic!("There must be at least one element"),
}
} else {
(None, 0)
};
if let Some(command) = command {
if let Some(from_state) = from_state {
let command_handler =
lowercase_ident(&format_ident!("for_{}_{}", from_state, command));
if let Some(event) = event {
command_matches.push(quote!(
(#state_enum::#from_state(s), #command_enum::#command(c)) => {
Self::#command_handler(s, c, se).map(#event_enum::#event)
}
));
} else {
command_matches.push(quote!(
(#state_enum::#from_state(s), #command_enum::#command(c)) => {
Self::#command_handler(s, c, se);
None
}
));
}
} else {
let command_handler = lowercase_ident(&format_ident!("for_any_{}", command));
if let Some(event) = event {
command_matches.push(quote!(
(_, #command_enum::#command(c)) => {
Self::#command_handler(s, c, se).map(#event_enum::#event)
}
));
} else {
command_matches.push(quote!(
(_, #command_enum::#command(c)) => {
Self::#command_handler(s, c, se);
None
}
));
}
}
}
let mut push_change_matches_conditionally = |to_state, event| {
if s.on_change() {
let change_handler = if let Some(to_state) = to_state {
lowercase_ident(&format_ident!("on_change_{}_{}", to_state, event))
} else {
lowercase_ident(&format_ident!("on_change_any_{}", event))
};
change_matches.push(quote!(
(#state_enum::#to_state(s), #event_enum::#event(e)) => {
Self::#change_handler(s, e, se)
}
));
}
};
if let Some(to_state) = to_state {
if let Some(from_state) = from_state {
if let Some(event) = event {
let event_handler =
lowercase_ident(&format_ident!("on_{}_{}", from_state, event));
match to_state_num.cmp(&1) {
Ordering::Less => event_matches.push(quote!(
(#state_enum::#from_state(s), #event_enum::#event(e)) => {
Self::#event_handler(s, e).map(|_| (edfsm::Change::Updated, None))
}
)),
Ordering::Equal => event_matches.push(quote!(
(#state_enum::#from_state(s), #event_enum::#event(e)) => {
Self::#event_handler(s, e).map(|new_s| (edfsm::Change::Transitioned, Some(#state_enum::#to_state(new_s))))
}
)),
Ordering::Greater => event_matches.push(quote!(
(#state_enum::#from_state(s), #event_enum::#event(e)) => {
Self::#event_handler(s, e)
}
)),
}
push_change_matches_conditionally(Some(to_state), event);
}
} else {
let event = event.unwrap(); let event_handler = lowercase_ident(&format_ident!("on_any_{}", event));
match to_state_num.cmp(&1) {
Ordering::Less => event_matches.push(quote!(
(s, #event_enum::#event(e)) => {
Self::#event_handler(s, e).map(|_| (edfsm::Change::Updated, None))
}
)),
Ordering::Equal => event_matches.push(quote!(
(s, #event_enum::#event(e)) => {
Self::#event_handler(s, e).map(|new_s| (edfsm::Change::Transitioned, Some(#state_enum::#to_state(new_s))))
}
)),
Ordering::Greater => event_matches.push(quote!(
(s, #event_enum::#event(e)) => {
Self::#event_handler(s, e)
}
)),
}
push_change_matches_conditionally(Some(to_state), event);
};
} else if let Some(from_state) = from_state {
if let Some(event) = event {
let event_handler = lowercase_ident(&format_ident!("on_{}_{}", from_state, event));
event_matches.push(quote!(
(#state_enum::#from_state(s), #event_enum::#event(e)) => {
Self::#event_handler(s, e);
Some((edfsm::Change::Updated, None))
}
));
push_change_matches_conditionally(Some(from_state), event);
}
} else {
if let Some(event) = event {
let event_handler = lowercase_ident(&format_ident!("on_any_{}", event));
event_matches.push(quote!(
(s, #event_enum::#event(e)) => {
Self::#event_handler(s, e);
Some((edfsm::Change::Updated, None))
}
));
push_change_matches_conditionally(from_state, event);
}
}
}
if fsm.ignore_commands.is_empty() {
command_matches.push(quote!(
_ => None,
));
} else {
for i in &fsm.ignore_commands {
let from_state = if let Type::Infer(_) = i.from_state {
None
} else {
Some(ident_from_type(&i.from_state)?)
};
let command = ident_from_type(&i.command)?;
if let Some(from_state) = from_state {
command_matches.push(quote!(
(#state_enum::#from_state(s), #command_enum::#command(_)) => None,
));
} else {
command_matches.push(quote!(
(_, #command_enum::#command(_)) => None,
));
}
}
}
if fsm.ignore_events.is_empty() {
event_matches.push(quote!(
_ => None,
));
} else {
for i in &fsm.ignore_events {
let from_state = if let Type::Infer(_) = i.from_state {
None
} else {
Some(ident_from_type(&i.from_state)?)
};
let event = ident_from_type(&i.event)?;
if let Some(from_state) = from_state {
event_matches.push(quote!(
(#state_enum::#from_state(s), #event_enum::#event(_)) => None,
));
} else {
event_matches.push(quote!(
(_, #event_enum::#event(_)) => None,
));
}
}
}
fsm.item_impl.items = vec![
parse2::<ImplItem>(quote!(
type S = #state_enum;
))
.unwrap(),
parse2::<ImplItem>(quote!(
type C = #command_enum;
))
.unwrap(),
parse2::<ImplItem>(quote!(
type E = #event_enum;
))
.unwrap(),
parse2::<ImplItem>(quote!(
type SE = #effect_handlers;
))
.unwrap(),
parse2::<ImplItem>(quote!(
fn for_command(
s: &#state_enum,
c: #command_enum,
se: &mut #effect_handlers,
) -> Option<#event_enum> {
match (s, c) {
#( #command_matches )*
}
}
))
.unwrap(),
parse2::<ImplItem>(quote!(
fn on_event(
mut s: &mut #state_enum,
e: &#event_enum,
) -> Option<edfsm::Change> {
let r = match (&mut s, e) {
#( #event_matches )*
};
if let Some((c, new_s)) = r {
if let Some(new_s) = new_s {
*s = new_s;
}
Some(c)
} else {
None
}
}
))
.unwrap(),
parse2::<ImplItem>(quote!(
fn on_change(new_s: &#state_enum, e: &#event_enum, se: &mut #effect_handlers, change: edfsm::Change) {
if let edfsm::Change::Transitioned = change {
match new_s {
#( #entry_matches )*
_ => {}
}
}
match (new_s, e) {
#( #change_matches )*
_ => (),
}
}
))
.unwrap(),
];
Ok(fsm.item_impl.to_token_stream())
}
fn lowercase_ident(ident: &Ident) -> Ident {
Ident::new(&ident.to_string().to_lowercase(), ident.span())
}
fn ident_from_type(from_type: &Type) -> Result<&Ident> {
if let Type::Path(path) = from_type {
if path.path.segments.len() == 1 {
let segment = path.path.segments.next().unwrap().first().unwrap();
if segment.arguments.is_empty() {
Ok(&segment.ident)
} else {
Err(Error::new_spanned(
segment.ident.clone(),
"No arguments are expected",
))
}
} else {
Err(Error::new_spanned(path, "No path segments are expected"))
}
} else {
Err(Error::new_spanned(
from_type.clone(),
"A type that can also be expressed as an enum variant is expected",
))
}
}