#![recursion_limit="128"]
extern crate proc_macro;
extern crate proc_macro2;
#[macro_use]
extern crate quote;
extern crate syn;
use proc_macro::TokenStream;
use proc_macro2::Span;
use syn::{Data, Fields, Ident, Lit, Meta, NestedMeta};
#[proc_macro_derive(Header, attributes(header))]
pub fn derive_header(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
impl_header(&ast).into()
}
fn impl_header(ast: &syn::DeriveInput) -> proc_macro2::TokenStream {
let fns = match impl_fns(ast) {
Ok(fns) => fns,
Err(msg) => {
return quote! {
compile_error!(#msg);
}.into();
}
};
let decode = fns.decode;
let encode = fns.encode;
let ty = &ast.ident;
let hname = fns.name.unwrap_or_else(|| {
to_header_name(&ty.to_string())
});
let hname_ident = Ident::new(&hname, Span::call_site());
let dummy_const = Ident::new(&format!("_IMPL_HEADER_FOR_{}", hname), Span::call_site());
let impl_block = quote! {
impl __hc::Header for #ty {
fn name() -> &'static __hc::HeaderName {
&__hc::header::#hname_ident
}
fn decode<'i, I>(values: &mut I) -> Result<Self, __hc::Error>
where
I: Iterator<Item = &'i __hc::HeaderValue>,
{
#decode
}
fn encode<E: Extend<__hc::HeaderValue>>(&self, values: &mut E) {
#encode
}
}
};
quote! {
const #dummy_const: () = {
extern crate headers_core as __hc;
#impl_block
};
}
}
struct Fns {
encode: proc_macro2::TokenStream,
decode: proc_macro2::TokenStream,
name: Option<String>,
}
fn impl_fns(ast: &syn::DeriveInput) -> Result<Fns, String> {
let ty = &ast.ident;
let st = match ast.data {
Data::Struct(ref st) => st,
_ => {
return Err("derive(Header) only works on structs".into())
}
};
let mut name = None;
for attr in &ast.attrs {
if attr.path.segments.len() != 1 {
continue;
}
if attr.path.segments[0].ident != "header" {
continue;
}
match attr.parse_meta() {
Ok(Meta::List(list)) => {
for meta in &list.nested {
match meta {
NestedMeta::Meta(Meta::NameValue(ref kv)) if kv.path.is_ident("name_const") => {
if name.is_some() {
return Err("repeated 'name_const' option in #[header] attribute".into());
}
name = match kv.lit {
Lit::Str(ref s) => Some(s.value()),
_ => {
return Err("illegal literal in #[header(name_const = ..)] attribute".into());
}
};
}
_ => {
return Err("illegal option in #[header(..)] attribute".into())
}
}
}
},
Ok(Meta::NameValue(_)) => {
return Err("illegal #[header = ..] attribute".into())
},
Ok(Meta::Path(_)) => {
return Err("empty #[header] attributes do nothing".into())
},
Err(e) => {
return Err(format!("illegal #[header ??] attribute: {:?}", e))
}
}
}
let decode_res = quote! {
::util::TryFromValues::try_from_values(values)
};
let (decode, encode_name) = match st.fields {
Fields::Named(ref fields) => {
if fields.named.len() != 1 {
return Err("derive(Header) doesn't support multiple fields".into());
}
let field = fields
.named
.iter()
.next()
.expect("just checked for len() == 1");
let field_name = field.ident.as_ref().unwrap();
let decode = quote! {
#decode_res
.map(|inner| #ty {
#field_name: inner,
})
};
let encode_name = Ident::new(&field_name.to_string(), Span::call_site());
(decode, Value::Named(encode_name))
},
Fields::Unnamed(ref fields) => {
if fields.unnamed.len() != 1 {
return Err("derive(Header) doesn't support multiple fields".into());
}
let decode = quote! {
#decode_res
.map(#ty)
};
(decode, Value::Unnamed)
},
Fields::Unit => {
return Err("derive(Header) doesn't support unit structs".into())
}
};
let encode = {
let field = if let Value::Named(field) = encode_name {
quote! {
(&self.#field)
}
} else {
quote! {
(&self.0)
}
};
quote! {
values.extend(::std::iter::once((#field).into()));
}
};
Ok(Fns {
decode,
encode,
name,
})
}
fn to_header_name(ty_name: &str) -> String {
let mut out = String::new();
let mut first = true;
for c in ty_name.chars() {
if first {
out.push(c.to_ascii_uppercase());
first = false;
} else {
if c.is_uppercase() {
out.push('_');
}
out.push(c.to_ascii_uppercase());
}
}
out
}
enum Value {
Named(Ident),
Unnamed,
}