Refactor protocol derive module (#11)

This commit is contained in:
vagola
2021-09-16 03:09:10 +03:00
committed by GitHub
parent f733fabedc
commit 70bfd01848
9 changed files with 340 additions and 209 deletions

View File

@@ -0,0 +1,45 @@
use syn::Error as SynError;
/// Possible errors while deriving.
#[derive(Debug)]
pub(crate) enum DeriveInputParserError {
/// Derive attribute must be placed on a structure or enum.
UnsupportedData,
/// Data fields must be named.
UnnamedDataFields,
FieldError {
field_error: FieldError,
},
}
/// Possible errors while parsing field.
#[derive(Debug)]
pub(crate) enum FieldError {
/// Failed to parse field meta due incorrect syntax.
BadAttributeSyntax { syn_error: SynError },
/// Unsupported field attribute type.
UnsupportedAttribute,
/// Field meta has wrong value type.
/// For example an int was expected, but a string was supplied.
AttributeWrongValueType,
}
impl From<FieldError> for DeriveInputParserError {
fn from(field_error: FieldError) -> Self {
DeriveInputParserError::FieldError { field_error }
}
}
impl From<SynError> for DeriveInputParserError {
fn from(syn_error: SynError) -> Self {
DeriveInputParserError::FieldError {
field_error: FieldError::BadAttributeSyntax { syn_error },
}
}
}
impl From<SynError> for FieldError {
fn from(syn_error: SynError) -> Self {
FieldError::BadAttributeSyntax { syn_error }
}
}

View File

@@ -1,185 +1,28 @@
extern crate proc_macro;
use proc_macro::TokenStream as TokenStream1;
use proc_macro2::TokenStream as TokenStream2;
use proc_macro2::{Ident, Span};
use quote::{quote, TokenStreamExt};
use std::iter::FromIterator;
use syn::{parse_macro_input, Data, DeriveInput, Field, Fields, Lit, Meta, NestedMeta};
use crate::parse::parse_derive_input;
use crate::render::decoder::render_decoder;
use crate::render::encoder::render_encoder;
use proc_macro::TokenStream;
use syn::parse_macro_input;
use syn::DeriveInput;
#[proc_macro_derive(Packet, attributes(packet))]
pub fn derive_packet(input: proc_macro::TokenStream) -> TokenStream1 {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
mod error;
mod parse;
mod render;
match input.data {
Data::Struct(data) => {
let fields = &data.fields;
#[proc_macro_derive(Encoder, attributes(data_type))]
pub fn derive_encoder(tokens: TokenStream) -> TokenStream {
let input = parse_macro_input!(tokens as DeriveInput);
let (name, fields) = parse_derive_input(&input).expect("Failed to parse derive input");
let encoder = impl_encoder_trait(name, fields);
let decoder = impl_decoder_trait(name, fields);
TokenStream1::from(quote! {
#encoder
#decoder
})
}
_ => panic!("Expected only structures"),
}
TokenStream::from(render_encoder(name, &fields))
}
fn impl_encoder_trait(name: &Ident, fields: &Fields) -> TokenStream2 {
let encode = quote_field(fields, |field| {
let name = &field.ident;
#[proc_macro_derive(Decoder, attributes(data_type))]
pub fn derive_decoder(tokens: TokenStream) -> TokenStream {
let input = parse_macro_input!(tokens as DeriveInput);
let (name, fields) = parse_derive_input(&input).expect("Failed to parse derive input");
let unparsed_meta = get_packet_field_meta(field);
let parsed_meta = parse_packet_field_meta(&unparsed_meta);
// This is special case because max length are used only for strings.
if let Some(max_length) = parsed_meta.max_length {
return quote! {
crate::encoder::EncoderWriteExt::write_string(writer, &self.#name, #max_length)?;
};
}
let module = parsed_meta.module.as_deref().unwrap_or("Encoder");
let module_ident = Ident::new(&module, Span::call_site());
quote! {
crate::encoder::#module_ident::encode(&self.#name, writer)?;
}
});
quote! {
#[automatically_derived]
impl crate::encoder::Encoder for #name {
fn encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), crate::error::EncodeError> {
#encode
Ok(())
}
}
}
}
fn impl_decoder_trait(name: &Ident, fields: &Fields) -> TokenStream2 {
let decode = quote_field(fields, |field| {
let name = &field.ident;
let ty = &field.ty;
let unparsed_meta = get_packet_field_meta(field);
let parsed_meta = parse_packet_field_meta(&unparsed_meta);
// This is special case because max length are used only for strings.
if let Some(max_length) = parsed_meta.max_length {
return quote! {
let #name = crate::decoder::DecoderReadExt::read_string(reader, #max_length)?;
};
}
match parsed_meta.module {
Some(module) => {
let module_ident = Ident::new(&module, Span::call_site());
quote! {
let #name = crate::decoder::#module_ident::decode(reader)?;
}
}
None => {
quote! {
let #name = <#ty as crate::decoder::Decoder>::decode(reader)?;
}
}
}
});
let create = quote_field(fields, |field| {
let name = &field.ident;
quote! {
#name,
}
});
quote! {
#[automatically_derived]
impl crate::decoder::Decoder for #name {
type Output = Self;
fn decode<R: std::io::Read>(reader: &mut R) -> Result<Self::Output, crate::error::DecodeError> {
#decode
Ok(#name {
#create
})
}
}
}
}
#[derive(Debug)]
struct PacketFieldMeta {
module: Option<String>,
max_length: Option<u16>,
}
fn parse_packet_field_meta(meta_list: &Vec<NestedMeta>) -> PacketFieldMeta {
let mut module = None;
let mut max_length = None;
for meta in meta_list {
match meta {
NestedMeta::Meta(Meta::NameValue(named_meta)) => match &named_meta.path {
path if path.is_ident("with") => match &named_meta.lit {
Lit::Str(lit_str) => module = Some(lit_str.value()),
_ => panic!("\"with\" attribute value must be string"),
},
path if path.is_ident("max_length") => match &named_meta.lit {
Lit::Int(lit_int) => {
max_length = Some(
lit_int
.base10_parse::<u16>()
.expect("Failed to parse max length attribute"),
)
}
_ => panic!("\"max_length\" attribute value must be integer"),
},
path => panic!(
"Received unrecognized attribute : \"{}\"",
path.get_ident().unwrap()
),
},
_ => panic!("Expected only named meta values"),
}
}
PacketFieldMeta { module, max_length }
}
fn get_packet_field_meta(field: &Field) -> Vec<NestedMeta> {
field
.attrs
.iter()
.filter(|a| a.path.is_ident("packet"))
.map(|a| a.parse_meta().expect("Failed to parse field attribute"))
.map(|m| match m {
Meta::List(meta_list) => Vec::from_iter(meta_list.nested),
_ => panic!("Expected only list attributes"),
})
.flatten()
.collect()
}
fn quote_field<F: Fn(&Field) -> TokenStream2>(fields: &Fields, func: F) -> TokenStream2 {
let mut output = quote!();
match fields {
Fields::Named(named_fields) => {
output.append_all(named_fields.named.iter().map(|f| func(f)))
}
_ => panic!("Expected only for named fields"),
}
output
TokenStream::from(render_decoder(name, &fields))
}

