use proc_macro::TokenStream; use syn::{parse_macro_input, DeriveInput, parenthesized}; use quote::quote; struct GMFTagged { paren_token: syn::token::Paren, fields: syn::punctuated::Punctuated, } impl syn::parse::Parse for GMFTagged { fn parse(input: syn::parse::ParseStream) -> syn::parse::Result { let content; Ok(Self { paren_token: parenthesized!(content in input), fields: content.parse_terminated(syn::LitInt::parse)?, }) } } #[proc_macro_derive(GMFElement, attributes(gmf_tagged))] pub fn gmf_element_macro(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let ident = input.ident; let mut tlv_header = quote! { }; let mut tlv_footer = quote! { }; if let Some(attr) = input.attrs.iter().find(|a| a.path.is_ident("gmf_tagged")) { let tagged = syn::parse2::(attr.tokens.clone()).expect("invalid gmf_tagged attribute"); if tagged.fields.len() != 2 { panic!("gmf_tagged takes two arguments"); } let a: u32 = tagged.fields[0].base10_parse().expect("gmf_tagged arg 1 not a number"); let b: u32 = tagged.fields[1].base10_parse().expect("gmf_tagged arg 2 not a number"); tlv_header = quote! { let tlv = r.tlv()?; if tlv.tag != #a || tlv.flags != #b { return Err(tlv.error("unsupported tag/flags (wanted ...)")); } }; tlv_footer = quote! { tlv.check(r)?; }; } let expanded = match input.data { syn::Data::Enum(en) => { let branches: Vec = en.variants.iter().map(|variant| { let vident = &variant.ident; let (_, expr) = variant.discriminant.as_ref().expect("enum variants must have values"); quote! { #expr => #ident::#vident, } }).collect(); quote! { impl GMFElement for #ident { fn read(r: &mut ReadStream) -> ReadResult { #tlv_header let v: u32 = r.read("value")?; let res = match v { #(#branches)* _ => return Err(r.error("unknown ")), }; #tlv_footer Ok(res) } } } }, syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Named(fields), .. }) => { let fields: Vec = fields.named.iter().map(|field| { let field_ident = &field.ident; let field_ident_str = field_ident.as_ref().unwrap().to_string(); let field_ident_quoted = syn::LitStr::new(&field_ident_str, proc_macro2::Span::call_site()); quote! { #field_ident: r.read(#field_ident_quoted)?, } }).collect(); quote! { impl GMFElement for #ident { fn read(r: &mut ReadStream) -> ReadResult { #tlv_header let res = Self { #(#fields)* }; #tlv_footer Ok(res) } } } }, _ => panic!("unimplemented"), }; TokenStream::from(expanded) }