use quote::{quote, format_ident}; use proc_macro2::Span; use proc_macro::TokenStream; use syn::{DeriveInput, Data::Enum, parse_macro_input}; #[proc_macro_derive(AllVariants)] pub fn derive_all_variants(input: TokenStream) -> TokenStream { let syn_item: DeriveInput = parse_macro_input!(input); let variants = match syn_item.data { Enum(enum_item) => { enum_item.variants.into_iter().map(|v|v.ident) }, _ => panic!("AllVariants only works on enums!"), }; let enum_name = syn_item.ident; let expanded = quote! { impl #enum_name { fn all_variants() -> &'static[#enum_name] { &[ #(#enum_name::#variants),* ] } } }; expanded.into() } #[proc_macro_derive(ParseBin, attributes(skip_bytes, len))] pub fn derive_parse_bin(input: TokenStream) -> TokenStream { let syn_item: DeriveInput = parse_macro_input!(input); let name = syn_item.ident; match syn_item.data { syn::Data::Struct(struct_item) => { match struct_item.fields { syn::Fields::Named(fields_named) => { derive_parse_bin_struct(&name, &fields_named.named) }, syn::Fields::Unnamed(fields_unnamed) => { if fields_unnamed.unnamed.len() != 1 { panic!("ParseBin does not support tuple structs!"); } let inner_type = fields_unnamed.unnamed.into_iter().next().unwrap().ty; derive_parse_bin_alias(name, inner_type) }, _ => panic!("ParseBin on unit structs makes no sense!"), } }, _ => panic!("ParseBin only works on structs so far!"), } } fn derive_parse_bin_alias(name: syn::Ident, ty: syn::Type) -> TokenStream { quote! { impl ParseBin for #name { fn parse_len(bytes: &[u8]) -> Result<(Self, usize), ParseError> { let (result, size) = <#ty>::parse_len(bytes)?; Ok((#name(result), size)) } } }.into() } fn derive_parse_bin_struct<'a, T>(name: &syn::Ident, fields: T) -> TokenStream where T: IntoIterator { let mut parsing_statements = Vec::new(); let mut combining_expressions = Vec::new(); for field in fields { let field_name = field.ident.as_ref().unwrap(); let field_type = &field.ty; let mut skip: Option = None; let mut veclen: Option = None; // look for attributes for at in &field.attrs { if let syn::Meta::NameValue(nv) = &at.meta { if nv.path.segments.len() == 1 { let attr_name = nv.path.segments[0].ident.to_string(); if attr_name == "skip_bytes" { if let syn::Expr::Lit(expr) = &nv.value { if let syn::Lit::Int(nbytes) = &expr.lit { // println!("reserved = {}", nbytes); skip = nbytes.base10_parse::().ok() } } } else if attr_name == "len" { if let syn::Expr::Lit(expr) = &nv.value { if let syn::Lit::Str(litstr) = &expr.lit { // println!("len = {}", litstr.value()); veclen = Some(litstr.value()); } } } } } } if let Some(offset) = skip { parsing_statements.push(quote!{ __parse_bin_derive_size += #offset; }); } if let Some(varname) = veclen { let field_name_item = format_ident!("{}_item", field_name); enum FieldType<'a> { Vec(&'a syn::Type), CString, } let syn::Type::Path(tp) = &field_type else { panic!() }; let single = tp.path.segments.iter().next().unwrap(); let field_type = if &single.ident.to_string() == "Vec" { let syn::PathArguments::AngleBracketed(args) = &single.arguments else { panic!() }; let firstarg = args.args.iter().next().unwrap(); let syn::GenericArgument::Type(ty) = firstarg else { panic!() }; FieldType::Vec(ty) } else if &single.ident.to_string() == "CString" { FieldType::CString } else { panic!("The len attribute is only allowed on Vec<_> or CString") }; let varname_ident = syn::Ident::new(&varname, Span::call_site()); match field_type { FieldType::Vec(field_type_item) => { parsing_statements.push(quote!{ let mut #field_name = Vec::new(); for i in 0 .. #varname_ident.0 as usize { let #field_name_item = <#field_type_item>::parse_len(&bytes[__parse_bin_derive_size..])?; __parse_bin_derive_size += #field_name_item.1; #field_name.push(#field_name_item.0); } }); combining_expressions.push(quote!(#field_name: #field_name)); }, FieldType::CString => { parsing_statements.push(quote!{ let #field_name = CString::parse_len(&bytes[__parse_bin_derive_size .. __parse_bin_derive_size + #varname_ident.0 as usize])?; __parse_bin_derive_size += #varname_ident.0 as usize; }); combining_expressions.push(quote!(#field_name: #field_name.0)); }, } } else { parsing_statements.push(quote!{ let #field_name = <#field_type>::parse_len(&bytes[__parse_bin_derive_size..])?; __parse_bin_derive_size += #field_name.1; }); combining_expressions.push(quote!(#field_name: #field_name.0)); } } quote! { impl ParseBin for #name { fn parse_len(bytes: &[u8]) -> Result<(Self, usize), ParseError> { let mut __parse_bin_derive_size: usize = 0; #(#parsing_statements)* let result = #name { #(#combining_expressions),* }; Ok((result, __parse_bin_derive_size)) } } }.into() }