View File

@@ -0,0 +1,118 @@
use crate::error::{DeriveInputParserError, FieldError};
use proc_macro2::Ident;
use std::iter::FromIterator;
use syn::Error as SynError;
use syn::{Data, DeriveInput, Field, Fields, FieldsNamed, Lit, Meta, NestedMeta, Type};
pub(crate) struct FieldData<'a> {
pub(crate) name: &'a Ident,
pub(crate) ty: &'a Type,
pub(crate) attribute: Attribute,
}
#[derive(Debug, Eq, PartialEq)]
pub(crate) enum Attribute {
With { module: String },
MaxLength { length: usize },
Empty,
}
pub(crate) fn parse_derive_input(
input: &DeriveInput,
) -> Result<(&Ident, Vec<FieldData>), DeriveInputParserError> {
let name = &input.ident;
match &input.data {
Data::Struct(data) => match &data.fields {
Fields::Named(named_fields) => Ok((name, parse_fields(named_fields)?)),
_ => Err(DeriveInputParserError::UnnamedDataFields),
},
_ => Err(DeriveInputParserError::UnsupportedData),
}
}
fn parse_fields(named_fields: &FieldsNamed) -> Result<Vec<FieldData>, DeriveInputParserError> {
let mut fields_data = Vec::new();
for field in named_fields.named.iter() {
let name = field.ident.as_ref().unwrap();
let ty = &field.ty;
let nested_metas = parse_field_nested_metas(field)?;
let attribute = parse_attribute(nested_metas)?;
fields_data.push(FieldData {
name,
ty,
attribute,
})
}
Ok(fields_data)
}
fn parse_field_nested_metas(field: &Field) -> Result<Vec<NestedMeta>, DeriveInputParserError> {
let parsed_metas = field
.attrs
.iter()
.filter(|a| a.path.is_ident("data_type"))
.map(|a| a.parse_meta())
.collect::<Result<Vec<Meta>, SynError>>()?;
let nested_metas = parsed_metas
.into_iter()
.map(|m| match m {
Meta::List(meta_list) => Ok(Vec::from_iter(meta_list.nested)),
_ => Err(FieldError::UnsupportedAttribute),
})
.collect::<Result<Vec<Vec<NestedMeta>>, FieldError>>()?;
Ok(nested_metas.into_iter().flatten().collect())
}
fn parse_attribute(nested_metas: Vec<NestedMeta>) -> Result<Attribute, DeriveInputParserError> {
let attribute_parsers: Vec<fn(&NestedMeta) -> Result<Attribute, FieldError>> =
vec![get_module_attribute, get_max_length_attribute];
for nested_meta in nested_metas.iter() {
for attribute_parser in attribute_parsers.iter() {
let attribute = attribute_parser(nested_meta)?;
if attribute != Attribute::Empty {
return Ok(attribute);
}
}
}
Ok(Attribute::Empty)
}
fn get_module_attribute(nested_meta: &NestedMeta) -> Result<Attribute, FieldError> {
if let NestedMeta::Meta(Meta::NameValue(named_meta)) = nested_meta {
if matches!(&named_meta.path, path if path.is_ident("with")) {
return match &named_meta.lit {
Lit::Str(lit_str) => Ok(Attribute::With {
module: lit_str.value(),
}),
_ => Err(FieldError::AttributeWrongValueType),
};
}
}
Ok(Attribute::Empty)
}
fn get_max_length_attribute(nested_meta: &NestedMeta) -> Result<Attribute, FieldError> {
if let NestedMeta::Meta(Meta::NameValue(named_meta)) = nested_meta {
if matches!(&named_meta.path, path if path.is_ident("max_length")) {
return match &named_meta.lit {
Lit::Int(lit_int) => Ok(Attribute::MaxLength {
length: lit_int.base10_parse::<usize>()?,
}),
_ => Err(FieldError::AttributeWrongValueType),
};
}
}
Ok(Attribute::Empty)
}

