use crate::parse_struct::StructMeta;
use proc_macro2::{Ident, Span};
use quote::quote;
use std::str::FromStr;
use syn::DeriveInput;
pub fn generate_struct_write(parsed: &StructMeta, input: &DeriveInput) -> proc_macro2::TokenStream {
let name = input.ident.clone();
let size_bytes = parsed.width_bits.div_ceil(8);
let fields_pack = parsed.fields.clone().into_iter().map(|field| {
let name = field.name;
let field_ty = field.ty;
let byte_start = field.bytes.start;
let bit_start = field.bit_offset;
if field.skip {
return quote! {};
}
let ty_name = field
.ty_name
.unwrap_or_else(|| Ident::new("UnknownTypeStopLookingAtMe", Span::call_site()));
let field_access = if parsed.repr_packed {
quote! {{
unsafe { core::ptr::read_unaligned(&raw const self.#name) }
}}
} else {
quote! {self.#name}
};
if ty_name == "u8" || ty_name == "bool" {
let mask = (2u16.pow(field.bits.len() as u32) - 1) << bit_start;
let mask = proc_macro2::TokenStream::from_str(&format!("{:#010b}", mask)).unwrap();
quote! {
buf[#byte_start] |= ((#field_access as u8) << #bit_start) & #mask;
}
}
else if field.bytes.len() == 1 {
let mask = (2u16.pow(field.bits.len() as u32) - 1) << bit_start;
let mask = proc_macro2::TokenStream::from_str(&format!("{:#010b}", mask)).unwrap();
quote! {
let mut field_buf = [0u8; 1];
let res = <#field_ty as ::ethercrab_wire::EtherCrabWireWrite>::pack_to_slice_unchecked(&#field_access, &mut field_buf)[0];
buf[#byte_start] |= (res << #bit_start) & #mask;
}
}
else {
let byte_end = field.bytes.end;
quote! {
<#field_ty as ::ethercrab_wire::EtherCrabWireWrite>::pack_to_slice_unchecked(&#field_access, &mut buf[#byte_start..#byte_end]);
}
}
});
let (impl_generics, type_generics, where_clause) = parsed.generics.split_for_impl();
quote! {
impl #impl_generics ::ethercrab_wire::EtherCrabWireWrite for #name #type_generics
#where_clause {
fn pack_to_slice_unchecked<'buf>(&self, buf: &'buf mut [u8]) -> &'buf [u8] {
let buf = match buf.get_mut(0..#size_bytes) {
Some(buf) => buf,
None => unreachable!()
};
unsafe {
buf.as_mut_ptr().write_bytes(0u8, buf.len());
}
#(#fields_pack)*
buf
}
fn packed_len(&self) -> usize {
#size_bytes
}
}
impl #impl_generics ::ethercrab_wire::EtherCrabWireWriteSized for #name #type_generics
#where_clause {
fn pack(&self) -> Self::Buffer {
let mut buf = [0u8; #size_bytes];
<Self as ::ethercrab_wire::EtherCrabWireWrite>::pack_to_slice_unchecked(self, &mut buf);
buf
}
}
}
}
pub fn generate_struct_read(parsed: &StructMeta, input: &DeriveInput) -> proc_macro2::TokenStream {
let name = input.ident.clone();
let size_bytes = parsed.width_bits.div_ceil(8);
let fields_unpack = parsed.fields.clone().into_iter().map(|field| {
let ty = field.ty;
let name = field.name;
let byte_start = field.bytes.start;
let bit_start = field.bit_offset;
let ty_name = field
.ty_name
.unwrap_or_else(|| Ident::new("UnknownTypeStopLookingAtMe", Span::call_site()));
if field.skip {
return quote! {
#name: Default::default()
}
}
if field.bits.len() <= 8 {
let mask = (2u16.pow(field.bits.len() as u32) - 1) << bit_start;
let mask =
proc_macro2::TokenStream::from_str(&format!("{:#010b}", mask)).unwrap();
if ty_name == "bool" {
quote! {
#name: ((buf.get(#byte_start).ok_or(::ethercrab_wire::WireError::ReadBufferTooShort)? & #mask) >> #bit_start) > 0
}
}
else if ty_name == "u8" {
quote! {
#name: (buf.get(#byte_start).ok_or(::ethercrab_wire::WireError::ReadBufferTooShort)? & #mask) >> #bit_start
}
}
else {
quote! {
#name: {
let masked = (buf.get(#byte_start).ok_or(::ethercrab_wire::WireError::ReadBufferTooShort)? & #mask) >> #bit_start;
<#ty as ::ethercrab_wire::EtherCrabWireRead>::unpack_from_slice(&[masked])?
}
}
}
}
else {
let start_byte = field.bytes.start;
let end_byte = field.bytes.end;
quote! {
#name: <#ty as ::ethercrab_wire::EtherCrabWireRead>::unpack_from_slice(buf.get(#start_byte..#end_byte).ok_or(::ethercrab_wire::WireError::ReadBufferTooShort)?)?
}
}
});
let (impl_generics, type_generics, where_clause) = parsed.generics.split_for_impl();
quote! {
impl #impl_generics ::ethercrab_wire::EtherCrabWireRead for #name #type_generics
#where_clause {
fn unpack_from_slice(buf: &[u8]) -> Result<Self, ::ethercrab_wire::WireError> {
let buf = buf.get(0..#size_bytes).ok_or(::ethercrab_wire::WireError::ReadBufferTooShort)?;
Ok(Self {
#(#fields_unpack),*
})
}
}
}
}
pub fn generate_sized_impl(parsed: &StructMeta, input: &DeriveInput) -> proc_macro2::TokenStream {
let name = input.ident.clone();
let size_bytes = parsed.width_bits.div_ceil(8);
let (impl_generics, type_generics, where_clause) = parsed.generics.split_for_impl();
quote! {
impl #impl_generics ::ethercrab_wire::EtherCrabWireSized for #name #type_generics
#where_clause {
const PACKED_LEN: usize = #size_bytes;
type Buffer = [u8; #size_bytes];
fn buffer() -> Self::Buffer {
[0u8; #size_bytes]
}
}
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use ethercrab_wire::{EtherCrabWireRead, EtherCrabWireReadWrite, EtherCrabWireWrite};
#[test]
fn generic_struct() {
#[derive(EtherCrabWireReadWrite, PartialEq, Debug)]
#[wire(bytes = 8)]
struct TestTypeGeneric<T: EtherCrabWireReadWrite> {
#[wire(bits = 32)]
a: i32,
#[wire(bits = 32)]
b: T,
}
let test_type_generic = TestTypeGeneric::<u32> {
a: -16,
b: u32::MAX,
};
let mut slice = [0u8; 8];
test_type_generic.pack_to_slice(&mut slice).unwrap();
assert_eq!(
Ok(test_type_generic),
TestTypeGeneric::<u32>::unpack_from_slice(&slice)
);
#[derive(EtherCrabWireReadWrite, PartialEq, Debug)]
#[wire(bytes = 8)]
struct TestWhereClause<T>
where
T: EtherCrabWireReadWrite,
{
#[wire(bits = 32)]
a: i32,
#[wire(bits = 32)]
b: T,
}
let test_where_clause = TestWhereClause::<u32> {
a: -16,
b: u32::MAX,
};
let mut slice = [0u8; 8];
test_where_clause.pack_to_slice(&mut slice).unwrap();
assert_eq!(
Ok(test_where_clause),
TestWhereClause::<u32>::unpack_from_slice(&slice)
);
}
}