binary_type_cast 0.1.2

A Rust crate for simplifying the process of parsing binary file data into various Rust data types using the TypeCast macro.
Documentation
extern crate proc_macro;
mod utils;

use proc_macro::TokenStream;
use quote::{format_ident, quote};
use utils::from_str::generated_from_str_impl;

use std::iter::repeat;
use syn;

use crate::utils::{
    cast_extraction::get_cast_types,
    try_into::{build_type_variants_map, generate_try_into_impls},
    CastTypeData,
};

#[doc = include_str!("doc.md")]
// Define the `TypeCast` custom derive macro
#[proc_macro_derive(TypeCast, attributes(cast))]
pub fn derive_macro(input: TokenStream) -> TokenStream {
    let ast: syn::DeriveInput = syn::parse(input).unwrap();
    let name = &ast.ident;

    // Declare mutable variables to store cast types, conversions, and other related information
    let data_type_names = repeat(name.clone());
    let complex_data_type_names = repeat(name.clone());
    let data_kind_name = format_ident!("{}Cast", name.clone());
    let data_kind_names = repeat(data_kind_name.clone());
    let complex_data_kind_names = repeat(data_kind_name.clone());

    let string_data_kind_names = name.clone();

    let cast_type_data = &mut CastTypeData::default();

    let errors = if let syn::Data::Enum(data_enum) = ast.data {
        // Call the `get_cast_types` function to extract the required information from the Enum
        // This should return an empty Vec if successful or return a Vec<TokenTree> of errors otherwise
        get_cast_types(data_enum, cast_type_data)
    } else {
        return syn::Error::new_spanned(&ast, "TypeCast can only be derived for enums")
            .to_compile_error()
            .into();
    };

    if !errors.is_empty() {
        return proc_macro2::TokenStream::from_iter(errors.into_iter()).into();
    }

    let type_variants_map = build_type_variants_map(&cast_type_data);

    let generated_try_into_impls = generate_try_into_impls(&type_variants_map, &data_kind_name);

    let CastTypeData {
        cast_types,
        complex_cast_types,
        complex_cast_types_group,
        conversion,
        number_of_array_elements,
        variants,
        complex_variants,
        string_types,
        string_variants,
        ..
    } = &cast_type_data;

    let gen = quote! {

        // Derive common traits and define the enum with primitive, complex, and string type variants. The enum's name is generated by appending "Cast" to the name of the enum decorated with #[derive(TypeCast)]
        #[derive(Clone, Debug, Serialize, Deserialize)]
        pub enum #data_kind_name {
            #(#variants(#cast_types),)*
            #(#complex_variants(#complex_cast_types_group),)*
            #(#string_variants(#string_types),)*
        }

        // Implement the parse method for the enum decorated with #[derive(TypeCast)]
        impl #name  {
            // The parse method takes a mutable reference to a byte slice and returns an instance of DataKind
            pub fn parse(self, input: &mut &[u8]) -> #data_kind_name {
                // Match the current variant of the enum decorated with #[derive(TypeCast)] and convert the input bytes accordingly
                match self {
                    #(
                        #data_type_names::#variants => #data_kind_names::#variants ({
                            // `bytes` should be the size of the entire data read in; therefore, the `rest` from the `split_at` can be ignored
                            let (bytes, _) = input.split_at(
                                std::mem::size_of::<#cast_types>()
                            );
                            <#cast_types>::#conversion(bytes.try_into().unwrap())
                        }),
                    )*
                    // Handle complex types
                    #(
                        #complex_data_type_names::#complex_variants => #complex_data_kind_names::#complex_variants ({
                            let mut tmp_vec = std::vec::Vec::new();
                            // Convert the byte information into the defined rust type from the cast. Push them to the temporary vector in this loop. This allows for an output array to be created that has the size of the array with the expected types defined in the cast.
                            for _ in 0..#number_of_array_elements {
                                let (bytes, rest) = input.split_at(
                                    std::mem::size_of::<#complex_cast_types>()
                                );
                                let converted = <#complex_cast_types>::from_le_bytes(bytes.try_into().unwrap());
                                // This allows the input to become the remaining bytes for the next iteration
                                *input = rest;
                                tmp_vec.push(converted);
                            }
                            // Transform the vec into the output array
                            let out: [#complex_cast_types;#number_of_array_elements]  = tmp_vec.into_iter().collect::<Vec<#complex_cast_types>>().try_into().unwrap();
                            out
                        }),
                    )*
                    // Handle string types
                    #(
                        #string_data_kind_names::#string_variants => #data_kind_name::#string_variants(String::from_utf8(input.to_vec()).unwrap())
                    )*

                }

            }

        }
    };

    let generated_from_str_impl = generated_from_str_impl(name, cast_type_data);

    let combined_gen = quote! {
        #gen
        #generated_try_into_impls
        #generated_from_str_impl
    };
    combined_gen.into()
}