affn-derive 0.10.0

Derive macros for affn: ReferenceFrame and ReferenceCenter
Documentation
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{DeriveInput, Expr, Lit, Meta, Type};

pub(crate) struct CenterAttributes {
    pub name: Option<String>,
    pub params: Option<Type>,
    pub affine: bool,
}

impl Default for CenterAttributes {
    fn default() -> Self {
        Self {
            name: None,
            params: None,
            affine: true,
        }
    }
}

pub(crate) fn derive_reference_center_impl(input: DeriveInput) -> syn::Result<TokenStream2> {
    let name = &input.ident;

    let attrs = parse_center_attributes(&input)?;

    let name_expr = match attrs.name {
        Some(custom_name) => quote! { #custom_name },
        None => {
            let name_str = name.to_string();
            quote! { #name_str }
        }
    };

    let params_type = match attrs.params {
        Some(ty) => quote! { #ty },
        None => quote! { () },
    };

    let affine_impl = if attrs.affine {
        quote! {
            impl ::affn::centers::AffineCenter for #name {}
        }
    } else {
        quote! {}
    };

    let expanded = quote! {
        impl ::affn::centers::ReferenceCenter for #name {
            type Params = #params_type;

            fn center_name() -> &'static str {
                #name_expr
            }
        }

        #affine_impl
    };

    Ok(expanded)
}

pub(crate) fn parse_center_attributes(input: &DeriveInput) -> syn::Result<CenterAttributes> {
    let mut attrs = CenterAttributes::default();

    for attr in &input.attrs {
        if attr.path().is_ident("center") {
            let nested = attr.parse_args_with(
                syn::punctuated::Punctuated::<Meta, syn::Token![,]>::parse_terminated,
            )?;

            for meta in nested {
                match &meta {
                    Meta::NameValue(nv) => {
                        if nv.path.is_ident("name") {
                            if let Expr::Lit(expr_lit) = &nv.value {
                                if let Lit::Str(lit_str) = &expr_lit.lit {
                                    attrs.name = Some(lit_str.value());
                                    continue;
                                }
                            }
                            return Err(syn::Error::new_spanned(
                                &nv.value,
                                "expected string literal for `name`",
                            ));
                        } else if nv.path.is_ident("params") {
                            if let Expr::Path(expr_path) = &nv.value {
                                attrs.params = Some(Type::Path(syn::TypePath {
                                    qself: None,
                                    path: expr_path.path.clone(),
                                }));
                                continue;
                            }
                            return Err(syn::Error::new_spanned(
                                &nv.value,
                                "expected type for `params`",
                            ));
                        } else if nv.path.is_ident("affine") {
                            if let Expr::Lit(expr_lit) = &nv.value {
                                if let Lit::Bool(lit_bool) = &expr_lit.lit {
                                    attrs.affine = lit_bool.value();
                                    continue;
                                }
                            }
                            return Err(syn::Error::new_spanned(
                                &nv.value,
                                "expected boolean for `affine`",
                            ));
                        } else {
                            return Err(syn::Error::new_spanned(
                                &nv.path,
                                format!(
                                    "unknown `center` attribute `{}`; \
                                     known attributes are: name, params, affine",
                                    nv.path
                                        .get_ident()
                                        .map(|id| id.to_string())
                                        .unwrap_or_else(|| "<unknown>".into())
                                ),
                            ));
                        }
                    }
                    _ => {
                        return Err(syn::Error::new_spanned(
                            &meta,
                            "unknown `center` attribute; expected a name=value attribute \
                             such as `name = \"...\"`, `params = MyType`, or `affine = true`",
                        ));
                    }
                }
            }
        }
    }

    Ok(attrs)
}