#![doc = include_str!("../README.md")]
use proc_macro::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, Fields};
#[proc_macro_derive(Rotalubat)]
pub fn rotalubat_derive(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let Data::Enum(data) = &input.data else {
return syn::Error::new_spanned(&input, "Rotalubat can only be derived for enums")
.to_compile_error()
.into();
};
for variant in &data.variants {
let Fields::Unit = variant.fields else {
return syn::Error::new_spanned(
variant,
"Rotalubat does not support enum variants with fields",
)
.to_compile_error()
.into();
};
}
let variants: Vec<&syn::Ident> = data.variants.iter().map(|v| &v.ident).collect();
if variants.is_empty() {
return syn::Error::new_spanned(&input, "Rotalubat requires at least one variant")
.to_compile_error()
.into();
}
let next_variants = variants.iter().cycle().skip(1);
let forward_arms = variants.iter().zip(next_variants).map(|(current, next)| {
quote! { #name::#current => #name::#next }
});
let prev_variants = variants.iter().cycle().skip(variants.len() - 1);
let backward_arms = variants.iter().zip(prev_variants).map(|(current, prev)| {
quote! { #name::#current => #name::#prev }
});
let expanded = quote! {
impl #name {
#[inline]
pub fn forward(&mut self) {
*self = match self {
#(#forward_arms),*
};
}
#[inline]
pub fn backward(&mut self) {
*self = match self {
#(#backward_arms),*
};
}
}
};
TokenStream::from(expanded)
}