#![deny(unsafe_code)]
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{Data, DeriveInput, Fields, parse_macro_input, spanned::Spanned};
#[proc_macro_derive(FsmState)]
pub fn derive_fsm_state(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match fsm_state_impl(input) {
Ok(ts) => ts.into(),
Err(e) => e.to_compile_error().into(),
}
}
fn fsm_state_impl(input: DeriveInput) -> syn::Result<TokenStream2> {
let name = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let data_enum = match &input.data {
Data::Enum(e) => e,
_ => {
return Err(syn::Error::new(
input.ident.span(),
"`#[derive(FsmState)]` can only be applied to enums",
));
}
};
for variant in &data_enum.variants {
match &variant.fields {
Fields::Unit => {}
_ => {
return Err(syn::Error::new(
variant.span(),
"`#[derive(FsmState)]` only supports unit variants (no fields). \
Tuple and struct variants are not supported.",
));
}
}
}
let as_key_arms = data_enum.variants.iter().map(|v| {
let ident = &v.ident;
let key = ident.to_string();
quote! { #name::#ident => #key }
});
let from_key_arms = data_enum.variants.iter().map(|v| {
let ident = &v.ident;
let key = ident.to_string();
quote! { #key => ::std::option::Option::Some(#name::#ident) }
});
Ok(quote! {
#[automatically_derived]
impl #impl_generics ::ferogram::fsm::FsmState
for #name #ty_generics
#where_clause
{
fn as_key(&self) -> ::std::string::String {
match self {
#(#as_key_arms),*
}
.to_string()
}
fn from_key(key: &str) -> ::std::option::Option<Self> {
match key {
#(#from_key_arms),*
_ => ::std::option::Option::None,
}
}
}
})
}