| 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(()) | 
 |     } | 
 | } |