346 lines
12 KiB
Rust
346 lines
12 KiB
Rust
use proc_macro::TokenStream;
|
|
use syn::{parse_macro_input, DeriveInput, parenthesized};
|
|
use quote::quote;
|
|
|
|
struct GMITagged {
|
|
#[allow(dead_code)]
|
|
paren_token: syn::token::Paren,
|
|
fields: syn::punctuated::Punctuated<syn::LitInt, syn::Token![,]>,
|
|
|
|
nolen: bool,
|
|
ty: u32,
|
|
flag: u32,
|
|
}
|
|
|
|
impl syn::parse::Parse for GMITagged {
|
|
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)?,
|
|
|
|
nolen: false,
|
|
ty: 0,
|
|
flag: 0,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl GMITagged {
|
|
fn from_attrs(attrs: &Vec<syn::Attribute>) -> Option<Self> {
|
|
let attr = attrs.iter().find(|a| a.path.is_ident("gmi_tagged") || a.path.is_ident("gmi_tagged_nolen"))?;
|
|
let mut res = syn::parse2::<GMITagged>(attr.tokens.clone()).expect("invalid gmi_tagged attribute");
|
|
if res.fields.len() != 2 {
|
|
panic!("gmi_tagged(_len) takes two arguments");
|
|
}
|
|
|
|
res.nolen = attr.path.is_ident("gmi_tagged_nolen");
|
|
res.ty = res.fields[0].base10_parse().expect("gmi_tagged arg 1 not a number");
|
|
res.flag = res.fields[1].base10_parse().expect("gmi_tagged arg 2 not a number");
|
|
Some(res)
|
|
}
|
|
}
|
|
|
|
struct GMIReadParameters {
|
|
#[allow(dead_code)]
|
|
paren_token: syn::token::Paren,
|
|
fields: syn::punctuated::Punctuated<syn::Expr, syn::Token![,]>,
|
|
}
|
|
|
|
impl syn::parse::Parse for GMIReadParameters {
|
|
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::Expr::parse)?,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl GMIReadParameters {
|
|
fn from_attrs(attrs: &Vec<syn::Attribute>) -> Option<Vec<syn::Expr>> {
|
|
if let Some(attr) = attrs.iter().find(|a| a.path.is_ident("gmi_read_parameters")) {
|
|
let parameters = syn::parse2::<GMIReadParameters>(attr.tokens.clone()).expect("invalid gmi_read_parameters attribute");
|
|
return Some(parameters.fields.iter().cloned().collect());
|
|
}
|
|
None
|
|
}
|
|
}
|
|
|
|
#[proc_macro_derive(GMISerializable, attributes(gmi_tagged, gmi_tagged_nolen, gmi_read_parameters))]
|
|
pub fn gmi_serializable_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(tagged) = GMITagged::from_attrs(&input.attrs) {
|
|
let (ty, flags) = (tagged.ty, tagged.flag);
|
|
|
|
tlv_header = quote! {
|
|
let tlv = _r.tlv()?;
|
|
if tlv.tag != #ty || tlv.flags != #flags {
|
|
return Err(_r.error(format!("unsupported tag/flags (wanted {}/{}, got {}/{})", #ty, #flags, tlv.tag, tlv.flags)));
|
|
}
|
|
};
|
|
if !tagged.nolen {
|
|
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 gmi::Serializable for #ident {
|
|
fn read<R: std::io::Read>(_r: &mut gmi::ReadStream<R>) -> gmi::ReadResult<Self> {
|
|
#tlv_header
|
|
let v: u32 = _r.read("value")?;
|
|
let res = match v {
|
|
#(#branches)*
|
|
_ => return Err(_r.error(format!("unknown enum value {}", v))),
|
|
};
|
|
#tlv_footer
|
|
Ok(res)
|
|
}
|
|
}
|
|
}
|
|
},
|
|
syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Named(fields), .. }) => {
|
|
let temporaries: Vec<proc_macro2::TokenStream> = fields.named.iter().map(|field| {
|
|
let field_ident = &field.ident;
|
|
let field_type = &field.ty;
|
|
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());
|
|
if let Some(p) = GMIReadParameters::from_attrs(&field.attrs) {
|
|
quote! {
|
|
let #field_ident: #field_type = _r.read_parametrized(#field_ident_quoted, &[#(#p),*])?;
|
|
}
|
|
} else {
|
|
quote! {
|
|
let #field_ident: #field_type = _r.read(#field_ident_quoted)?;
|
|
}
|
|
}
|
|
}).collect();
|
|
let assignments: Vec<proc_macro2::TokenStream> = fields.named.iter().map(|field| {
|
|
let field_ident = &field.ident;
|
|
quote! {
|
|
#field_ident,
|
|
}
|
|
}).collect();
|
|
quote! {
|
|
impl gmi::Serializable for #ident {
|
|
fn read<R: std::io::Read>(_r: &mut gmi::ReadStream<R>) -> gmi::ReadResult<Self> {
|
|
#tlv_header
|
|
#(#temporaries)*
|
|
let res = Self {
|
|
#(#assignments)*
|
|
};
|
|
#tlv_footer
|
|
Ok(res)
|
|
}
|
|
}
|
|
}
|
|
},
|
|
_ => panic!("unimplemented"),
|
|
};
|
|
|
|
TokenStream::from(expanded)
|
|
}
|
|
|
|
struct GMAName {
|
|
#[allow(dead_code)]
|
|
paren_token: syn::token::Paren,
|
|
fields: syn::punctuated::Punctuated<syn::LitStr, syn::Token![,]>,
|
|
|
|
name: String,
|
|
bare: bool,
|
|
}
|
|
|
|
impl syn::parse::Parse for GMAName {
|
|
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::LitStr as syn::parse::Parse>::parse)?,
|
|
name: String::new(),
|
|
bare: false,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl GMAName {
|
|
fn from_attrs(attrs: &Vec<syn::Attribute>) -> Option<Self> {
|
|
let attr = attrs.iter()
|
|
.find(|a| a.path.is_ident("gma_name")
|
|
|| a.path.is_ident("gma_name_bare"))?;
|
|
let mut res = syn::parse2::<Self>(attr.tokens.clone()).expect("invalid gma_name attribute");
|
|
if res.fields.len() != 1 {
|
|
panic!("gma_name takes one argument");
|
|
}
|
|
|
|
res.name = res.fields[0].value();
|
|
res.bare = attr.path.is_ident("gma_name_bare");
|
|
Some(res)
|
|
}
|
|
}
|
|
|
|
|
|
struct GMAValue {
|
|
#[allow(dead_code)]
|
|
paren_token: syn::token::Paren,
|
|
fields: syn::punctuated::Punctuated<syn::LitStr, syn::Token![,]>,
|
|
|
|
name: String,
|
|
}
|
|
|
|
impl syn::parse::Parse for GMAValue {
|
|
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::LitStr as syn::parse::Parse>::parse)?,
|
|
name: String::new(),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl GMAValue {
|
|
fn from_attrs(attrs: &Vec<syn::Attribute>) -> Option<Self> {
|
|
let attr = attrs.iter().find(|a| a.path.is_ident("gma_value"))?;
|
|
let mut res = syn::parse2::<Self>(attr.tokens.clone()).expect("invalid gma_value attribute");
|
|
if res.fields.len() != 1 {
|
|
panic!("gma_value takes one argument");
|
|
}
|
|
|
|
res.name = res.fields[0].value();
|
|
Some(res)
|
|
}
|
|
}
|
|
|
|
struct GMASkip {
|
|
#[allow(dead_code)]
|
|
paren_token: syn::token::Paren,
|
|
#[allow(dead_code)]
|
|
fields: syn::punctuated::Punctuated<syn::Expr, syn::Token![,]>,
|
|
}
|
|
|
|
impl syn::parse::Parse for GMASkip {
|
|
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::Expr::parse)?,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl GMASkip {
|
|
fn from_attrs(attrs: &Vec<syn::Attribute>) -> Option<syn::Expr> {
|
|
if let Some(attr) = attrs.iter().find(|a| a.path.is_ident("gma_skip")) {
|
|
let parameters = syn::parse2::<GMIReadParameters>(attr.tokens.clone()).expect("invalid gma_skip attribute");
|
|
if parameters.fields.len() != 1 {
|
|
panic!("gma_skip must have exactly one attribute");
|
|
}
|
|
return Some(parameters.fields.iter().next().unwrap().clone());
|
|
}
|
|
None
|
|
}
|
|
}
|
|
|
|
#[proc_macro_derive(GMASerializable, attributes(gma_name, gma_name_bare, gma_value, gma_skip, gma_space_delim))]
|
|
pub fn gma_serializable_macro(input: TokenStream) -> TokenStream {
|
|
let input = parse_macro_input!(input as DeriveInput);
|
|
let ident = input.ident;
|
|
|
|
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 value: String = GMAValue::from_attrs(&variant.attrs)
|
|
.map(|s| s.name.to_string())
|
|
.or_else(|| {
|
|
Some(vident.to_string())
|
|
}).unwrap();
|
|
quote! {
|
|
#ident::#vident => #value,
|
|
}
|
|
}).collect();
|
|
|
|
quote! {
|
|
impl gma::Serializable for #ident {
|
|
fn write<W: std::io::Write, S: std::string::ToString>(&self, _name: S, _w: &mut gma::WriteStream<W>) -> gma::WriteResult<()> {
|
|
_w.emit_pair(&_name.to_string(), match self {
|
|
#(#branches)*
|
|
})
|
|
}
|
|
}
|
|
}
|
|
},
|
|
syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Named(fields), .. }) => {
|
|
let struct_name = GMAName::from_attrs(&input.attrs)
|
|
.map(|s| "*".to_string() + &s.name)
|
|
.or_else(|| {
|
|
Some("*".to_string() + &gma_name_from_ident(&ident))
|
|
}).unwrap();
|
|
let writes: Vec<proc_macro2::TokenStream> = fields.named.iter().map(|field| {
|
|
let field_ident = &field.ident;
|
|
let field_skip = GMASkip::from_attrs(&field.attrs);
|
|
let field_space = field.attrs.iter().any(|a| a.path.is_ident("gma_space_delim"));
|
|
let field_name = GMAName::from_attrs(&field.attrs)
|
|
.map(|s|
|
|
if s.bare {
|
|
s.name
|
|
} else {
|
|
"*".to_string() + &s.name
|
|
})
|
|
.or_else(|| {
|
|
Some(struct_name.clone() + "_" + &gma_name_from_ident(field_ident.as_ref().unwrap()))
|
|
}).unwrap();
|
|
let cond = field_skip.map(|el| quote! { #el } ).unwrap_or(quote! { false } );
|
|
if field_space {
|
|
quote! {
|
|
if ! ( #cond ) {
|
|
_w.emit(&format!("{} {}", #field_name, self.#field_ident.atom()))?;
|
|
}
|
|
}
|
|
} else {
|
|
quote! {
|
|
if ! ( #cond ) {
|
|
self.#field_ident.write(#field_name, _w)?;
|
|
}
|
|
}
|
|
}
|
|
}).collect();
|
|
quote! {
|
|
impl gma::Serializable for #ident {
|
|
fn write<W: std::io::Write, S: std::string::ToString>(&self, _name: S, _w: &mut gma::WriteStream<W>) -> gma::WriteResult<()> {
|
|
_w.push(#struct_name)?;
|
|
#(#writes)*
|
|
_w.pop()?;
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
},
|
|
_ => panic!("unimplemented"),
|
|
};
|
|
|
|
TokenStream::from(expanded)
|
|
}
|
|
|
|
fn gma_name_from_ident(ident: &syn::Ident) -> String {
|
|
ident.to_string().to_uppercase().replace("_", "")
|
|
}
|