pcd-rs-derive 0.13.0

Derive macros for pcd-rs crate
Documentation
use crate::{common::*, parse::ItemStruct, utils::parse_field_attributes};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{
    punctuated::Punctuated, spanned::Spanned, token, Field, Ident, Type, TypeArray, TypePath,
};

struct DerivedTokens {
    pub write_spec_tokens: TokenStream,
    pub bin_write_tokens: TokenStream,
    pub text_write_tokens: TokenStream,
}

pub fn f_pcd_record_write_derive(item: ItemStruct) -> syn::Result<TokenStream> {
    let struct_name = &item.ident;

    let DerivedTokens {
        write_spec_tokens,
        bin_write_tokens,
        text_write_tokens,
    } = derive_named_fields(struct_name, &item.fields)?;

    let expanded = quote! {
        impl ::pcd_rs::record::PcdSerialize for #struct_name {
            fn is_dynamic() -> bool {
                false
            }

            fn write_spec() -> ::pcd_rs::metas::Schema {
                #write_spec_tokens
            }

            fn write_chunk<R: std::io::Write>(&self, writer: &mut R, _: &::pcd_rs::metas::Schema) -> ::pcd_rs::Result<()> {
                use ::pcd_rs::byteorder::{LittleEndian, WriteBytesExt};
                { #bin_write_tokens };
                Ok(())
            }

            fn write_line<R: std::io::Write>(&self, writer: &mut R, _: &::pcd_rs::metas::Schema) -> ::pcd_rs::Result<()> {
                let mut tokens = Vec::<String>::new();
                { #text_write_tokens };
                let line = tokens.join(" ");
                writeln!(writer, "{}", line)?;
                Ok(())
            }
        }
    };

    Ok(expanded)
}

fn derive_named_fields(
    struct_name: &Ident,
    fields: &Punctuated<Field, token::Comma>,
) -> syn::Result<DerivedTokens> {
    let fields: Vec<_> = fields
        .iter()
        .enumerate()
        .map(|(field_index, field)| {
            let field_error = syn::Error::new(
                field.span(),
                "Type of struct field must be a primitive type or array of primitive type.",
            );
            let field_ident = format_ident!("{}", &field.ident.as_ref().unwrap());

            let pcd_name = {
                let opts = parse_field_attributes(&field.attrs)?;

                match (opts.ignore, opts.rename) {
                    (true, _) => None,
                    (false, None) => Some(field_ident.to_string()),
                    (false, Some(rename)) => Some(rename),
                }
            };

            let tokens = match &field.ty {
                Type::Array(array) => derive_array_field(&field_ident, array).ok_or(field_error)?,
                Type::Path(path) => {
                    derive_path_field(field_index, &field_ident, path).ok_or(field_error)?
                }
                _ => return Err(field_error),
            };

            Ok((field_ident, pcd_name, tokens))
        })
        .try_collect()?;

    let (field_idents, write_specs, bin_write_fields, text_write_fields) = fields
        .into_iter()
        .map(|(field_ident, pcd_name, tokens)| {
            let write_spec_tokens = tokens.write_spec_tokens;
            (
                field_ident,
                quote! { (#pcd_name.to_owned(), #write_spec_tokens) },
                tokens.bin_write_tokens,
                tokens.text_write_tokens,
            )
        })
        .unzip_n_vec();

    let write_spec_tokens = quote! {
        vec![#(#write_specs),*]
            .into_iter()
            .collect::<::pcd_rs::metas::Schema>()
    };
    let bin_write_tokens = quote! {
        let #struct_name { #(#field_idents),* } = self;
        #(#bin_write_fields)*
    };
    let text_write_tokens = quote! {
        let #struct_name { #(#field_idents),* } = self;
        #(#text_write_fields)*
    };

    let derived_tokens = DerivedTokens {
        write_spec_tokens,
        bin_write_tokens,
        text_write_tokens,
    };
    Ok(derived_tokens)
}

fn derive_array_field(var_ident: &Ident, array: &TypeArray) -> Option<DerivedTokens> {
    let len = &array.len;
    let type_ident = match &*array.elem {
        Type::Path(path) => path.path.get_ident()?,
        _ => return None,
    };

    let DerivedTokens {
        write_spec_tokens: write_spec,
        bin_write_tokens: bin_write,
        text_write_tokens: text_write,
    } = make_rw_expr(type_ident)?;

    let write_spec_tokens = quote! { #write_spec, #len };
    let bin_write_tokens = quote! {
        for value_ref in #var_ident.iter() {
            let value = *value_ref;
            #bin_write;
        }
    };
    let text_write_tokens = quote! {
        for value_ref in #var_ident.iter() {
            let value = *value_ref;
            #text_write;
        }
    };

    let derived_tokens = DerivedTokens {
        write_spec_tokens,
        bin_write_tokens,
        text_write_tokens,
    };

    Some(derived_tokens)
}

fn derive_path_field(
    _field_index: usize,
    var_ident: &Ident,
    path: &TypePath,
) -> Option<DerivedTokens> {
    let type_ident = path.path.get_ident()?;
    derive_primitive_field(var_ident, type_ident)
}

fn derive_primitive_field(var_ident: &Ident, type_ident: &Ident) -> Option<DerivedTokens> {
    let DerivedTokens {
        write_spec_tokens: write_spec,
        bin_write_tokens: bin_write,
        text_write_tokens: text_write,
    } = make_rw_expr(type_ident)?;

    let write_spec_tokens = quote! { #write_spec, 1 };
    let bin_write_tokens = quote! {
        {
            let value = *#var_ident;
            #bin_write;
        }
    };
    let text_write_tokens = quote! {
        {
            let value = *#var_ident;
            #text_write;
        }
    };

    let derived_tokens = DerivedTokens {
        write_spec_tokens,
        bin_write_tokens,
        text_write_tokens,
    };

    Some(derived_tokens)
}

fn make_rw_expr(type_ident: &Ident) -> Option<DerivedTokens> {
    let (write_spec_tokens, bin_write_tokens, text_write_tokens) =
        match type_ident.to_string().as_str() {
            "u8" => (
                quote! { ::pcd_rs::metas::ValueKind::U8 },
                quote! { writer.write_u8(value)? },
                quote! { tokens.push(u8::to_string(&value)) },
            ),
            "u16" => (
                quote! { ::pcd_rs::metas::ValueKind::U16 },
                quote! { writer.write_u16::<LittleEndian>(value)? },
                quote! { tokens.push(u16::to_string(&value)) },
            ),
            "u32" => (
                quote! { ::pcd_rs::metas::ValueKind::U32 },
                quote! { writer.write_u32::<LittleEndian>(value)? },
                quote! { tokens.push(u32::to_string(&value)) },
            ),
            "u64" => (
                quote! { ::pcd_rs::metas::ValueKind::U64 },
                quote! { writer.write_u64::<LittleEndian>(value)? },
                quote! { tokens.push(u64::to_string(&value)) },
            ),
            "i8" => (
                quote! { ::pcd_rs::metas::ValueKind::I8 },
                quote! { writer.write_i8(value)? },
                quote! { tokens.push(i8::to_string(&value)) },
            ),
            "i16" => (
                quote! { ::pcd_rs::metas::ValueKind::I16 },
                quote! { writer.write_i16::<LittleEndian>(value)? },
                quote! { tokens.push(i16::to_string(&value)) },
            ),
            "i32" => (
                quote! { ::pcd_rs::metas::ValueKind::I32 },
                quote! { writer.write_i32::<LittleEndian>(value)? },
                quote! { tokens.push(i32::to_string(&value)) },
            ),
            "i64" => (
                quote! { ::pcd_rs::metas::ValueKind::I64 },
                quote! { writer.write_i64::<LittleEndian>(value)? },
                quote! { tokens.push(i64::to_string(&value)) },
            ),
            "f32" => (
                quote! { ::pcd_rs::metas::ValueKind::F32 },
                quote! { writer.write_f32::<LittleEndian>(value)? },
                quote! { tokens.push(f32::to_string(&value)) },
            ),
            "f64" => (
                quote! { ::pcd_rs::metas::ValueKind::F64 },
                quote! { writer.write_f64::<LittleEndian>(value)? },
                quote! { tokens.push(f64::to_string(&value)) },
            ),
            "Rgb" => (
                quote! { ::pcd_rs::metas::ValueKind::F32 },
                quote! { writer.write_f32::<LittleEndian>(value.to_packed())? },
                quote! { tokens.push(f32::to_string(&value.to_packed())) },
            ),
            "Rgba" => (
                quote! { ::pcd_rs::metas::ValueKind::F32 },
                quote! { writer.write_f32::<LittleEndian>(value.to_packed())? },
                quote! { tokens.push(f32::to_string(&value.to_packed())) },
            ),
            _ => return None,
        };

    let derived_tokens = DerivedTokens {
        write_spec_tokens,
        bin_write_tokens,
        text_write_tokens,
    };

    Some(derived_tokens)
}