dataparser_derive 0.1.0

The struct serialization/deserialization feature for dataparser_core
Documentation
extern crate proc_macro;
extern crate syn;
extern crate quote;
use proc_macro::TokenStream;
use quote::quote;
use syn::{Data, Fields, GenericArgument, PathArguments, Type};
#[proc_macro_derive(StructDeserialize)]
pub fn derive_struct_deserialize(input: TokenStream) -> TokenStream {
    let ast = syn::parse_macro_input!(input as syn::DeriveInput);
    impl_struct_deserialize(&ast)
}

fn impl_struct_deserialize(ast: &syn::DeriveInput) -> TokenStream {
    let struct_name = &ast.ident;

    let fields = match &ast.data {
        Data::Struct(data_struct) => match &data_struct.fields {
            Fields::Named(fields) => fields.named.iter().map(|f| {
                let name = &f.ident;
                let ty = &f.ty;
                let expr = get_parse_expr(ty);
                quote! {
                    #name: #expr
                }
            }).collect::<Vec<_>>(),
            _ => panic!("Only named fields supported"),
        },
        _ => panic!("Only structs supported"),
    };

    let generator = quote! {
        impl ::dataparser_core::Decodable for #struct_name {
            fn from_parser(parser: &mut ::dataparser_core::parser::core::DataParser) -> ::dataparser_core::utils::ParseResult<Self> {
                Ok(Self {
                    #(#fields),*
                })
            }
        }
    };

    generator.into()
}

fn get_parse_expr(ty: &Type) -> proc_macro2::TokenStream {
    match ty {
        Type::Path(type_path) => {
            let last = type_path.path.segments.last().unwrap();
            let ident = &last.ident;
            let ident_str = ident.to_string();

            match ident_str.as_str() {
                "u8" => quote! { parser.get_byte()? },
                "u16" => quote! { parser.get_u16()? },
                "u32" => quote! { parser.get_u32()? },
                "u64" => quote! { parser.get_u64()? },
                "usize" => quote! { parser.get_usize()? },
                "i8" => quote! { parser.get_raw::<i8>(true)? },
                "i16" => quote! { parser.get_i16()? },
                "i32" => quote! { parser.get_i32()? },
                "i64" => quote! { parser.get_i64()? },
                "isize" => quote! { parser.get_raw::<isize>(true)? },
                "f32" => quote! { parser.get_f32()? },
                "f64" => quote! { parser.get_f64()? },
                "bool" => quote! { parser.get_bool()? },
                "String" => quote! { parser.get_string(false)? },
                "Option" => {
                    if let PathArguments::AngleBracketed(args) = &last.arguments {
                        if let Some(GenericArgument::Type(inner)) = args.args.first() {
                            quote! { parser.get_option::<#inner>()? }
                        } else {
                            quote! { compile_error!("Invalid Option<T> type") }
                        }
                    } else {
                        quote! { compile_error!("Invalid Option syntax") }
                    }
                }
                "Vec" => {
                    if let PathArguments::AngleBracketed(args) = &last.arguments {
                        if let Some(GenericArgument::Type(inner)) = args.args.first() {
                            quote! { parser.get_vector::<#inner>()? }
                        } else {
                            quote! { compile_error!("Invalid Vec<T> type") }
                        }
                    } else {
                        quote! { compile_error!("Invalid Vec syntax") }
                    }
                }
                _ => quote! { <#ty as ::dataparser_core::Decodable>::from_parser(parser)? }
            }
        }
        _ => quote! { <#ty as ::dataparser_core::Decodable>::from_parser(parser)? },
    }
}


#[proc_macro_derive(StructSerialize)]
pub fn derive_struct_serialize(input: TokenStream) -> TokenStream {
    let ast = syn::parse_macro_input!(input as syn::DeriveInput);
    impl_struct_serialize(&ast)
}

fn impl_struct_serialize(ast: &syn::DeriveInput) -> TokenStream {
    let struct_name = &ast.ident;

    let field_writes = match &ast.data {
        Data::Struct(data_struct) => match &data_struct.fields {
            Fields::Named(fields) => fields.named.iter().map(|f| {
                let name = &f.ident;
                let ty = &f.ty;
                get_write_expr(ty, name)
            }).collect::<Vec<_>>(),
            _ => panic!("Only named fields supported"),
        },
        _ => panic!("Only structs supported"),
    };

    let generator = quote! {
        impl ::dataparser_core::encoder::helpers::Encodable for #struct_name {
            fn encode_data(
                &self,
                writer: &mut ::dataparser_core::encoder::core::DataEncoder
            ) -> ::dataparser_core::utils::ParseResult<()> {
                #(#field_writes)*
                Ok(())
            }
        }
    };

    generator.into()
}

fn get_write_expr(ty: &Type, field: &Option<syn::Ident>) -> proc_macro2::TokenStream {
    let field_expr = quote!(self.#field);

    match ty {
        Type::Path(type_path) => {
            let last = type_path.path.segments.last().unwrap();
            let ident_str = last.ident.to_string();

            match ident_str.as_str() {
                "String" => quote! { writer.add_string(&*#field_expr)?; },
                "bool" => quote! { writer.add_bool(#field_expr)?; },
                "Option" => {
                    if let PathArguments::AngleBracketed(args) = &last.arguments {
                        if let Some(GenericArgument::Type(_)) = args.args.first() {
                            quote! {
                                match &#field_expr {
                                    Some(val) => {
                                        writer.add_bool(true)?;
                                        val.encode_data(writer)?;
                                    }
                                    None => writer.add_bool(false)?,
                                }
                            }
                        } else {
                            quote! { compile_error!("Invalid Option syntax"); }
                        }
                    } else {
                        quote! { compile_error!("Invalid Option structure"); }
                    }
                }
                "Vec" => {
                    if let PathArguments::AngleBracketed(args) = &last.arguments {
                        if let Some(GenericArgument::Type(inner_ty)) = args.args.first() {
                            quote! {
                                writer.add_slice::<#inner_ty>(&#field_expr)?;
                            }
                        } else {
                            quote! { compile_error!("Invalid Vec<T> type"); }
                        }
                    } else {
                        quote! { compile_error!("Invalid Vec syntax"); }
                    }
                }
                primitive if [
                    "u8", "u16", "u32", "u64", "usize",
                    "i8", "i16", "i32", "i64", "isize",
                    "f32", "f64"
                ].contains(&primitive) => {
                    let method = syn::Ident::new(&format!("add_{}", ident_str), last.ident.span());
                    quote! { writer.#method(#field_expr)?; }
                }
                _ => quote! { #field_expr.encode_data(writer)?; }
            }
        }
        _ => quote! { #field_expr.encode_data(writer)?; },
    }
}