mev-proc-impl 0.1.1

mev procedural macros implementation
Documentation
use proc_easy::EasyAttributes;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{self, spanned::Spanned};

proc_easy::easy_token!(vertex);
proc_easy::easy_token!(constant);
proc_easy::easy_token!(instance);
proc_easy::easy_token!(skip);

proc_easy::easy_argument_value!(
    struct InstanceRate {
        instance: instance,
        rate: syn::LitInt,
    }
);

proc_easy::easy_argument_group! {
    enum StepMode {
        Vertex(vertex),
        Constant(constant),
        Instance(InstanceRate),
    }
}

proc_easy::easy_attributes! {
    @(mev)
    struct TypeAttributes {
        pub step_mode: Option<StepMode>,
    }
}

proc_easy::easy_attributes! {
    @(mev)
    pub struct FieldAttributes {
        pub skip: Option<skip>,
    }
}

pub fn derive(input: &syn::DeriveInput, mev: &TokenStream) -> syn::Result<TokenStream> {
    let name = &input.ident;
    let vis = &input.vis;

    if !input.generics.params.is_empty() {
        return Err(syn::Error::new_spanned(
            &input.generics,
            "generic arguments are not supported by `#[derive(VertexBinding)]`",
        ));
    }

    let data = match &input.data {
        syn::Data::Struct(data) => data,
        _ => {
            return Err(syn::Error::new_spanned(
                input,
                "only structs are supported by `#[derive(VertexBinding)]`",
            ))
        }
    };

    let type_attrs = TypeAttributes::parse(&input.attrs, input.span())?;

    let step_mode = match type_attrs.step_mode {
        Some(StepMode::Vertex(_)) | None => quote! { #mev::for_macro::VertexStepMode::Vertex },
        Some(StepMode::Constant(_)) => quote! { #mev::for_macro::VertexStepMode::Constant },
        Some(StepMode::Instance(rate)) => {
            let rate = rate.rate;
            quote! { #mev::for_macro::VertexStepMode::Instance { rate: #rate } }
        }
    };

    let name_descs = quote::format_ident!("__MevGenerated{}AttributeDescs", name);

    let field_attrs = data
        .fields
        .iter()
        .map(|field| FieldAttributes::parse(&field.attrs, field.span()))
        .collect::<Result<Vec<_>, _>>()?;

    let field_types = data
        .fields
        .iter()
        .zip(field_attrs.iter())
        .filter_map(|(field, attr)| {
            if attr.skip.is_none() {
                Some(&field.ty)
            } else {
                None
            }
        })
        .collect::<Vec<_>>();

    match data.fields {
        syn::Fields::Named(_) => {
            let field_names = data
                .fields
                .iter()
                .zip(field_attrs.iter())
                .filter_map(|(field, attr)| {
                    if attr.skip.is_none() {
                        Some(&field.ident)
                    } else {
                        None
                    }
                })
                .collect::<Vec<_>>();

            let tokens = quote::quote! {
                #[allow(unused, non_camel_case_types)]
                #[doc(hidden)]
                #[derive(Clone, Copy, Debug)]
                #vis struct #name_descs {
                    #(
                        #[allow(non_snake_case)]
                        #field_names: #mev::for_macro::VertexAttributeDescs<#field_types>,
                    )*
                }

                impl #mev::for_macro::VertexBinding for #name {
                    const LAYOUT: #mev::for_macro::VertexLayoutDesc = #mev::for_macro::VertexLayoutDesc {
                        stride: std::mem::size_of::<Self>() as u32,
                        step_mode: #step_mode,
                    };

                    type AttributeDescs = #name_descs;

                    fn descs(buffer_index: u32) -> #name_descs {
                        #name_descs {
                            #(
                                #field_names: #mev::for_macro::VertexAttributeDescs::new(buffer_index, #mev::for_macro::offset_of!(Self, #field_names)),
                            )*
                        }
                    }
                }
            };

            Ok(tokens)
        }
        _ => unimplemented!("only named fields are supported by `#[derive(VertexBinding)]`"),
    }
}