View File

@@ -0,0 +1,72 @@
use crate::parse::{Attribute, FieldData};
use proc_macro2::TokenStream as TokenStream2;
use proc_macro2::{Ident, Span};
use quote::quote;
use syn::Type;
pub(crate) fn render_decoder(name: &Ident, fields: &Vec<FieldData>) -> TokenStream2 {
let struct_create = render_struct_create(name, fields);
let render_fields = render_fields(fields);
quote! {
#[automatically_derived]
impl crate::decoder::Decoder for #name {
type Output = Self;
fn decode<R: std::io::Read>(reader: &mut R) -> Result<Self::Output, crate::error::DecodeError> {
#render_fields
Ok(#struct_create)
}
}
}
}
fn render_struct_create(name: &Ident, fields: &Vec<FieldData>) -> TokenStream2 {
let struct_fields = fields
.iter()
.map(|f| f.name)
.map(|n| quote!(#n,))
.collect::<TokenStream2>();
quote! {
#name {
#struct_fields
}
}
}
fn render_fields(fields: &Vec<FieldData>) -> TokenStream2 {
fields.iter().map(|f| render_field(f)).flatten().collect()
}
fn render_field(field: &FieldData) -> TokenStream2 {
let name = field.name;
let ty = field.ty;
match &field.attribute {
Attribute::With { module } => render_with_field(name, module),
Attribute::MaxLength { length } => render_max_length_field(name, *length as u16),
Attribute::Empty => render_simple_field(name, ty),
}
}
fn render_simple_field(name: &Ident, ty: &Type) -> TokenStream2 {
quote! {
let #name = <#ty as crate::decoder::Decoder>::decode(reader)?;
}
}
fn render_with_field(name: &Ident, module: &str) -> TokenStream2 {
let module_ident = Ident::new(module, Span::call_site());
quote! {
let #name = crate::decoder::#module_ident::decode(reader)?;
}
}
fn render_max_length_field(name: &Ident, max_length: u16) -> TokenStream2 {
quote! {
let #name = crate::decoder::DecoderReadExt::read_string(reader, #max_length)?;
}
}

View File

@@ -0,0 +1,51 @@
use crate::parse::{Attribute, FieldData};
use proc_macro2::TokenStream as TokenStream2;
use proc_macro2::{Ident, Span};
use quote::quote;
pub(crate) fn render_encoder(name: &Ident, fields: &Vec<FieldData>) -> TokenStream2 {
let render_fields = render_fields(fields);
quote! {
#[automatically_derived]
impl crate::encoder::Encoder for #name {
fn encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), crate::error::EncodeError> {
#render_fields
Ok(())
}
}
}
}
fn render_fields(fields: &Vec<FieldData>) -> TokenStream2 {
fields.iter().map(|f| render_field(f)).flatten().collect()
}
fn render_field(field: &FieldData) -> TokenStream2 {
let name = field.name;
match &field.attribute {
Attribute::With { module } => render_with_field(name, module),
Attribute::MaxLength { length } => render_max_length_field(name, *length as u16),
Attribute::Empty => render_simple_field(name),
}
}
fn render_simple_field(name: &Ident) -> TokenStream2 {
render_with_field(name, "Encoder")
}
fn render_with_field(name: &Ident, module: &str) -> TokenStream2 {
let module_ident = Ident::new(module, Span::call_site());
quote! {
crate::encoder::#module_ident::encode(&self.#name, writer)?;
}
}
fn render_max_length_field(name: &Ident, max_length: u16) -> TokenStream2 {
quote! {
crate::encoder::EncoderWriteExt::write_string(writer, &self.#name, #max_length)?;
}
}

View File

@@ -0,0 +1,2 @@
pub(crate) mod decoder;
pub(crate) mod encoder;