mod constants;
mod models;
mod utils;
use crate::models::{ByteMeField, ByteMeStruct};
#[proc_macro_derive(ByteMe, attributes(byte_me))]
pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
let strukt = syn::parse_macro_input!(tokens as ByteMeStruct);
let fn_lines_to_bytes = strukt.fields.iter().clone().map(to_bytes_fn_factory);
let count = core::cell::Cell::new(0_usize);
let fn_line_from_bytes = strukt
.fields
.iter()
.clone()
.map(|field| from_bytes_fn_factory(field, &count));
let name = &strukt.ident;
let size: usize = strukt.fields.clone().iter().clone().map(|field| field.size).sum();
let fields = strukt.fields.iter().clone().map(get_field_name);
let processed = quote::quote! {
impl #name {
pub const SIZE: usize = #size;
pub fn get_delimiter(self) -> Vec<u8> {
(Self::SIZE as u16).to_be_bytes().to_vec()
}
}
impl From<Vec<u8>> for #name {
fn from(bytes: Vec<u8>) -> Self {
use num_traits::FromPrimitive;
#(#fn_line_from_bytes)*
Self {
#(#fields)*
}
}
}
impl From<#name> for Vec<u8> {
fn from(d: #name) -> Self {
let mut bytes = Vec::new();
#(#fn_lines_to_bytes)*
bytes
}
}
};
processed.into()
}
fn to_bytes_fn_factory(field: &ByteMeField) -> proc_macro2::TokenStream {
let name = &field.ident;
let data_type = &field.data_type;
if field.is_array {
quote::quote! {
bytes.extend_from_slice(&(d.#name));
}
} else {
quote::quote! {
bytes.extend_from_slice(&(d.#name as #data_type).to_be_bytes().to_vec());
}
}
}
fn from_bytes_fn_factory(field: &ByteMeField, count: &core::cell::Cell<usize>) -> proc_macro2::TokenStream {
let name = &field.ident;
let size = &field.size;
let data_type = &field.data_type;
let start = count.get();
let end = start + field.size;
count.set(end);
let lines = quote::quote! {
let #name: [u8; #size] = bytes[#start .. #end].try_into().unwrap();
};
let lines = if !field.is_array && field.attribute.is_none() {
quote::quote! {
#lines
let #name = #data_type::from_be_bytes(#name);
}
} else {
quote::quote! {
#lines
}
};
let lines = if !field.is_array && field.attribute.is_some() {
let enum_name = &field.attribute.clone();
let enum_data_type = data_type.to_string();
let from_enum_data_type = syn::Ident::new(
format!("from_{}", enum_data_type).as_str(),
proc_macro2::Span::call_site(),
);
quote::quote! {
#lines
let #name = #data_type::from_be_bytes(#name);
let #name = #enum_name::#from_enum_data_type(#name).unwrap();
}
} else {
quote::quote! {
#lines
}
};
lines
}
fn get_field_name(field: &ByteMeField) -> proc_macro2::TokenStream {
let name = field.ident.clone();
quote::quote! {#name,}
}