use darling::{ast, FromDeriveInput, FromField, FromMeta, FromVariant};
use proc_macro2::TokenStream;
use quote::quote;
mod macros;
use crate::macros::{deku_read::emit_deku_read, deku_write::emit_deku_write};
#[derive(Debug, Clone, Copy, PartialEq, FromMeta)]
#[darling(default)]
pub(crate) enum EndianNess {
Little,
Big,
}
impl Default for EndianNess {
fn default() -> Self {
#[cfg(target_endian = "little")]
let ret = EndianNess::Little;
#[cfg(target_endian = "big")]
let rets = EndianNess::Big;
ret
}
}
#[derive(Debug, FromDeriveInput)]
#[darling(
attributes(deku),
supports(struct_any, enum_any),
map = "DekuReceiver::init"
)]
struct DekuReceiver {
vis: syn::Visibility,
ident: syn::Ident,
generics: syn::Generics,
data: ast::Data<DekuVariantReceiver, DekuFieldReceiver>,
#[darling(default)]
endian: EndianNess,
#[darling(default)]
id_type: Option<syn::Ident>,
#[darling(default)]
id_bits: Option<usize>,
#[darling(default)]
id_bytes: Option<usize>,
}
impl DekuReceiver {
fn init(self) -> Self {
if (self.id_type.is_some() || self.id_bits.is_some() || self.id_bytes.is_some())
&& !self.data.is_enum()
{
panic!("`id_*` attributes only supported on enum")
}
if (self.id_bits.is_some() || self.id_bytes.is_some()) && self.id_type.is_none() {
panic!("`id_type` must be specified with `id_bits` or `id_bytes`");
}
if self.id_bits.is_some() && self.id_bytes.is_some() {
panic!("conflicting: both \"id_bits\" and \"id_bytes\" specified on field");
}
let id_bits = self.id_bits.or_else(|| self.id_bytes.map(|v| v * 8));
let id_bytes = None;
Self {
id_bits,
id_bytes,
..self
}
}
fn emit_reader(&self) -> Result<TokenStream, darling::Error> {
emit_deku_read(self)
}
fn emit_writer(&self) -> Result<TokenStream, darling::Error> {
emit_deku_write(self)
}
}
fn option_as_tokenstream(input: Option<String>) -> Option<TokenStream> {
input.map(|v| {
v.parse::<TokenStream>()
.expect("could not parse token stream")
})
}
fn gen_field_ident<T: ToString>(ident: Option<T>, index: usize, prefix: bool) -> TokenStream {
let field_name = match ident {
Some(field_name) => field_name.to_string(),
None => {
let index = syn::Index::from(index);
let prefix = if prefix { "field_" } else { "" };
format!("{}{}", prefix, quote! { #index })
}
};
field_name.parse().unwrap()
}
#[derive(Debug, FromField)]
#[darling(attributes(deku), map = "DekuFieldReceiver::init")]
struct DekuFieldReceiver {
ident: Option<syn::Ident>,
ty: syn::Type,
#[darling(default)]
endian: Option<EndianNess>,
#[darling(default)]
bits: Option<usize>,
#[darling(default)]
bytes: Option<usize>,
#[darling(default, map = "option_as_tokenstream")]
count: Option<TokenStream>,
#[darling(default, map = "option_as_tokenstream")]
map: Option<TokenStream>,
#[darling(default, map = "option_as_tokenstream")]
update: Option<TokenStream>,
#[darling(default, map = "option_as_tokenstream")]
reader: Option<TokenStream>,
#[darling(default, map = "option_as_tokenstream")]
writer: Option<TokenStream>,
#[darling(default)]
skip: bool,
#[darling(default, map = "option_as_tokenstream")]
default: Option<TokenStream>,
}
impl DekuFieldReceiver {
fn init(self) -> Self {
if self.bits.is_some() && self.bytes.is_some() {
panic!("conflicting: both \"bits\" and \"bytes\" specified on field");
}
let bits = self.bits.or_else(|| self.bytes.map(|v| v * 8));
let bytes = None;
if self.default.is_some() && !self.skip {
panic!("`default` attribute must be used with `skip`");
}
let default = if self.skip && self.default.is_none() {
Some(quote! { Default::default() })
} else {
self.default
};
Self {
bits,
bytes,
default,
..self
}
}
fn get_ident(&self, index: usize, prefix: bool) -> TokenStream {
let field_ident = gen_field_ident(self.ident.as_ref(), index, prefix);
quote! { #field_ident }
}
}
#[derive(Debug, FromVariant)]
#[darling(attributes(deku), map = "DekuVariantReceiver::init")]
struct DekuVariantReceiver {
ident: syn::Ident,
fields: ast::Fields<DekuFieldReceiver>,
#[darling(default, map = "option_as_tokenstream")]
reader: Option<TokenStream>,
#[darling(default, map = "option_as_tokenstream")]
writer: Option<TokenStream>,
id: String,
}
impl DekuVariantReceiver {
fn init(self) -> Self {
self
}
}
#[proc_macro_derive(DekuRead, attributes(deku))]
pub fn proc_deku_read(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let receiver = DekuReceiver::from_derive_input(&syn::parse(input).unwrap()).unwrap();
let tokens = receiver.emit_reader().unwrap();
tokens.into()
}
#[proc_macro_derive(DekuWrite, attributes(deku))]
pub fn proc_deku_write(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let receiver = DekuReceiver::from_derive_input(&syn::parse(input).unwrap()).unwrap();
let tokens = receiver.emit_writer().unwrap();
tokens.into()
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
use syn::parse_str;
#[rstest(input,
case::struct_empty(r#"struct Test {}"#),
case::struct_unnamed(r#"struct Test(u8, u8);"#),
case::struct_unnamed_attrs(r#"struct Test(#[deku(bits=4)] u8, u8);"#),
case::struct_all_attrs(r#"
struct Test {
#[deku(bits = 4)]
field_a: u8,
#[deku(bytes = 4)]
field_b: u64,
#[deku(endian = little)]
field_c: u32,
#[deku(endian = big)]
field_d: u32,
#[deku(skip, default = "5")]
field_e: u32,
}"#),
#[should_panic(expected = "UnknownField(ErrorUnknownField { name: \"sbits\", did_you_mean: Some(\"bits\") })")]
case::invalid_field(r#"struct Test(#[deku(sbits=4)] u8);"#),
#[should_panic(expected = "DuplicateField(\"bits\")")]
case::invalid_field_duplicate(r#"struct Test(#[deku(bits=4, bits=5)] u8);"#),
#[should_panic(expected = "conflicting: both \"bits\" and \"bytes\" specified on field")]
case::invalid_field_bitsnbytes(r#"struct Test(#[deku(bits=4, bytes=1)] u8);"#),
#[should_panic(expected = "`id_*` attributes only supported on enum")]
case::invalid_struct_id_type(r#"#[deku(id_type="u8")] struct Test(u8);"#),
#[should_panic(expected = "`default` attribute must be used with `skip`")]
case::invalid_default(r#"struct Test(u8, #[deku(default ="asd")] Vec<u8>);"#),
case::enum_empty(r#"#[deku(id_type = "u8")] enum Test {}"#),
case::enum_all(r#"
#[deku(id_type = "u8")]
enum Test {
#[deku(id = "1")]
A,
#[deku(id = "2")]
B(#[deku(bits = 4)] u8),
#[deku(id = "3")]
C { field_n: u8 },
}"#),
#[should_panic(expected = "expected `id_type` on enum")]
case::invalid_expected_id_type(r#"enum Test { #[deku(id="1")] A }"#),
#[should_panic(expected = "`id_type` must be specified with `id_bits` or `id_bytes`")]
case::invalid_expected_id_type(r#"#[deku(id_bits="5")] enum Test { #[deku(id="1")] A }"#),
#[should_panic(expected = "`id_type` must be specified with `id_bits` or `id_bytes`")]
case::invalid_expected_id_type(r#"#[deku(id_bytes="5")] enum Test { #[deku(id="1")] A }"#),
#[should_panic(expected = "conflicting: both \"id_bits\" and \"id_bytes\" specified on field")]
case::invalid_conflict(r#"#[deku(id_type="u8", id_bytes="5", id_bits="5")] enum Test { #[deku(id="1")] A }"#),
#[should_panic(expected = "MissingField(\"id\")")]
case::invalid_expected_id(r#"#[deku(id_type="u8")] enum Test { A }"#),
case::invalid_storage(r#"struct Test(#[deku(bits=9)] u8);"#),
case::invalid_endian(r#"struct Test(#[endian=big] u8);"#),
)]
fn test_macro(input: &str) {
let parsed = parse_str(input).unwrap();
let receiver = DekuReceiver::from_derive_input(&parsed).unwrap();
let res_reader = receiver.emit_reader();
let res_writer = receiver.emit_writer();
res_reader.unwrap();
res_writer.unwrap();
}
}