djin_protocol_derive/
lib.rs

1#![recursion_limit = "128"]
2
3extern crate proc_macro;
4extern crate proc_macro2;
5extern crate syn;
6#[macro_use]
7extern crate quote;
8
9mod attr;
10mod codegen;
11mod format;
12mod plan;
13
14use proc_macro::TokenStream;
15
16#[proc_macro_derive(Protocol, attributes(protocol))]
17pub fn protocol(input: TokenStream) -> TokenStream {
18    // Parse the string representation
19    let ast: syn::DeriveInput = syn::parse(input).unwrap();
20
21    // Build the impl
22    let gen = impl_parcel(&ast);
23
24    // Return the generated impl
25    gen.to_string().parse().expect("Could not parse generated parcel impl")
26}
27
28// The `Parcel` trait is used for data that can be sent/received.
29fn impl_parcel(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
30    match ast.data {
31        syn::Data::Struct(ref s) => impl_parcel_for_struct(ast, s),
32        syn::Data::Enum(ref e) => {
33            let plan = plan::Enum::new(ast, e);
34
35            let mut stream = impl_parcel_for_enum(&plan, ast);
36            stream.extend(impl_enum_for_enum(&plan, ast));
37            stream
38        },
39        syn::Data::Union(..) => unimplemented!(),
40    }
41}
42
43/// Builds generics for a new impl.
44///
45/// Returns `(generics, where_predicates)`
46fn build_generics(ast: &syn::DeriveInput) -> (Vec<proc_macro2::TokenStream>, Vec<proc_macro2::TokenStream>) {
47    use quote::ToTokens;
48
49    let mut where_predicates = Vec::new();
50    let mut generics = Vec::new();
51
52    generics.extend(ast.generics.type_params().map(|t| {
53        let (ident, bounds) = (&t.ident, &t.bounds);
54        where_predicates.push(quote!(#ident : djin_protocol::Parcel + #bounds));
55        quote!(#ident)
56    }));
57
58    generics.extend(ast.generics.lifetimes().enumerate().map(|(i, _)| {
59        let letter = ('a' as u8 + i as u8) as char;
60        quote!(#letter)
61    }));
62
63    if let Some(where_clause) = ast.generics.where_clause.clone() {
64        where_predicates.push(where_clause.predicates.into_token_stream());
65    }
66
67    assert!(ast.generics.const_params().next().is_none(),
68            "constant parameters are not supported yet");
69
70    (generics, where_predicates)
71}
72
73fn impl_parcel_for_struct(ast: &syn::DeriveInput,
74                          strukt: &syn::DataStruct) -> proc_macro2::TokenStream {
75    let strukt_name = &ast.ident;
76    let read_fields = codegen::read_struct_field(&strukt.fields);
77    let write_fields = codegen::write_fields(&strukt.fields);
78    let named_field_variables = codegen::named_fields_declarations(&strukt.fields);
79
80    impl_trait_for(ast, quote!(djin_protocol::Parcel), quote! {
81        const TYPE_NAME: &'static str = stringify!(#strukt_name);
82
83        #[allow(unused_variables)]
84        fn read_field(__io_reader: &mut io::Read,
85                      __settings: &djin_protocol::Settings,
86                      _: &mut djin_protocol::hint::Hints)
87            -> Result<Self, djin_protocol::Error> {
88            // Each type gets its own hints.
89            let mut __hints = djin_protocol::hint::Hints::default();
90            __hints.begin_fields();
91            #named_field_variables
92            Ok(#strukt_name # read_fields)
93        }
94
95        #[allow(unused_variables)]
96        fn write_field(&self, __io_writer: &mut io::Write,
97                       __settings: &djin_protocol::Settings,
98                       _: &mut djin_protocol::hint::Hints)
99            -> Result<(), djin_protocol::Error> {
100            // Each type gets its own hints.
101            let mut __hints = djin_protocol::hint::Hints::default();
102            __hints.begin_fields();
103
104            #write_fields
105            Ok(())
106        }
107    })
108}
109
110/// Generates a `Parcel` trait implementation for an enum.
111fn impl_parcel_for_enum(plan: &plan::Enum,
112                        ast: &syn::DeriveInput)
113    -> proc_macro2::TokenStream {
114
115    let enum_name = &plan.ident;
116    let read_variant = codegen::enums::read_variant(plan);
117    let write_variant = codegen::enums::write_variant(plan);
118
119    impl_trait_for(ast, quote!(djin_protocol::Parcel), quote! {
120        const TYPE_NAME: &'static str = stringify!(#enum_name);
121
122        #[allow(unused_variables)]
123        fn read_field(__io_reader: &mut io::Read,
124                      __settings: &djin_protocol::Settings,
125                      _: &mut djin_protocol::hint::Hints)
126            -> Result<Self, djin_protocol::Error> {
127            // Each type gets its own hints.
128            let mut __hints = djin_protocol::hint::Hints::default();
129            __hints.begin_fields();
130
131            Ok(#read_variant)
132        }
133
134        #[allow(unused_variables)]
135        fn write_field(&self, __io_writer: &mut io::Write,
136                       __settings: &djin_protocol::Settings,
137                       _: &mut djin_protocol::hint::Hints)
138            -> Result<(), djin_protocol::Error> {
139            // Each type gets its own hints.
140            let mut __hints = djin_protocol::hint::Hints::default();
141            __hints.begin_fields();
142
143            #write_variant
144
145            Ok(())
146        }
147    })
148}
149
150fn impl_enum_for_enum(plan: &plan::Enum,
151                      ast: &syn::DeriveInput)
152    -> proc_macro2::TokenStream {
153    let enum_ident = &plan.ident;
154    let discriminant = plan.discriminant();
155
156    let variant_matchers = plan.variants.iter().map(|variant| {
157        let variant_ident = &variant.ident;
158        let discriminator = variant.discriminator_expr();
159        let fields_expr = variant.ignore_fields_pattern_expr();
160
161        quote!(#enum_ident::#variant_ident #fields_expr => {
162            #discriminator
163        })
164    });
165
166    impl_trait_for(ast, quote!(djin_protocol::Enum), quote!(
167        type Discriminant = #discriminant;
168
169        fn discriminator(&self) -> Self::Discriminant {
170            match *self {
171                #(#variant_matchers)*
172            }
173        }
174    ))
175}
176
177/// Wraps a stream of tokens in an anonymous constant block.
178///
179/// Inside this block, the protocol crate accessible.
180fn anonymous_constant_block(description: &str,
181                            item_name: &syn::Ident,
182                            body: proc_macro2::TokenStream)
183    -> proc_macro2::TokenStream {
184    let anon_const_name = syn::Ident::new(&format!("__{}_FOR_{}",
185                                                   description.replace(" ", "_").replace("::", "_"),
186                                                   item_name.to_owned()),
187                                          proc_macro2::Span::call_site());
188
189    quote! {
190        #[allow(non_upper_case_globals)]
191        const #anon_const_name: () = {
192            extern crate djin_protocol;
193            use std::io;
194
195            #body
196        };
197    }
198}
199
200fn impl_trait_for(ast: &syn::DeriveInput,
201                  trait_name: proc_macro2::TokenStream,
202                  impl_body: proc_macro2::TokenStream)
203    -> proc_macro2::TokenStream {
204    let item_name = &ast.ident;
205    let description = format!("impl {}", trait_name);
206
207    let (generics, where_predicates) = build_generics(ast);
208    let (generics, where_predicates) = (&generics, where_predicates);
209
210    anonymous_constant_block(&description, item_name, quote! {
211        impl < #(#generics),* > #trait_name for #item_name < #(#generics),* >
212            where #(#where_predicates),* {
213            #impl_body
214        }
215    })
216}
217