100 lines
3.6 KiB
Rust
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)
|
|
}
|