ethercrab-wire-derive 0.3.0

Derive macros for EtherCrab
Documentation
use crate::parse_enum::EnumMeta;
use quote::quote;
use std::str::FromStr;
use syn::DeriveInput;

pub fn generate_enum_write(
    parsed: EnumMeta,
    input: &DeriveInput,
    gen_sized_impl: bool,
) -> proc_macro2::TokenStream {
    let name = input.ident.clone();
    let repr_type = parsed.repr_type;
    let size_bytes = match repr_type.to_string().as_str() {
        "u8" | "i8" => 1usize,
        "u16" | "i16" => 2,
        "u32" | "i32" => 4,
        "u64" | "i64" => 8,
        invalid => unreachable!("Invalid repr {}", invalid),
    };

    let pack = if parsed.catch_all.is_some() {
        let match_arms = parsed.variants.clone().into_iter().map(|variant| {
            let value =
                proc_macro2::TokenStream::from_str(&variant.discriminant.to_string()).unwrap();
            let variant_name = variant.name;

            if variant.catch_all {
                quote! {
                    #name::#variant_name (value) => { *value }
                }
            } else {
                quote! {
                    #name::#variant_name => { #value }
                }
            }
        });

        quote! {
            let value: #repr_type = match self {
                #(#match_arms),*
            };

            buf.copy_from_slice(&value.to_le_bytes());
        }
    } else {
        quote! {
            buf.copy_from_slice(&(*self as #repr_type).to_le_bytes());
        }
    };

    let sized_impl = if gen_sized_impl {
        quote! {
            impl ::ethercrab_wire::EtherCrabWireSized for #name {
                const PACKED_LEN: usize = #size_bytes;

                type Buffer = [u8; #size_bytes];

                fn buffer() -> Self::Buffer {
                    [0u8; #size_bytes]
                }
            }
        }
    } else {
        quote! {}
    };

    quote! {
        impl ::ethercrab_wire::EtherCrabWireWrite for #name {
            fn pack_to_slice_unchecked<'buf>(&self, buf: &'buf mut [u8]) -> &'buf [u8] {
                let mut buf = &mut buf[0..#size_bytes];

                unsafe {
                    buf.as_mut_ptr().write_bytes(0u8, #size_bytes);
                }

                #pack

                buf
            }

            fn packed_len(&self) -> usize {
                #size_bytes
            }
        }

        #sized_impl

        impl ::ethercrab_wire::EtherCrabWireWriteSized for #name {
            fn pack(&self) -> Self::Buffer {
                let mut buf = [0u8; #size_bytes];

                // Delegate to EtherCrabWireWrite impl above
                <Self as ::ethercrab_wire::EtherCrabWireWrite>::pack_to_slice_unchecked(self, &mut buf);

                buf
            }
        }


    }
}

pub fn generate_enum_read(parsed: EnumMeta, input: &DeriveInput) -> proc_macro2::TokenStream {
    let name = input.ident.clone();
    let repr_type = parsed.repr_type;
    let size_bytes = match repr_type.to_string().as_str() {
        "u8" | "i8" => 1usize,
        "u16" | "i16" => 2,
        "u32" | "i32" => 4,
        "u64" | "i64" => 8,
        invalid => unreachable!("Invalid repr {}", invalid),
    };

    let primitive_variants = parsed
        .variants
        .clone()
        .into_iter()
        .filter(|variant| !variant.catch_all);

    let result_match_arms = primitive_variants.clone().map(|variant| {
        let value = proc_macro2::TokenStream::from_str(&variant.discriminant.to_string()).unwrap();
        let variant_name = variant.name;

        quote! {
            #value => { Ok(Self::#variant_name) }
        }
    });

    let fallthrough = if let Some(ref catch_all_variant) = parsed.catch_all {
        let catch_all = catch_all_variant.name.clone();

        quote! {
            other => Ok(Self::#catch_all(other))
        }
    } else if let Some(ref default_variant) = parsed.default_variant {
        let default = default_variant.name.clone();

        quote! {
            _other => Ok(Self::#default)
        }
    } else {
        quote! {
            _other => { Err(::ethercrab_wire::WireError::InvalidValue) }
        }
    };

    let match_arms = primitive_variants.clone().map(|variant| {
        let value = proc_macro2::TokenStream::from_str(&variant.discriminant.to_string()).unwrap();
        let variant_name = variant.name;

        quote! {
            #value => { Self::#variant_name }
        }
    });

    let from_primitive_impl = if let Some(catch_all_variant) = &parsed.catch_all {
        let catch_all = catch_all_variant.name.clone();
        let match_arms = match_arms.clone();

        quote! {
            impl From<#repr_type> for #name {
                fn from(value: #repr_type) -> Self {
                    match value {
                        #(#match_arms),*
                        other => Self::#catch_all(other)
                    }
                }
            }
        }
    } else if let Some(default_variant) = parsed.default_variant {
        let default = default_variant.name;
        let match_arms = match_arms.clone();

        quote! {
            impl From<#repr_type> for #name {
                fn from(value: #repr_type) -> Self {
                    match value {
                        #(#match_arms),*
                        other => Self::#default
                    }
                }
            }
        }
    } else {
        let match_arms = result_match_arms.clone();

        quote! {
            impl TryFrom<#repr_type> for #name {
                type Error = ::ethercrab_wire::WireError;

                fn try_from(value: #repr_type) -> Result<Self, Self::Error> {
                    match value {
                        #(#match_arms),*
                        _other => Err(::ethercrab_wire::WireError::InvalidValue)
                    }
                }
            }
        }
    };

    let into_primitive_impl = if parsed.catch_all.is_some() {
        let match_arms_from = parsed.variants.clone().into_iter().map(|variant| {
            let value =
                proc_macro2::TokenStream::from_str(&variant.discriminant.to_string()).unwrap();
            let variant_name = variant.name;

            if variant.catch_all {
                quote! {
                    #name::#variant_name (value) => { value }
                }
            } else {
                quote! {
                    #name::#variant_name => { #value }
                }
            }
        });

        quote! {
            impl From<#name> for #repr_type {
                fn from(value: #name) -> Self {
                    match value {
                        #(#match_arms_from),*
                    }
                }
            }
        }
    } else {
        quote! {
            impl From<#name> for #repr_type {
                fn from(value: #name) -> Self {
                    value as #repr_type
                }
            }
        }
    };

    quote! {
        impl ::ethercrab_wire::EtherCrabWireRead for #name {
            fn unpack_from_slice(buf: &[u8]) -> Result<Self, ::ethercrab_wire::WireError> {
                let raw = buf.first_chunk::<#size_bytes>().map(|chunk| {
                    #repr_type::from_le_bytes(*chunk)
                }).ok_or(::ethercrab_wire::WireError::ReadBufferTooShort)?;

                match raw {
                    #(#result_match_arms),*
                    #fallthrough,
                }
            }
        }

        impl ::ethercrab_wire::EtherCrabWireSized for #name {
            const PACKED_LEN: usize = #size_bytes;

            type Buffer = [u8; #size_bytes];

            fn buffer() -> Self::Buffer {
                [0u8; #size_bytes]
            }
        }

        #from_primitive_impl
        #into_primitive_impl
    }
}