use darling::{util::Flag, ToTokens};
use crate::util::*;
#[derive(Debug, darling::FromDeriveInput)]
#[darling(forward_attrs(allow, doc, cfg))]
#[darling(attributes(recode), supports(struct_named, struct_unit))]
pub(crate) struct Encoder {
pub(crate) ident: syn::Ident,
pub(crate) generics: syn::Generics,
pub(crate) data: darling::ast::Data<(), EncoderField>,
#[darling(default)]
pub(crate) encoder: EncoderOpts,
}
#[derive(Clone, Debug, Default, darling::FromMeta)]
#[darling(default)]
pub(crate) struct EncoderOpts {
pub(crate) disable: Flag,
pub(crate) error: Option<syn::Type>,
pub(crate) buffer_type: Option<syn::Type>,
pub(crate) buffer_name: Option<syn::Ident>,
pub(crate) input_type: Option<syn::Type>,
pub(crate) input_name: Option<syn::Ident>,
}
#[derive(Debug, darling::FromField)]
#[darling(attributes(recode))]
pub(crate) struct EncoderField {
pub(crate) ident: Option<syn::Ident>,
pub(crate) ty: syn::Type,
#[darling(default)]
pub(crate) encoder: EncoderFieldOpts,
}
#[derive(Clone, Debug, Default, darling::FromMeta)]
#[darling(default)]
pub(crate) struct EncoderFieldOpts {
pub(crate) skip: Flag,
pub(crate) skip_if: Option<syn::Expr>,
pub(crate) map: Option<syn::Expr>,
pub(crate) with: Option<syn::Type>,
pub(crate) size: Option<syn::Expr>,
}
impl darling::ToTokens for Encoder {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
use quote::quote;
use syn::parse::{Parse, Parser};
let Encoder {
ident,
generics,
data,
encoder:
EncoderOpts {
disable,
error,
buffer_type,
buffer_name,
input_type,
input_name,
},
} = self;
if disable.is_present() {
return;
}
let mut generics = OwnedGenerics::new(generics.clone());
let input_type = input_type
.clone()
.unwrap_or(syn::Type::Verbatim(quote!(Self)));
let input_name =
input_name.clone().unwrap_or(quote::format_ident!("input"));
let error = error.clone().unwrap_or(box_type());
let buffer_name = buffer_name.clone().unwrap_or(default_buffer_name());
let buffer_type = buffer_type.clone().unwrap_or_else(|| {
generics.push_impl_param(
syn::TypeParam::parse
.parse2(quote!(B: recode::bytes::BufMut))
.unwrap(),
)
});
let fields: Vec<_> = data
.as_ref()
.take_struct()
.expect("only structs are supported")
.fields;
let field_stmts = fields
.iter()
.map(|&f| f.to_encode_stmt(&input_name, &buffer_name));
let size_exprs = fields
.iter()
.map(|&f| f.to_size_expr(&input_name, &buffer_name));
let (imp, ty, wher) = generics.split_for_impl();
tokens.extend(quote::quote! {
impl #imp recode::Encoder<#buffer_type, #input_type> for #ident #ty #wher {
type Error = #error;
fn encode(
#input_name: &#input_type,
#buffer_name: &mut #buffer_type,
) -> Result<(), Self::Error> {
use recode::Encoder;
#( #field_stmts )*
Ok(())
}
fn size_of(
#input_name: &#input_type,
#buffer_name: &#buffer_type
) -> usize {
0 #( + #size_exprs )*
}
}
});
}
}
impl EncoderField {
pub(crate) fn to_encode_stmt(
&self,
input_ident: &syn::Ident,
buf_ident: &syn::Ident,
) -> proc_macro2::TokenStream {
use quote::quote;
let EncoderField {
ident,
ty,
encoder:
EncoderFieldOpts {
skip,
skip_if,
map,
with,
size: _,
},
} = self;
if skip.is_present() {
return Default::default();
}
let with = with.as_ref().unwrap_or(ty);
let input = map
.as_ref()
.map(|m| quote! (((#m)(#input_ident.#ident))))
.unwrap_or(quote! (#input_ident.#ident));
let stmt = quote! {
<#with as recode::Encoder<_, #ty>>::encode(&#input, #buf_ident)?;
};
skip_if
.as_ref()
.map(|s| quote::quote! (if !(#s) { #stmt }))
.unwrap_or(stmt)
}
pub(crate) fn to_size_expr(
&self,
input_ident: &syn::Ident,
buf_ident: &syn::Ident,
) -> proc_macro2::TokenStream {
use quote::quote;
if let Some(ref expr) = self.encoder.size {
return expr.to_token_stream();
}
let ident = self.ident.as_ref();
let ty = &self.ty;
let with = self.encoder.with.as_ref().unwrap_or(&self.ty);
quote! {
<#with as recode::Encoder<_, #ty>>::size_of(&#input_ident.#ident, #buf_ident)
}
}
}