blob: b4646f984d471d8f7c6196c126556ec0932a821b [file] [log] [blame]
use heck::ToUpperCamelCase;
use proc_macro2 as pm2;
use proc_macro2::{Ident, Span};
use quote::quote;
use syn::spanned::Spanned;
use syn::{Fields, ItemEnum, Variant};
use crate::{ALL_OPERATIONS, base_name};
/// Implement `#[function_enum]`, see documentation in `lib.rs`.
pub fn function_enum(
mut item: ItemEnum,
attributes: pm2::TokenStream,
) -> syn::Result<pm2::TokenStream> {
expect_empty_enum(&item)?;
let attr_span = attributes.span();
let mut attr = attributes.into_iter();
// Attribute should be the identifier of the `BaseName` enum.
let Some(tt) = attr.next() else {
return Err(syn::Error::new(attr_span, "expected one attribute"));
};
let pm2::TokenTree::Ident(base_enum) = tt else {
return Err(syn::Error::new(tt.span(), "expected an identifier"));
};
if let Some(tt) = attr.next() {
return Err(syn::Error::new(
tt.span(),
"unexpected token after identifier",
));
}
let enum_name = &item.ident;
let mut as_str_arms = Vec::new();
let mut from_str_arms = Vec::new();
let mut base_arms = Vec::new();
for func in ALL_OPERATIONS.iter() {
let fn_name = func.name;
let ident = Ident::new(&fn_name.to_upper_camel_case(), Span::call_site());
let bname_ident = Ident::new(&base_name(fn_name).to_upper_camel_case(), Span::call_site());
// Match arm for `fn as_str(self)` matcher
as_str_arms.push(quote! { Self::#ident => #fn_name });
from_str_arms.push(quote! { #fn_name => Self::#ident });
// Match arm for `fn base_name(self)` matcher
base_arms.push(quote! { Self::#ident => #base_enum::#bname_ident });
let variant = Variant {
attrs: Vec::new(),
ident,
fields: Fields::Unit,
discriminant: None,
};
item.variants.push(variant);
}
let variants = item.variants.iter();
let res = quote! {
// Instantiate the enum
#item
impl #enum_name {
/// All variants of this enum.
pub const ALL: &[Self] = &[
#( Self::#variants, )*
];
/// The stringified version of this function name.
pub const fn as_str(self) -> &'static str {
match self {
#( #as_str_arms , )*
}
}
/// If `s` is the name of a function, return it.
pub fn from_str(s: &str) -> Option<Self> {
let ret = match s {
#( #from_str_arms , )*
_ => return None,
};
Some(ret)
}
/// The base name enum for this function.
pub const fn base_name(self) -> #base_enum {
match self {
#( #base_arms, )*
}
}
/// Return information about this operation.
pub fn math_op(self) -> &'static crate::op::MathOpInfo {
crate::op::ALL_OPERATIONS.iter().find(|op| op.name == self.as_str()).unwrap()
}
}
};
Ok(res)
}
/// Implement `#[base_name_enum]`, see documentation in `lib.rs`.
pub fn base_name_enum(
mut item: ItemEnum,
attributes: pm2::TokenStream,
) -> syn::Result<pm2::TokenStream> {
expect_empty_enum(&item)?;
if !attributes.is_empty() {
let sp = attributes.span();
return Err(syn::Error::new(sp.span(), "no attributes expected"));
}
let mut base_names: Vec<_> = ALL_OPERATIONS
.iter()
.map(|func| base_name(func.name))
.collect();
base_names.sort_unstable();
base_names.dedup();
let item_name = &item.ident;
let mut as_str_arms = Vec::new();
for base_name in base_names {
let ident = Ident::new(&base_name.to_upper_camel_case(), Span::call_site());
// Match arm for `fn as_str(self)` matcher
as_str_arms.push(quote! { Self::#ident => #base_name });
let variant = Variant {
attrs: Vec::new(),
ident,
fields: Fields::Unit,
discriminant: None,
};
item.variants.push(variant);
}
let res = quote! {
// Instantiate the enum
#item
impl #item_name {
/// The stringified version of this base name.
pub const fn as_str(self) -> &'static str {
match self {
#( #as_str_arms ),*
}
}
}
};
Ok(res)
}
/// Verify that an enum is empty, otherwise return an error
fn expect_empty_enum(item: &ItemEnum) -> syn::Result<()> {
if !item.variants.is_empty() {
Err(syn::Error::new(
item.variants.span(),
"expected an empty enum",
))
} else {
Ok(())
}
}