gmftools/gmfmacros/src/lib.rs

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("_", "")
}