1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#![recursion_limit = "128"]

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

use proc_macro::TokenStream;
use syn::spanned::Spanned;
use syn::{Data, DataStruct, DeriveInput, Fields, Attribute, Meta, MetaList, MetaNameValue, NestedMeta};

#[proc_macro_derive(Vertex, attributes(glium))]
pub fn glium_vertex_derive(input: TokenStream) -> TokenStream {
    let ast = syn::parse(input).unwrap();

    // Build the impl
    impl_glium_vertex_derive(&ast)
}

fn impl_glium_vertex_derive(ast: &DeriveInput) -> TokenStream {
    let struct_name = &ast.ident;
    let generics = &ast.generics;
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    let fields = match ast.data {
        Data::Struct(DataStruct {
            fields: Fields::Named(ref fields),
            ..
        }) => {
            &fields.named
        },
        _ => {
            panic!("#[derive(Vertex)] only defined for structs.");
        }
    };

    let bindings = fields.iter().map(|field| {
        let attributes = field.attrs.iter()
            .flat_map(Attribute::interpret_meta)
            .flat_map(|meta| {
                match meta {
                    Meta::List(MetaList { ref ident, ref nested, .. }) if ident == "glium" => {
                        nested.iter().cloned().collect()
                    },
                    _ => {
                        Vec::new()
                    }
                }
            }).collect::<Vec<_>>();

        println!("Attrs: {:?}", &attributes);

        let field_name = &field.ident;
        let mut vertex_attr_name = quote!(#field_name);
        let mut normalize = false;

        for meta in &attributes {
            match meta {
                NestedMeta::Meta(Meta::NameValue(MetaNameValue { ref ident, ref lit, .. })) => {
                    if quote!(#ident).to_string() == "attr" {
                        vertex_attr_name = quote!(#lit);
                    } else {
                        panic!("Unknown field attribute {}", ident);
                    }
                },
                NestedMeta::Meta(Meta::Word(ref ident)) => {
                    if quote!(#ident).to_string() == "normalize" {
                        normalize = true;
                    } else {
                        panic!("Unknown field attribute {}", ident);
                    }
                },
                _ => (),
            }
        };

        quote_spanned! {field.span()=>
            (
                Cow::Borrowed(#vertex_attr_name),
                {
                    // calculate the offset of the struct fields
                    let dummy: #struct_name = unsafe { ::std::mem::uninitialized() };
                    let offset: usize = {
                        let dummy_ref = &dummy;
                        let field_ref = &dummy.#field_name;
                        (field_ref as *const _ as usize) - (dummy_ref as *const _ as usize)
                    };
                    // NOTE: `glium::vertex::Vertex` requires `#struct_name` to have `Copy` trait
                    // `Copy` excludes `Drop`, so we don't have to `std::mem::forget(dummy)`
                    offset
                },
                {
                    fn attr_type_of_val<T: ::glium::vertex::Attribute>(_: &T) -> ::glium::vertex::AttributeType {
                        <T as ::glium::vertex::Attribute>::get_type()
                    }
                    let dummy: &#struct_name = unsafe { ::std::mem::transmute(0usize) };
                    attr_type_of_val(&dummy.#field_name)
                },
                #normalize
            )
        }
    });

    let stream = quote! {
        impl #impl_generics ::glium::vertex::Vertex for #struct_name #ty_generics #where_clause {
            #[inline]
            fn build_bindings() -> ::glium::vertex::VertexFormat {
                use std::borrow::Cow;

                // TODO: use a &'static [] if possible
                Cow::Owned(vec![
                    #(
                        #bindings,
                    )*
                ])
            }
        }
    };

    stream.into()
}