glsl-layout-derive 0.1.0

Custom derive for `glsl-layout` crate.
Documentation
#![recursion_limit="128"]

extern crate proc_macro;
extern crate syn;
#[macro_use]
extern crate quote;

use proc_macro::TokenStream;

#[proc_macro_derive(Uniform)]
pub fn uniform(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();
    impl_uniform(&ast).into()
}

fn impl_uniform(ast: &syn::DeriveInput) -> quote::Tokens {
    let name = &ast.ident;

    let rname = syn::Ident::from(format!("LayoutStd140{}", name));
    
    let fields = match &ast.data {
        syn::Data::Struct(syn::DataStruct {
            fields: syn::Fields::Named(syn::FieldsNamed {
                named,
                ..
            }),
            ..
        }) => named,
        _ => panic!(),
    };

    let aligned_fields = fields.iter().flat_map(|field| {
        let (a, f) = aligned_field(field);
        vec![a, f]
    });

    let field_names = fields.iter().map(|field| field.ident.unwrap());
    let field_names2 = fields.iter().map(|field| field.ident.unwrap());

    let dummy = syn::Ident::from(format!("_GLSL_LAYOUT_{}", name));

    #[cfg(feature="gfx")]
    let pod = (Some(syn::token::Unsafe::default()), Some((None, syn::Path {
                leading_colon: None,
                segments: once(syn::PathSegment::from("_glsl_layout"))
                    .chain(once("Pod".into()))
                    .collect(),
            }, syn::token::For::default())));

    #[cfg(not(feature="gfx"))]
    let pod = (None, None);

    let rname_impl = syn::ItemImpl {
        attrs: Vec::new(),
        defaultness: None,
        unsafety: pod.0,
        impl_token: syn::token::Impl::default(),
        generics: syn::Generics::default(),
        trait_: pod.1,
        self_ty: Box::new(syn::Type::from(syn::TypePath {
            qself: None,
            path: syn::Path::from(rname.clone()),
        })),
        brace_token: syn::token::Brace::default(),
        items: Vec::new(),
    };

    quote! {
        #[allow(bad_style)]
        const #dummy: () = {
            extern crate glsl_layout as _glsl_layout;

            #[repr(C, align(16))]
            #[derive(Clone, Copy, Debug, Default)]
            pub struct #rname {#(
                #aligned_fields,
            )*}

            #rname_impl

            unsafe impl _glsl_layout::Std140 for #rname {}

            impl _glsl_layout::Uniform for #rname {
                type Align = _glsl_layout::align::Align16;
                type Std140 = #rname;

                fn align() -> _glsl_layout::align::Align16 { _glsl_layout::align::Align16 }
                fn std140(&self) -> #rname {
                    self.clone()
                }
            }

            impl _glsl_layout::Uniform for #name {
                type Align = _glsl_layout::align::Align16;
                type Std140 = #rname;

                fn align() -> _glsl_layout::align::Align16 { _glsl_layout::align::Align16 }
                fn std140(&self) -> #rname {
                    #rname {
                        #(#field_names: self.#field_names2.std140(),)*
                        ..Default::default()
                    }
                }
            }
        };
    }
}

fn aligned_field(field: &syn::Field) -> (syn::Field, syn::Field) {
    let name = field.ident.unwrap();
    let align = syn::Field {
        ty: syn::Type::Path(align_type_for(&field.ty)),
        ident: Some(format!("_align_{}", name).into()),
        attrs: Vec::new(),
        vis: syn::Visibility::Inherited,
        colon_token: Some(Default::default()),
    };

    let std140 = syn::Field {
        ty: syn::Type::Path(std140_type_for(&field.ty)),
        ..field.clone()
    };

    (align, std140)
}

fn align_type_for(aligned: &syn::Type) -> syn::TypePath {
    use std::iter::once;
    syn::TypePath {
        qself: Some(syn::QSelf {
            lt_token: Default::default(),
            ty: Box::new(aligned.clone()),
            position: 2,
            as_token: Some(Default::default()),
            gt_token: Default::default(),
        }),
        path: syn::Path {
            leading_colon: None,
            segments: once(syn::PathSegment::from("_glsl_layout"))
                .chain(once("Uniform".into()))
                .chain(once("Align".into()))
                .collect(),
        }
    }
}

fn std140_type_for(aligned: &syn::Type) -> syn::TypePath {
    use std::iter::once;
    syn::TypePath {
        qself: Some(syn::QSelf {
            lt_token: Default::default(),
            ty: Box::new(aligned.clone()),
            position: 2,
            as_token: Some(Default::default()),
            gt_token: Default::default(),
        }),
        path: syn::Path {
            leading_colon: None,
            segments: once(syn::PathSegment::from("_glsl_layout"))
                .chain(once("Uniform".into()))
                .chain(once("Std140".into()))
                .collect(),
        }
    }
}