5
\$\begingroup\$

I wrote a trait library that allows the de-/serialization of data structures from/to little endian bytes 1.

For it I now wrote my first procedural macro 2 to be able to derive the traits FromLeBytes and ToLeBytes for structs:

use proc_macro2::TokenStream;
use quote::quote;
use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields, GenericParam, Generics};

pub fn from_le_bytes(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    // Parse the input tokens into a syntax tree.
    let input = parse_macro_input!(input as DeriveInput);

    // Used in the quasi-quotation below as `#name`.
    let name = input.ident;

    let generics = add_trait_bounds(input.generics);
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    // Generate an expression to sum up the heap size of each field.
    let body = impl_body(&input.data);

    let expanded = quote! {
        // The generated impl.
        impl #impl_generics le_stream::FromLeBytes for #name #ty_generics #where_clause {
            fn from_le_bytes<T>(bytes: &mut T) -> le_stream::Result<Self>
            where
                T: Iterator<Item = u8>
            {
                #body
            }
        }
    };

    // Hand the output tokens back to the compiler.
    proc_macro::TokenStream::from(expanded)
}

// Add a bound `T: FromLeBytes` to every type parameter T.
fn add_trait_bounds(mut generics: Generics) -> Generics {
    for param in &mut generics.params {
        if let GenericParam::Type(ref mut type_param) = *param {
            type_param.bounds.push(parse_quote!(le_stream::FromLeBytes));
        }
    }
    generics
}

fn impl_body(data: &Data) -> TokenStream {
    match *data {
        Data::Struct(ref structure) => match structure.fields {
            Fields::Named(ref fields) => {
                let mut tokens = TokenStream::new();
                let mut constructor_fields = TokenStream::new();

                for field in &fields.named {
                    let item_name = field.ident.clone().expect("struct field has no name");
                    let item_type = &field.ty;

                    tokens.extend(quote! {
                            let #item_name = <#item_type as le_stream::FromLeBytes>::from_le_bytes(bytes)?;
                        });

                    constructor_fields.extend(quote! {
                        #item_name,
                    });
                }

                tokens.extend(quote! { Ok(Self { #constructor_fields }) });
                tokens
            }
            _ => unimplemented!(),
        },
        Data::Enum(_) | Data::Union(_) => unimplemented!(),
    }
}
use proc_macro2::TokenStream;
use quote::quote;
use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields, GenericParam, Generics};

pub fn to_le_bytes(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    // Parse the input tokens into a syntax tree.
    let input = parse_macro_input!(input as DeriveInput);

    // Used in the quasi-quotation below as `#name`.
    let name = input.ident;

    let generics = add_trait_bounds(input.generics);
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    // Generate an expression to sum up the heap size of each field.
    let (iterator_statement, iterator_type) = impl_body(&input.data);

    let expanded = quote! {
        // The generated impl.
        impl #impl_generics le_stream::ToLeBytes for #name #ty_generics #where_clause {
            type Iter = #iterator_type;

            fn to_le_bytes(&self) -> <Self as ToLeBytes>::Iter {
                #iterator_statement
            }
        }
    };

    // Hand the output tokens back to the compiler.
    proc_macro::TokenStream::from(expanded)
}

// Add a bound `T: ToLeBytes` to every type parameter T.
fn add_trait_bounds(mut generics: Generics) -> Generics {
    for param in &mut generics.params {
        if let GenericParam::Type(ref mut type_param) = *param {
            type_param.bounds.push(parse_quote!(le_stream::ToLeBytes));
        }
    }
    generics
}

fn impl_body(data: &Data) -> (TokenStream, TokenStream) {
    match *data {
        Data::Struct(ref structure) => match structure.fields {
            Fields::Named(ref fields) => {
                let mut iterator_type = quote! { std::iter::Empty<u8> };
                let mut iterator_statement = quote! { std::iter::empty::<u8>() };

                for field in &fields.named {
                    let item_name = field.ident.clone().expect("struct field has no name");
                    let item_type = &field.ty;

                    iterator_statement.extend(quote! {
                        .chain(<#item_type as le_stream::ToLeBytes>::to_le_bytes(&self.#item_name))
                    });

                    iterator_type = quote! {
                        std::iter::Chain<#iterator_type, <#item_type as le_stream::ToLeBytes>::Iter>
                    };
                }

                (iterator_statement, iterator_type)
            }
            _ => unimplemented!(),
        },
        Data::Enum(_) | Data::Union(_) => unimplemented!(),
    }
}
mod from_le_bytes;
mod to_le_bytes;

use from_le_bytes::from_le_bytes;
use to_le_bytes::to_le_bytes;

#[proc_macro_derive(FromLeBytes)]
pub fn derive_from_le_bytes(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    from_le_bytes(input)
}

#[proc_macro_derive(ToLeBytes)]
pub fn derive_to_le_bytes(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    to_le_bytes(input)
}

Since this is the first macro I ever wrote, I'd like to have feedback on how I could improve its implementation.

\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.