use std::collections::HashMap;
use brisk_it::{generator::Result, Required};
use convert_case::Casing;
use quote::ToTokens;
fn unknown_state(state: &syn::Ident) -> proc_macro2::TokenStream {
syn::Error::new(state.span(), format!("Unknown state {}", state))
.into_compile_error()
}
fn snake_ident(id: &syn::Ident) -> syn::Ident {
let id_str = id.to_string().to_case(convert_case::Case::Snake);
syn::Ident::new(&id_str, id.span())
}
#[derive(Debug)]
enum TransitionInfo {
ForwardTransition,
SelfTransition {
destination_state: syn::Ident,
action: syn::Block,
},
}
pub(crate) struct GenerationInfo {
pub(crate) visibility: syn::Visibility,
pub(crate) event_dispatcher: syn::Ident,
}
pub(crate) struct GroupGenerationInfo<'a> {
pub(crate) id_event: &'a Option<syn::Ident>,
pub(crate) properties: &'a Option<proc_macro2::TokenStream>,
pub(crate) event_dispatcher: &'a syn::Ident,
}
#[derive(Debug)]
pub(crate) struct ParsedStateGroup {
id: syn::Ident,
name: syn::Ident,
first_state: syn::Ident,
states: HashMap<syn::Ident, ParsedState>,
transitions: HashMap<syn::Ident, HashMap<syn::Ident, TransitionInfo>>,
}
impl ParsedStateGroup {
pub(crate) fn parse(
input: brisk_it::component::ComponentInput,
manager: &brisk_it::generator::Manager,
) -> Result<ParsedStateGroup> {
let name = input.id.required(&input.name, "id")?;
let id = snake_ident(&name);
let (first_state, states) = Self::parse_states(input.children, manager)?;
let transitions = Self::parse_transitions(input.on_expressions, &states)?;
Ok(ParsedStateGroup {
id,
name,
first_state,
states,
transitions,
})
}
fn parse_states(
children: Vec<brisk_it::component::ComponentInput>,
manager: &brisk_it::generator::Manager,
) -> Result<(syn::Ident, HashMap<syn::Ident, ParsedState>)> {
Ok((
children
.first()
.as_ref()
.ok_or(brisk_it::errors::impossible(proc_macro2::Span::call_site()))?
.id
.as_ref()
.ok_or(brisk_it::errors::impossible(proc_macro2::Span::call_site()))?
.to_owned(),
children
.into_iter()
.map(|c| {
let s = ParsedState::parse(c, manager)?;
Ok((s.id.clone(), s))
})
.collect::<Result<_>>()?,
))
}
fn parse_transitions(
on_expressions: Vec<brisk_it::component::OnValue>,
states: &HashMap<syn::Ident, ParsedState>,
) -> Result<HashMap<syn::Ident, HashMap<syn::Ident, TransitionInfo>>> {
let mut map: HashMap<syn::Ident, HashMap<syn::Ident, TransitionInfo>> = on_expressions
.into_iter()
.map(|oe| {
let transition_name = oe.name;
let transition_infos = oe
.transition_definitions
.into_iter()
.map(|td| {
(
td.source_state,
TransitionInfo::SelfTransition {
destination_state: td.destination_state,
action: td.action,
},
)
})
.collect();
(transition_name, transition_infos)
})
.collect();
for (src_state_name, state) in states.iter() {
for group in state.groups.iter() {
for (transition_name, _) in group.transitions.iter() {
let s_ti = map.get_mut(transition_name);
match s_ti {
Some(v) => {
if let Some(ct) = v.get(src_state_name) {
match ct {
TransitionInfo::ForwardTransition => {}
TransitionInfo::SelfTransition { .. } => {
return Err(syn::Error::new(transition_name.span(), format!("Transition {} for state {} is shadowed by a higher priority transition.", transition_name, src_state_name))
.into_compile_error());
}
}
} else {
v.insert(
src_state_name.to_owned(),
TransitionInfo::ForwardTransition,
);
}
}
None => {
map.insert(
transition_name.to_owned(),
[(src_state_name.to_owned(), TransitionInfo::ForwardTransition)]
.into(),
);
}
}
}
}
}
Ok(map)
}
pub(crate) fn generate<'a>(
&self,
gen_info: &GenerationInfo,
group_gen_info: GroupGenerationInfo<'a>,
) -> Result<proc_macro2::TokenStream> {
let mut children = Vec::<proc_macro2::TokenStream>::new();
let mut states = Vec::<proc_macro2::TokenStream>::new();
let group_name = &self.name;
for (_, state) in self.states.iter() {
states.push(state.generate_enum_field()?);
children.push(state.generate(gen_info)?);
}
let transition_fn_args = match group_gen_info.properties {
Some(properties) => quote::quote! { , super_properties: &#properties },
None => Default::default(),
};
let mut transitions = Vec::<proc_macro2::TokenStream>::new();
for (transition_name, src_to_transitions) in self.transitions.iter() {
let mut transitions_matches = Vec::<proc_macro2::TokenStream>::new();
for (src_state, transition) in src_to_transitions {
let src_state_info = self
.states
.get(src_state)
.ok_or_else(|| unknown_state(src_state))?;
let transition_call_fn_args = match src_state_info.properties {
Some(_) => quote::quote! { properties },
None => Default::default(),
};
let src_field_names = src_state_info.field_names();
match transition {
TransitionInfo::ForwardTransition => {
let mut forwards = Vec::<proc_macro2::TokenStream>::new();
transitions.push(quote::quote!());
for group in src_state_info.groups.iter() {
if group.transitions.contains_key(transition_name) {
let id = snake_ident(&group.id);
forwards.push(quote::quote! { #id.#transition_name(event_dispatcher, #transition_call_fn_args); });
}
}
transitions_matches.push(quote::quote! (
#group_name :: #src_state #src_field_names => {
#(#forwards)*
}
));
}
TransitionInfo::SelfTransition {
destination_state,
action,
} => {
let dst_state_info = self
.states
.get(destination_state)
.ok_or_else(|| unknown_state(destination_state))?;
let prelude: proc_macro2::TokenStream;
let mut fields = Vec::<proc_macro2::TokenStream>::new();
for group in dst_state_info.groups.iter() {
let id = &group.id;
let name = &group.name;
if group_gen_info.properties.is_some() {
fields.push(quote::quote! { #id: #name::create(&properties), });
} else {
fields.push(quote::quote! { #id: #name::create(), });
}
}
if let Some(dst_properties_type) = &dst_state_info.properties {
if group_gen_info.properties.is_some() {
prelude = quote::quote! { let properties: #dst_properties_type = super_properties.into(); }
} else {
prelude = quote::quote! { let properties: #dst_properties_type = Default::default(); }
}
fields.push(quote::quote! { properties });
} else {
prelude = Default::default();
}
if fields.is_empty() {
transitions_matches.push(quote::quote! {
#group_name :: #src_state #src_field_names => {
#action
*self = #group_name :: #destination_state;
},
});
} else {
transitions_matches.push(quote::quote! {
#group_name :: #src_state #src_field_names => {
#prelude
#action
*self = #group_name :: #destination_state { #(#fields)* };
},
});
}
}
}
}
let event_dispatcher = &group_gen_info.event_dispatcher;
transitions.push(quote::quote! {
fn #transition_name(&mut self, event_dispatcher: &#event_dispatcher #transition_fn_args)
{
#[allow(unused_variables, unused_braces)]
match self
{
#(#transitions_matches)*
_ => {}
}
}
});
}
let visibility = &gen_info.visibility;
let id_transitions = group_gen_info.id_event.as_ref().unwrap_or(group_name);
let first_state = &self.first_state;
let first_state_info = self
.states
.get(first_state)
.ok_or_else(|| unknown_state(&self.first_state))?;
let (use_properties, initial_fields) = first_state_info.initialise_fields();
let (create_function_args, create_function_prelude) = match group_gen_info.properties {
Some(properties) => (
quote::quote! { super_properties: &#properties },
match use_properties {
true => quote::quote! { let properties = super_properties.into(); },
false => proc_macro2::TokenStream::new(),
},
),
None => (
proc_macro2::TokenStream::new(),
match first_state_info.properties {
Some(_) => quote::quote! { let properties = Default::default(); },
None => proc_macro2::TokenStream::new(),
},
),
};
let create_function = quote::quote! {
pub fn create(#create_function_args) -> Self
{
#create_function_prelude
Self::#first_state {
#initial_fields
}
}
};
Ok(quote::quote! {
#(#children)*
#visibility enum #group_name {
#(#states,)*
}
impl #group_name {
#create_function
}
impl #id_transitions {
#(#transitions)*
}
})
}
}
#[derive(Debug)]
pub(crate) struct ParsedState {
pub(crate) id: syn::Ident,
groups: Vec<ParsedStateGroup>,
pub(crate) properties: Option<proc_macro2::TokenStream>,
}
impl ParsedState {
pub(crate) fn parse(
input: brisk_it::component::ComponentInput,
manager: &brisk_it::generator::Manager,
) -> Result<ParsedState> {
let id = input.id.required(&input.name, "id")?;
let mut groups: Vec<ParsedStateGroup> = Default::default();
let mut properties: Option<proc_macro2::TokenStream> = None;
for prop in input.properties.into_iter() {
if prop.name == "properties" {
properties = Some(prop.expr.into_token_stream());
}
}
let state_child_count = input.children.iter().filter(|c| c.name == "State").count();
let group_child_count = input.children.iter().filter(|c| c.name == "Group").count();
if state_child_count != 0 && group_child_count != 0 {
return Err(
syn::Error::new(input.name.span(), "Cannot mix states and groups.")
.into_compile_error(),
);
}
if state_child_count != 0 {
let name = id.clone();
let id = syn::Ident::new("state", input.name.span());
let (first_state, states) = ParsedStateGroup::parse_states(input.children, manager)?;
let transitions = ParsedStateGroup::parse_transitions(input.on_expressions, &states)?;
let psg = ParsedStateGroup {
id,
name,
first_state,
states,
transitions,
};
groups.push(psg);
} else if group_child_count != 0 {
groups = input
.children
.into_iter()
.map(|c| ParsedStateGroup::parse(c, manager))
.collect::<Result<_>>()?;
}
Ok(ParsedState {
id,
groups,
properties,
})
}
pub(crate) fn events(&self) -> Vec<syn::Ident> {
self.groups
.iter()
.flat_map(|x| x.transitions.keys())
.cloned()
.collect()
}
pub(crate) fn generate(&self, gen_info: &GenerationInfo) -> Result<proc_macro2::TokenStream> {
if self.groups.is_empty() {
Ok(proc_macro2::TokenStream::new())
} else {
let mut children = Vec::<proc_macro2::TokenStream>::new();
for group in self.groups.iter() {
children.push(group.generate(
gen_info,
crate::state::GroupGenerationInfo {
id_event: &None,
properties: &self.properties,
event_dispatcher: &gen_info.event_dispatcher,
},
)?);
}
Ok(quote::quote! {
#(#children)*
})
}
}
fn initialise_fields(&self) -> (bool, proc_macro2::TokenStream) {
let mut fields = Vec::<proc_macro2::TokenStream>::new();
let (mut use_properties, states_init) = self.initialise_states(false);
fields.push(states_init);
if self.properties.is_some() {
use_properties = true;
fields.push(quote::quote! { properties, });
}
(use_properties, quote::quote! { #(#fields)* })
}
pub(crate) fn initialise_states(&self, ref_cell: bool) -> (bool, proc_macro2::TokenStream) {
let mut fields = Vec::<proc_macro2::TokenStream>::new();
let mut use_properties = false;
for group in self.groups.iter() {
let id = &group.id;
let name = &group.name;
if ref_cell {
if self.properties.is_some() {
use_properties = true;
fields.push(
quote::quote! { #id: std::cell::RefCell::new(#name::create(&properties)), },
);
} else {
fields.push(quote::quote! { #id: std::cell::RefCell::new(#name::create()), });
}
} else if self.properties.is_some() {
use_properties = true;
fields.push(quote::quote! { #id: #name::create(super_properties), });
} else {
fields.push(quote::quote! { #id: #name::create(), });
}
}
(use_properties, quote::quote! { #(#fields)* })
}
fn generate_enum_field(&self) -> Result<proc_macro2::TokenStream> {
let mut fields = Vec::<proc_macro2::TokenStream>::new();
if let Some(properties) = &self.properties {
fields.push(quote::quote! {
properties: #properties,
});
}
for group in self.groups.iter() {
let id = &group.id;
let name = &group.name;
fields.push(quote::quote! { #id: #name, });
}
let id = &self.id;
if fields.is_empty() {
Ok(quote::quote! { #id })
} else {
Ok(quote::quote! { #id { #(#fields)* } })
}
}
fn field_names(&self) -> proc_macro2::TokenStream {
if self.properties.is_none() {
if self.groups.is_empty() {
proc_macro2::TokenStream::new()
} else {
let gn = self.groups.iter().map(|g| {
let id = snake_ident(&g.id);
quote::quote! { #id, }
});
quote::quote! { {#(#gn)*} }
}
} else {
let gn = self.groups.iter().map(|g| {
let id = snake_ident(&g.id);
quote::quote! { #id, }
});
quote::quote! { {properties, #(#gn)*} }
}
}
}