gmftools/gmfmacros/src/lib.rs

100 lines
3.6 KiB
Rust

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<syn::LitInt, syn::Token![,]>,
}
impl syn::parse::Parse for GMFTagged {
fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
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::<GMFTagged>(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<proc_macro2::TokenStream> = 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: std::io::Read>(r: &mut ReadStream<R>) -> ReadResult<Self> {
#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<proc_macro2::TokenStream> = 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: std::io::Read>(r: &mut ReadStream<R>) -> ReadResult<Self> {
#tlv_header
let res = Self {
#(#fields)*
};
#tlv_footer
Ok(res)
}
}
}
},
_ => panic!("unimplemented"),
};
TokenStream::from(expanded)
}