mirror of
https://codeberg.org/icewind/bitbuffer.git
synced 2026-06-03 08:34:07 +02:00
rewrite derive macro
This commit is contained in:
parent
0701318120
commit
3852f09dd5
21 changed files with 1548 additions and 840 deletions
|
|
@ -12,10 +12,11 @@ name = "bitbuffer_derive"
|
|||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = "1.0"
|
||||
quote = "1.0"
|
||||
proc-macro2 = "1.0"
|
||||
syn_util = "0.4"
|
||||
syn = { version = "2.0.22", features = ["extra-traits"] }
|
||||
quote = "1.0.29"
|
||||
proc-macro2 = "1.0.63"
|
||||
structmeta = "0.2.0"
|
||||
merge = "0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
bitbuffer = { version = "0.10", path = ".." }
|
||||
|
|
|
|||
|
|
@ -1,36 +1,105 @@
|
|||
use syn::{Expr, Lit, Variant};
|
||||
use syn_util::get_attribute_value;
|
||||
use crate::err;
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, quote_spanned};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{Error, Expr, ExprLit, Lit, LitInt};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum Discriminant {
|
||||
Int(usize),
|
||||
Default,
|
||||
Wildcard,
|
||||
}
|
||||
|
||||
impl From<Lit> for Discriminant {
|
||||
fn from(lit: Lit) -> Self {
|
||||
match lit {
|
||||
Lit::Int(lit) => Discriminant::Int(lit.base10_parse::<usize>().unwrap()),
|
||||
Lit::Str(lit) => match lit.value().as_str() {
|
||||
"_" => Discriminant::Wildcard,
|
||||
_ => panic!("discriminant is required to be an integer literal or \"_\""),
|
||||
},
|
||||
_ => panic!("discriminant is required to be an integer literal or \"_\""),
|
||||
impl TryFrom<Expr> for Discriminant {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: Expr) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Expr::Lit(ExprLit { lit, .. }) => lit.try_into(),
|
||||
_ => err("non literal discriminant", value.span())?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Variant> for Discriminant {
|
||||
fn from(variant: &Variant) -> Self {
|
||||
variant
|
||||
.discriminant
|
||||
.as_ref()
|
||||
.map(|(_, expr)| match expr {
|
||||
Expr::Lit(expr_lit) => expr_lit.lit.clone(),
|
||||
_ => panic!("discriminant is required to be an integer literal"),
|
||||
})
|
||||
.or_else(|| get_attribute_value(&variant.attrs, &["discriminant"]))
|
||||
.map(Discriminant::from)
|
||||
.unwrap_or(Discriminant::Default)
|
||||
impl TryFrom<Lit> for Discriminant {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: Lit) -> Result<Self, Self::Error> {
|
||||
let span = value.span();
|
||||
match value {
|
||||
Lit::Int(lit) => Ok(Discriminant::Int(lit.base10_parse()?)),
|
||||
Lit::Str(lit) => match lit.value().as_str() {
|
||||
"_" => Ok(Discriminant::Wildcard),
|
||||
_ => err(
|
||||
"discriminant is required to be an integer literal or \"_\"",
|
||||
span,
|
||||
),
|
||||
},
|
||||
_ => err(
|
||||
"discriminant is required to be an integer literal or \"_\"",
|
||||
span,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Discriminant {
|
||||
pub fn read_token(&self, last_discriminant: &mut isize, span: Span) -> TokenStream {
|
||||
match self {
|
||||
Discriminant::Int(discriminant) => {
|
||||
let lit = LitInt::new(&format!("{}", discriminant), span);
|
||||
*last_discriminant = *discriminant as isize;
|
||||
quote! { #lit }
|
||||
}
|
||||
Discriminant::Wildcard => quote! { _ },
|
||||
Discriminant::Default => {
|
||||
let new_discriminant = (*last_discriminant + 1) as usize;
|
||||
let lit = LitInt::new(&format!("{}", new_discriminant), span);
|
||||
*last_discriminant += 1;
|
||||
quote! { #lit }
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn write_token(
|
||||
&self,
|
||||
last_discriminant: &mut isize,
|
||||
max_discriminant: usize,
|
||||
span: Span,
|
||||
) -> TokenStream {
|
||||
match self {
|
||||
Discriminant::Int(discriminant) => {
|
||||
let lit = LitInt::new(&format!("{}", discriminant), span);
|
||||
*last_discriminant = *discriminant as isize;
|
||||
quote_spanned! { span => #lit }
|
||||
}
|
||||
Discriminant::Wildcard => {
|
||||
let free_discriminant = max_discriminant + 1;
|
||||
let lit = LitInt::new(&format!("{}", free_discriminant), span);
|
||||
quote_spanned! { span => #lit }
|
||||
}
|
||||
Discriminant::Default => {
|
||||
let new_discriminant = (*last_discriminant + 1) as usize;
|
||||
let lit = LitInt::new(&format!("{}", new_discriminant), span);
|
||||
*last_discriminant += 1;
|
||||
quote_spanned! { span => #lit }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max_value(&self, last_discriminant: &mut isize) -> usize {
|
||||
match self {
|
||||
Discriminant::Int(discriminant) => {
|
||||
*last_discriminant = *discriminant as isize;
|
||||
*discriminant
|
||||
}
|
||||
Discriminant::Wildcard => 0,
|
||||
Discriminant::Default => {
|
||||
let new_discriminant = (*last_discriminant + 1) as usize;
|
||||
*last_discriminant += 1;
|
||||
new_discriminant
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,498 +165,110 @@
|
|||
//! ```
|
||||
//!
|
||||
mod discriminant;
|
||||
mod params;
|
||||
mod read;
|
||||
mod size_hint;
|
||||
mod write;
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
use crate::write::derive_bitwrite_trait;
|
||||
use discriminant::Discriminant;
|
||||
use crate::read::{Read, ReadSized};
|
||||
use crate::write::{Write, WriteSized};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{
|
||||
parse_macro_input, parse_quote, parse_str, Attribute, Data, DataStruct, DeriveInput, Expr,
|
||||
Fields, GenericParam, Ident, Lit, LitInt, LitStr, Path,
|
||||
};
|
||||
use syn_util::{contains_attribute, get_attribute_value};
|
||||
use std::fmt::Display;
|
||||
use syn::{parse_macro_input, DeriveInput, Error, Result};
|
||||
|
||||
/// See the [crate documentation](index.html) for details
|
||||
#[proc_macro_derive(
|
||||
BitRead,
|
||||
attributes(size, size_bits, discriminant_bits, discriminant, endianness, align)
|
||||
attributes(
|
||||
bitbuffer,
|
||||
size,
|
||||
size_bits,
|
||||
discriminant_bits,
|
||||
discriminant,
|
||||
endianness,
|
||||
align
|
||||
)
|
||||
)]
|
||||
pub fn derive_bitread(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
derive_bitread_trait(input, "BitRead".to_owned(), None)
|
||||
derive_trait::<Read>(input)
|
||||
}
|
||||
|
||||
//
|
||||
/// See the [crate documentation](index.html) for details
|
||||
#[proc_macro_derive(
|
||||
BitReadSized,
|
||||
attributes(size, size_bits, discriminant_bits, discriminant, endianness, align)
|
||||
attributes(
|
||||
bitbuffer,
|
||||
size,
|
||||
size_bits,
|
||||
discriminant_bits,
|
||||
discriminant,
|
||||
endianness,
|
||||
align
|
||||
)
|
||||
)]
|
||||
pub fn derive_bitread_sized(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let extra_param = parse_str::<TokenStream>(", input_size: usize").unwrap();
|
||||
derive_bitread_trait(input, "BitReadSized".to_owned(), Some(extra_param))
|
||||
derive_trait::<ReadSized>(input)
|
||||
}
|
||||
|
||||
/// See the [crate documentation](index.html) for details
|
||||
#[proc_macro_derive(
|
||||
BitWrite,
|
||||
attributes(size, size_bits, discriminant_bits, discriminant, endianness, align)
|
||||
attributes(
|
||||
bitbuffer,
|
||||
size,
|
||||
size_bits,
|
||||
discriminant_bits,
|
||||
discriminant,
|
||||
endianness,
|
||||
align
|
||||
)
|
||||
)]
|
||||
pub fn derive_bitwrite(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
derive_bitwrite_trait(input, "BitWrite".into(), "write".into(), None)
|
||||
derive_trait::<Write>(input)
|
||||
}
|
||||
|
||||
//
|
||||
/// See the [crate documentation](index.html) for details
|
||||
#[proc_macro_derive(
|
||||
BitWriteSized,
|
||||
attributes(size, size_bits, discriminant_bits, discriminant, endianness, align)
|
||||
attributes(
|
||||
bitbuffer,
|
||||
size,
|
||||
size_bits,
|
||||
discriminant_bits,
|
||||
discriminant,
|
||||
endianness,
|
||||
align
|
||||
)
|
||||
)]
|
||||
pub fn derive_bitwrite_sized(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let extra_param = parse_str::<TokenStream>(", input_size: usize").unwrap();
|
||||
derive_bitwrite_trait(
|
||||
input,
|
||||
"BitWriteSized".into(),
|
||||
"write_sized".into(),
|
||||
Some(extra_param),
|
||||
)
|
||||
derive_trait::<WriteSized>(input)
|
||||
}
|
||||
|
||||
fn derive_bitread_trait(
|
||||
input: proc_macro::TokenStream,
|
||||
trait_name: String,
|
||||
extra_param: Option<TokenStream>,
|
||||
) -> proc_macro::TokenStream {
|
||||
/// Basic wrapper for error handling
|
||||
fn derive_trait<Trait: Derivable>(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let input: DeriveInput = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
let name = &input.ident;
|
||||
|
||||
let endianness = get_attribute_value(&input.attrs, &["endianness"]);
|
||||
let mut trait_generics = input.generics.clone();
|
||||
// we need these separate generics to only add out Endianness param to the 'impl'
|
||||
let (_, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
let lifetime: Option<&GenericParam> = trait_generics
|
||||
.params
|
||||
.iter()
|
||||
.find(|param| matches!(param, GenericParam::Lifetime(_)));
|
||||
let lifetime = match lifetime {
|
||||
Some(GenericParam::Lifetime(lifetime)) => lifetime.lifetime.clone(),
|
||||
_ => {
|
||||
trait_generics.params.push(parse_quote!('a));
|
||||
parse_quote!('a)
|
||||
}
|
||||
};
|
||||
if endianness.is_none() {
|
||||
trait_generics
|
||||
.params
|
||||
.push(parse_quote!(_E: ::bitbuffer::Endianness));
|
||||
}
|
||||
let (impl_generics, _, _) = trait_generics.split_for_impl();
|
||||
let span = input.span();
|
||||
|
||||
let size = size(
|
||||
input.data.clone(),
|
||||
name,
|
||||
&input.attrs,
|
||||
extra_param.is_some(),
|
||||
);
|
||||
let parsed = parse(input.data.clone(), name, &input.attrs, false);
|
||||
let parsed_unchecked = parse(input.data.clone(), name, &input.attrs, true);
|
||||
|
||||
let endianness_placeholder = endianness.unwrap_or_else(|| "_E".to_owned());
|
||||
let trait_def_str = format!(
|
||||
"::bitbuffer::{}<{}, {}>",
|
||||
trait_name, lifetime, &endianness_placeholder
|
||||
);
|
||||
let trait_def = parse_str::<Path>(&trait_def_str).unwrap();
|
||||
|
||||
let endianness_ident = Ident::new(&endianness_placeholder, span);
|
||||
|
||||
let size_extra_param = if extra_param.is_some() {
|
||||
Some(quote!(input_size: usize))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let extra_param_call = if extra_param.is_some() {
|
||||
Some(quote!(input_size,))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let size_method_name = Ident::new(
|
||||
if extra_param.is_some() {
|
||||
"bit_size_sized"
|
||||
} else {
|
||||
"bit_size"
|
||||
},
|
||||
Span::call_site(),
|
||||
);
|
||||
|
||||
let expanded = quote! {
|
||||
impl #impl_generics #trait_def for #name #ty_generics #where_clause {
|
||||
#[allow(unused_braces)]
|
||||
fn read(stream: &mut ::bitbuffer::BitReadStream<#lifetime, #endianness_ident>#extra_param) -> ::bitbuffer::Result<Self> {
|
||||
// if the read has a predicable size, we can do the bounds check in one go
|
||||
match <Self as #trait_def>::#size_method_name(#extra_param_call) {
|
||||
Some(size) => {
|
||||
let end = stream.check_read(size)?;
|
||||
unsafe {
|
||||
<Self as #trait_def>::read_unchecked(stream, #extra_param_call end)
|
||||
}
|
||||
},
|
||||
None => {
|
||||
#parsed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_braces)]
|
||||
unsafe fn read_unchecked(stream: &mut ::bitbuffer::BitReadStream<#lifetime, #endianness_ident>#extra_param, end: bool) -> ::bitbuffer::Result<Self> {
|
||||
#parsed_unchecked
|
||||
}
|
||||
|
||||
fn #size_method_name(#size_extra_param) -> Option<usize> {
|
||||
#size
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// panic!("{}", TokenStream::to_string(&expanded));
|
||||
|
||||
proc_macro::TokenStream::from(expanded)
|
||||
derive_trait_inner::<Trait>(input)
|
||||
.unwrap_or_else(|err| err.into_compile_error())
|
||||
.into()
|
||||
}
|
||||
|
||||
fn parse(data: Data, struct_name: &Ident, attrs: &[Attribute], unchecked: bool) -> TokenStream {
|
||||
let span = struct_name.span();
|
||||
|
||||
let align = get_align(attrs);
|
||||
|
||||
match data {
|
||||
Data::Struct(DataStruct { fields, .. }) => {
|
||||
let values = fields.iter().map(|f| {
|
||||
// Get attributes `#[..]` on each field
|
||||
let size = get_field_size(&f.attrs, f.span());
|
||||
let align = get_align(attrs);
|
||||
let field_type = &f.ty;
|
||||
let span = f.span();
|
||||
if unchecked {
|
||||
match size {
|
||||
Some(size) => {
|
||||
quote_spanned! { span =>
|
||||
{
|
||||
#align;
|
||||
let _size: usize = #size;
|
||||
stream.read_sized_unchecked::<#field_type>(_size, end)?
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
quote_spanned! { span =>
|
||||
{
|
||||
#align;
|
||||
stream.read_unchecked::<#field_type>(end)?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match size {
|
||||
Some(size) => {
|
||||
quote_spanned! { span =>
|
||||
{
|
||||
#align;
|
||||
let _size: usize = #size;
|
||||
stream.read_sized::<#field_type>(_size)?
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
quote_spanned! { span =>
|
||||
{
|
||||
#align;
|
||||
stream.read::<#field_type>()?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
match &fields {
|
||||
Fields::Named(fields) => {
|
||||
let definitions = fields.named.iter().zip(values).map(|(f, value)| {
|
||||
let name = &f.ident;
|
||||
quote_spanned! { f.span() =>
|
||||
let #name = #value;
|
||||
}
|
||||
});
|
||||
let struct_definition = fields.named.iter().map(|f| {
|
||||
let name = &f.ident;
|
||||
quote_spanned! { f.span() =>
|
||||
#name,
|
||||
}
|
||||
});
|
||||
quote_spanned! { span =>
|
||||
#align;
|
||||
|
||||
#(#definitions)*
|
||||
|
||||
Ok(#struct_name {
|
||||
#(#struct_definition)*
|
||||
})
|
||||
}
|
||||
}
|
||||
Fields::Unnamed(_) => quote_spanned! { span =>
|
||||
#align;
|
||||
|
||||
Ok(#struct_name(
|
||||
#(#values ,)*
|
||||
))
|
||||
},
|
||||
Fields::Unit => quote_spanned! { span=>
|
||||
#align;
|
||||
|
||||
Ok(#struct_name)
|
||||
},
|
||||
}
|
||||
}
|
||||
Data::Enum(data) => {
|
||||
let discriminant_bits: u64 = match get_attribute_value(attrs, &["discriminant_bits"]) {
|
||||
Some(attr) => attr,
|
||||
None => {
|
||||
return quote_spanned! { span=>
|
||||
compile_error!("'discriminant_bits' attribute is required when deriving `BinRead` for enums");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut last_discriminant = -1;
|
||||
let match_arms = data.variants.iter().map(|variant| {
|
||||
let span = variant.span();
|
||||
let variant_name = &variant.ident;
|
||||
let read_fields = match &variant.fields {
|
||||
Fields::Unit => {
|
||||
if contains_attribute(&variant.attrs, &["align"]) {
|
||||
return quote_spanned! { span =>
|
||||
compile_error!("'align' attribute is not allowed on unit variants");
|
||||
};
|
||||
}
|
||||
quote_spanned! { span=>
|
||||
#struct_name::#variant_name
|
||||
}
|
||||
}
|
||||
Fields::Unnamed(f) => {
|
||||
let size = get_field_size(&variant.attrs, f.span());
|
||||
let align = get_align(&variant.attrs);
|
||||
match size {
|
||||
Some(size) => {
|
||||
quote_spanned! { span =>
|
||||
#struct_name::#variant_name({
|
||||
#align;
|
||||
let _size:usize = #size;
|
||||
stream.read_sized(_size)?
|
||||
})
|
||||
}
|
||||
}
|
||||
None => {
|
||||
quote_spanned! { span =>
|
||||
{
|
||||
#align;
|
||||
#struct_name::#variant_name(stream.read()?)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
|
||||
let discriminant_token: TokenStream = match Discriminant::from(variant) {
|
||||
Discriminant::Int(discriminant) => {
|
||||
let lit = LitInt::new(&format!("{}", discriminant), span);
|
||||
last_discriminant = discriminant as isize;
|
||||
quote_spanned! { span => #lit }
|
||||
}
|
||||
Discriminant::Wildcard => quote_spanned! { span => _ },
|
||||
Discriminant::Default => {
|
||||
let new_discriminant = (last_discriminant + 1) as usize;
|
||||
let lit = LitInt::new(&format!("{}", new_discriminant), span);
|
||||
last_discriminant += 1;
|
||||
quote_spanned! { span => #lit }
|
||||
}
|
||||
};
|
||||
quote_spanned! {span=>
|
||||
#discriminant_token => #read_fields,
|
||||
}
|
||||
});
|
||||
|
||||
let span = data.enum_token.span();
|
||||
|
||||
let repr = repr_for_bits(discriminant_bits);
|
||||
|
||||
let enum_name = Lit::Str(LitStr::new(&struct_name.to_string(), struct_name.span()));
|
||||
quote_spanned! {span=>
|
||||
#align;
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
let discriminant:#repr = stream.read_int(#discriminant_bits as usize)?;
|
||||
Ok(match discriminant {
|
||||
#(#match_arms)*
|
||||
_ => {
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
return Err(::bitbuffer::BitError::UnmatchedDiscriminant{discriminant: discriminant as usize, enum_name: #enum_name.to_string()})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
fn derive_trait_inner<Trait: Derivable>(input: DeriveInput) -> Result<TokenStream> {
|
||||
let params = Trait::Params::parse(&input)?;
|
||||
Trait::derive(params)
|
||||
}
|
||||
|
||||
fn size(data: Data, struct_name: &Ident, attrs: &[Attribute], has_input_size: bool) -> TokenStream {
|
||||
let span = struct_name.span();
|
||||
trait Derivable {
|
||||
type Params: DeriveParams;
|
||||
|
||||
if contains_attribute(attrs, &["align"]) {
|
||||
return quote_spanned! { span =>
|
||||
None
|
||||
};
|
||||
}
|
||||
|
||||
match data {
|
||||
Data::Struct(DataStruct { fields, .. }) => {
|
||||
let sizes = fields.iter().map(|f| {
|
||||
// Get attributes `#[..]` on each field
|
||||
if is_const_size(&f.attrs, has_input_size) {
|
||||
let size = get_field_size(&f.attrs, f.span());
|
||||
let field_type = &f.ty;
|
||||
let span = f.span();
|
||||
match size {
|
||||
Some(size) => {
|
||||
quote_spanned! { span =>
|
||||
<#field_type as ::bitbuffer::BitReadSized<'_, ::bitbuffer::LittleEndian>>::bit_size_sized(#size)
|
||||
}
|
||||
}
|
||||
None => {
|
||||
quote_spanned! { span =>
|
||||
<#field_type as ::bitbuffer::BitRead<'_, ::bitbuffer::LittleEndian>>::bit_size()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote_spanned! { span =>
|
||||
None
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
match &fields {
|
||||
Fields::Named(_) => quote_spanned! { span =>
|
||||
Some(0usize)#(.and_then(|sum: usize| #sizes.map(|size: usize| sum + size)))*
|
||||
},
|
||||
Fields::Unnamed(_) => quote_spanned! { span =>
|
||||
Some(0usize)#(.and_then(|sum: usize| #sizes.map(|size: usize| sum + size)))*
|
||||
},
|
||||
Fields::Unit => quote_spanned! {span=>
|
||||
Some(0usize)
|
||||
},
|
||||
}
|
||||
}
|
||||
Data::Enum(data) => {
|
||||
let discriminant_bits = match get_attribute_value::<u64>(attrs, &["discriminant_bits"])
|
||||
{
|
||||
Some(attr) => attr as usize,
|
||||
None => {
|
||||
return quote! {span=>
|
||||
compile_error!("'discriminant_bits' attribute is required when deriving `BinRead` for enums");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Unit variants having "align" attributes are not allowed, so we can just check if all variants are unit
|
||||
let is_unit = data
|
||||
.variants
|
||||
.iter()
|
||||
.all(|variant| matches!(variant.fields, Fields::Unit));
|
||||
|
||||
if is_unit {
|
||||
quote_spanned! {span=>
|
||||
Some(#discriminant_bits)
|
||||
}
|
||||
} else {
|
||||
quote_spanned! {span=>
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
fn derive(params: Self::Params) -> Result<TokenStream>;
|
||||
}
|
||||
|
||||
fn is_const_size(attrs: &[Attribute], has_input_size: bool) -> bool {
|
||||
if get_attribute_value::<Lit>(attrs, &["size_bits"]).is_some() {
|
||||
return false;
|
||||
}
|
||||
if contains_attribute(attrs, &["align"]) {
|
||||
return false;
|
||||
}
|
||||
get_attribute_value(attrs, &["size"])
|
||||
.map(|size_lit| match size_lit {
|
||||
Lit::Int(_) => true,
|
||||
Lit::Str(size_field) => &size_field.value() == "input_size" && has_input_size,
|
||||
_ => panic!("Unsupported value for size attribute"),
|
||||
})
|
||||
.unwrap_or(true)
|
||||
trait DeriveParams: Sized {
|
||||
fn parse(input: &DeriveInput) -> Result<Self>;
|
||||
}
|
||||
|
||||
fn get_field_size(attrs: &[Attribute], span: Span) -> Option<TokenStream> {
|
||||
get_attribute_value(attrs, &["size"])
|
||||
.map(|size_lit| match size_lit {
|
||||
Lit::Int(size) => {
|
||||
quote_spanned! {span =>
|
||||
#size
|
||||
}
|
||||
}
|
||||
Lit::Str(size_field) => {
|
||||
let size = parse_str::<Expr>(&size_field.value()).unwrap();
|
||||
quote_spanned! {span => {
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
let __size = (#size) as usize;
|
||||
__size
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => panic!("Unsupported value for size attribute"),
|
||||
})
|
||||
.or_else(|| {
|
||||
get_attribute_value::<Lit>(attrs, &["size_bits"]).map(|size_bits_lit| {
|
||||
quote_spanned! {span =>
|
||||
stream.read_int::<usize> (#size_bits_lit) ?
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn repr_for_bits(discriminant_bits: u64) -> TokenStream {
|
||||
if discriminant_bits <= 8 {
|
||||
quote!(u8)
|
||||
} else if discriminant_bits <= 16 {
|
||||
quote!(u16)
|
||||
} else if discriminant_bits <= 32 {
|
||||
quote!(u32)
|
||||
} else {
|
||||
quote!(usize)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_align(attrs: &[Attribute]) -> TokenStream {
|
||||
if contains_attribute(attrs, &["align"]) {
|
||||
quote! {
|
||||
stream.align()?
|
||||
}
|
||||
} else {
|
||||
quote! { () }
|
||||
}
|
||||
fn err<R, Msg: Display>(msg: Msg, span: Span) -> Result<R> {
|
||||
return Err(Error::new(span, msg));
|
||||
}
|
||||
|
|
|
|||
135
bitbuffer_derive/src/params/enum.rs
Normal file
135
bitbuffer_derive/src/params/enum.rs
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
use crate::params::parse_attrs;
|
||||
use crate::params::variant::VariantParam;
|
||||
use merge::Merge;
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::quote;
|
||||
use structmeta::StructMeta;
|
||||
use syn::{Attribute, DataEnum, Error, LitInt, Result};
|
||||
|
||||
#[derive(Default, StructMeta, Merge, Debug)]
|
||||
struct EnumAttrs {
|
||||
discriminant_bits: Option<LitInt>,
|
||||
}
|
||||
|
||||
pub struct EnumParam {
|
||||
pub span: Span,
|
||||
pub ident: Ident,
|
||||
pub variants: Vec<VariantParam>,
|
||||
pub discriminant_bits: usize,
|
||||
}
|
||||
|
||||
impl EnumParam {
|
||||
pub fn size_can_be_predicted(&self) -> bool {
|
||||
self.variants
|
||||
.iter()
|
||||
.all(|field| field.size_can_be_predicted())
|
||||
}
|
||||
|
||||
pub fn parse(
|
||||
data: &DataEnum,
|
||||
ident: Ident,
|
||||
attrs: &[Attribute],
|
||||
span: Span,
|
||||
) -> Result<EnumParam> {
|
||||
let attrs: EnumAttrs = parse_attrs(attrs)?;
|
||||
let variants = data
|
||||
.variants
|
||||
.iter()
|
||||
.map(VariantParam::parse)
|
||||
.collect::<Result<Vec<VariantParam>>>()?;
|
||||
let discriminant_bits = attrs
|
||||
.discriminant_bits
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
span.clone(),
|
||||
"'discriminant_bits' attribute is required when deriving `BinRead` for enums",
|
||||
)
|
||||
})?
|
||||
.base10_parse()?;
|
||||
|
||||
Ok(EnumParam {
|
||||
span,
|
||||
ident,
|
||||
variants,
|
||||
discriminant_bits,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
|
||||
pub fn read_discriminant_tokens(&self) -> impl Iterator<Item = TokenStream> + '_ {
|
||||
ReadDiscriminantTokenIter {
|
||||
last: -1,
|
||||
variants: self.variants.iter(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_discriminant_tokens(&self) -> impl Iterator<Item = TokenStream> + '_ {
|
||||
WriteDiscriminantTokenIter {
|
||||
last: -1,
|
||||
max: self.max_discriminant(),
|
||||
variants: self.variants.iter(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max_discriminant(&self) -> usize {
|
||||
let mut last_discriminant = -1;
|
||||
|
||||
self.variants
|
||||
.iter()
|
||||
.map(|variant| variant.discriminant.max_value(&mut last_discriminant))
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
pub fn discriminant_repr(&self) -> TokenStream {
|
||||
if self.discriminant_bits <= 8 {
|
||||
quote!(u8)
|
||||
} else if self.discriminant_bits <= 16 {
|
||||
quote!(u16)
|
||||
} else if self.discriminant_bits <= 32 {
|
||||
quote!(u32)
|
||||
} else {
|
||||
quote!(u64)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReadDiscriminantTokenIter<'a> {
|
||||
last: isize,
|
||||
variants: std::slice::Iter<'a, VariantParam>,
|
||||
}
|
||||
|
||||
impl Iterator for ReadDiscriminantTokenIter<'_> {
|
||||
type Item = TokenStream;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let variant = self.variants.next()?;
|
||||
Some(
|
||||
variant
|
||||
.discriminant
|
||||
.read_token(&mut self.last, variant.span()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WriteDiscriminantTokenIter<'a> {
|
||||
last: isize,
|
||||
max: usize,
|
||||
variants: std::slice::Iter<'a, VariantParam>,
|
||||
}
|
||||
|
||||
impl Iterator for WriteDiscriminantTokenIter<'_> {
|
||||
type Item = TokenStream;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let variant = self.variants.next()?;
|
||||
Some(
|
||||
variant
|
||||
.discriminant
|
||||
.write_token(&mut self.last, self.max, variant.span()),
|
||||
)
|
||||
}
|
||||
}
|
||||
81
bitbuffer_derive/src/params/field.rs
Normal file
81
bitbuffer_derive/src/params/field.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
use crate::params::{parse_attrs, Alignment, Size};
|
||||
use merge::Merge;
|
||||
use proc_macro2::{Ident, Span};
|
||||
use structmeta::StructMeta;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{Expr, Field, Index, LitInt, Member, Result, Type};
|
||||
|
||||
#[derive(Default, StructMeta, Merge)]
|
||||
struct FieldAttrs {
|
||||
size: Option<Expr>,
|
||||
size_bits: Option<LitInt>,
|
||||
#[merge(strategy = merge::bool::overwrite_false)]
|
||||
align: bool,
|
||||
}
|
||||
|
||||
pub struct FieldParam {
|
||||
pub span: Span,
|
||||
pub field_name: Option<Ident>,
|
||||
pub size: Option<Size>,
|
||||
pub align: Alignment,
|
||||
pub ty: Type,
|
||||
}
|
||||
|
||||
impl FieldParam {
|
||||
/// Whether the size of the field can be determined without having to read further bits
|
||||
pub fn size_can_be_predicted(&self) -> bool {
|
||||
if self.align == Alignment::Auto {
|
||||
return false;
|
||||
}
|
||||
match &self.size {
|
||||
Some(size) => size.is_const(),
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(input: &Field) -> Result<FieldParam> {
|
||||
let attrs: FieldAttrs = parse_attrs(&input.attrs)?;
|
||||
let field_name = input.ident.clone();
|
||||
let align = attrs.align.into();
|
||||
let size = Size::from_attrs(attrs.size, attrs.size_bits, input.span())?;
|
||||
let ty = input.ty.clone();
|
||||
|
||||
Ok(FieldParam {
|
||||
span: input.span(),
|
||||
field_name,
|
||||
size,
|
||||
align,
|
||||
ty,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
|
||||
pub fn member(&self, index: u32) -> Member {
|
||||
match self.field_name.as_ref() {
|
||||
Some(name) => Member::Named(name.clone()),
|
||||
None => Member::Unnamed(Index {
|
||||
index,
|
||||
span: self.span(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_int(&self) -> bool {
|
||||
if let Type::Path(path) = &self.ty {
|
||||
if let Some(ident) = path.path.get_ident() {
|
||||
let name = ident.to_string();
|
||||
matches!(
|
||||
name.as_str(),
|
||||
"u8" | "u16" | "u32" | "u64" | "usize" | "i8" | "i16" | "i32" | "i64" | "isize"
|
||||
)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
305
bitbuffer_derive/src/params/mod.rs
Normal file
305
bitbuffer_derive/src/params/mod.rs
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
mod r#enum;
|
||||
mod field;
|
||||
mod r#struct;
|
||||
mod variant;
|
||||
|
||||
pub use crate::params::field::FieldParam;
|
||||
pub use crate::params::r#enum::EnumParam;
|
||||
pub use crate::params::r#struct::StructParam;
|
||||
pub use crate::params::variant::{VariantBody, VariantBodyType, VariantParam};
|
||||
use crate::{err, DeriveParams};
|
||||
use merge::Merge;
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::{quote, quote_spanned, ToTokens, TokenStreamExt};
|
||||
use std::any::type_name;
|
||||
use std::fmt::Debug;
|
||||
use structmeta::StructMeta;
|
||||
use syn::__private::{bool, IntoSpans};
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::spanned::Spanned;
|
||||
use syn::token::Paren;
|
||||
use syn::{
|
||||
parse_quote, parse_str, Attribute, Data, DeriveInput, Expr, ExprLit, ExprPath, GenericParam,
|
||||
Generics, ImplGenerics, Lifetime, Lit, LitBool, LitInt, LitStr, MacroDelimiter, Meta, MetaList,
|
||||
Result, TypeGenerics, WhereClause,
|
||||
};
|
||||
|
||||
pub enum Size {
|
||||
Expression(Expr, Span),
|
||||
Bits(usize, Span),
|
||||
}
|
||||
|
||||
impl Size {
|
||||
pub fn is_const(&self) -> bool {
|
||||
match self {
|
||||
Size::Expression(
|
||||
Expr::Lit(ExprLit {
|
||||
lit: Lit::Int(_), ..
|
||||
}),
|
||||
_,
|
||||
) => true,
|
||||
Size::Expression(Expr::Path(ExprPath { path, .. }), _) => path.is_ident("input_size"),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_attrs(
|
||||
size: Option<Expr>,
|
||||
size_bits: Option<LitInt>,
|
||||
span: Span,
|
||||
) -> Result<Option<Self>> {
|
||||
Ok(match (size, size_bits) {
|
||||
(
|
||||
Some(Expr::Lit(ExprLit {
|
||||
lit: Lit::Str(field),
|
||||
..
|
||||
})),
|
||||
None,
|
||||
) => Some(Size::Expression(parse_str(&field.value())?, span)),
|
||||
(Some(size), None) => Some(Size::Expression(size, span)),
|
||||
(None, Some(bits)) => Some(Size::Bits(bits.base10_parse()?, span)),
|
||||
(Some(_), Some(_)) => err("#[size] and #[size_bits] are mutually exclusive", span)?,
|
||||
(None, None) => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Size {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
match self {
|
||||
Size::Expression(expr, span) => {
|
||||
let span = span.clone();
|
||||
tokens.append_all(quote_spanned! {span => {
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
let __size = (#expr) as usize;
|
||||
__size
|
||||
}
|
||||
});
|
||||
}
|
||||
Size::Bits(bits, span) => {
|
||||
let span = span.clone();
|
||||
tokens.append_all(quote_spanned! {span => {
|
||||
__stream.read_int::<usize>(#bits)?
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialOrd, PartialEq, Copy, Clone, Debug)]
|
||||
pub enum Alignment {
|
||||
#[default]
|
||||
None,
|
||||
Auto,
|
||||
}
|
||||
|
||||
impl Alignment {
|
||||
pub fn write(&self) -> TokenStream {
|
||||
match self {
|
||||
Alignment::Auto => quote! {
|
||||
__stream.align();
|
||||
},
|
||||
Alignment::None => quote!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Alignment {
|
||||
fn from(value: bool) -> Self {
|
||||
match value {
|
||||
true => Alignment::Auto,
|
||||
false => Alignment::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for Alignment {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
Ok(LitBool::parse(input)?.value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Merge for Alignment {
|
||||
fn merge(&mut self, other: Self) {
|
||||
if other == Alignment::Auto {
|
||||
*self = Alignment::Auto
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Alignment {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
match self {
|
||||
Alignment::Auto => tokens.append_all(quote! {
|
||||
__stream.align()?;
|
||||
}),
|
||||
Alignment::None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, StructMeta, Merge, Debug)]
|
||||
struct InputAttrs {
|
||||
endianness: Option<LitStr>,
|
||||
#[merge(strategy = merge::bool::overwrite_false)]
|
||||
align: bool,
|
||||
}
|
||||
|
||||
pub struct InputParams {
|
||||
pub ident: Ident,
|
||||
pub span: Span,
|
||||
endianness: Option<String>,
|
||||
pub align: Alignment,
|
||||
pub generics: Generics,
|
||||
pub generics_with_endianness: Generics,
|
||||
pub inner: InputInnerParams,
|
||||
pub lifetime: Lifetime,
|
||||
}
|
||||
|
||||
pub enum InputInnerParams {
|
||||
Struct(StructParam),
|
||||
Enum(EnumParam),
|
||||
}
|
||||
|
||||
impl DeriveParams for InputParams {
|
||||
fn parse(input: &DeriveInput) -> Result<Self> {
|
||||
let attrs: InputAttrs = parse_attrs(&input.attrs)?;
|
||||
let inner = match &input.data {
|
||||
Data::Struct(data) => InputInnerParams::Struct(StructParam::parse(
|
||||
data,
|
||||
input.ident.clone(),
|
||||
&input.attrs,
|
||||
input.span(),
|
||||
)?),
|
||||
Data::Enum(data) => InputInnerParams::Enum(EnumParam::parse(
|
||||
data,
|
||||
input.ident.clone(),
|
||||
&input.attrs,
|
||||
input.span(),
|
||||
)?),
|
||||
_ => return err("Only structs and enums are supported", input.span()),
|
||||
};
|
||||
|
||||
let endianness = attrs.endianness.map(|lit| lit.value());
|
||||
let align = attrs.align.into();
|
||||
|
||||
let generics = input.generics.clone();
|
||||
let mut generics_with_endianness = generics.clone();
|
||||
let mut lifetimes = input
|
||||
.generics
|
||||
.params
|
||||
.iter()
|
||||
.filter_map(|param| match param {
|
||||
GenericParam::Lifetime(lifetime) => Some(lifetime),
|
||||
_ => None,
|
||||
});
|
||||
let lifetime = match (lifetimes.next(), lifetimes.next()) {
|
||||
(_, Some(_)) => {
|
||||
return err("Only a single lifetime generic is supported", input.span())
|
||||
}
|
||||
(Some(param), None) => param.lifetime.clone(),
|
||||
(None, None) => {
|
||||
let lifetime = Lifetime::new("'a", input.span());
|
||||
generics_with_endianness
|
||||
.params
|
||||
.push(GenericParam::Lifetime(parse_str("'a").unwrap()));
|
||||
lifetime
|
||||
}
|
||||
};
|
||||
|
||||
if endianness.is_none() {
|
||||
generics_with_endianness
|
||||
.params
|
||||
.push(parse_quote!(_E: ::bitbuffer::Endianness));
|
||||
}
|
||||
|
||||
Ok(InputParams {
|
||||
ident: input.ident.clone(),
|
||||
span: input.span(),
|
||||
endianness,
|
||||
align,
|
||||
generics,
|
||||
generics_with_endianness,
|
||||
lifetime,
|
||||
inner,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl InputParams {
|
||||
#[allow(dead_code)]
|
||||
pub fn size_can_be_predicted(&self) -> bool {
|
||||
match &self.inner {
|
||||
InputInnerParams::Struct(inner) => inner.size_can_be_predicted(),
|
||||
InputInnerParams::Enum(inner) => inner.size_can_be_predicted(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generics_for_impl<'a>(
|
||||
&'a self,
|
||||
) -> (ImplGenerics<'a>, TypeGenerics<'a>, Option<&'a WhereClause>) {
|
||||
// we need these separate generics to only add out Endianness param to the 'impl'
|
||||
let (_, ty_generics, where_clause) = self.generics.split_for_impl();
|
||||
let (impl_generics, _, _) = self.generics_with_endianness.split_for_impl();
|
||||
|
||||
(impl_generics, ty_generics, where_clause)
|
||||
}
|
||||
|
||||
pub fn endianness(&self) -> Ident {
|
||||
Ident::new(
|
||||
self.endianness.as_deref().unwrap_or_else(|| "_E"),
|
||||
self.span,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const BARE_ATTRS: &'static [&'static str] = &[
|
||||
"size",
|
||||
"size_bits",
|
||||
"discriminant_bits",
|
||||
"discriminant",
|
||||
"endianness",
|
||||
"align",
|
||||
];
|
||||
|
||||
fn parse_attrs<T: Parse + Default + Merge>(attrs: &[Attribute]) -> Result<T> {
|
||||
let mut result = T::default();
|
||||
for attr in attrs {
|
||||
let parsed = if BARE_ATTRS
|
||||
.iter()
|
||||
.any(|name| attr.meta.path().is_ident(name))
|
||||
{
|
||||
let wrapped_meta = Meta::List(MetaList {
|
||||
path: parse_str("bitbuffer").unwrap(),
|
||||
delimiter: MacroDelimiter::Paren(Paren {
|
||||
span: attr.span().into_spans(),
|
||||
}),
|
||||
tokens: attr.meta.clone().into_token_stream(),
|
||||
});
|
||||
let wrapped = Attribute {
|
||||
pound_token: attr.pound_token,
|
||||
style: attr.style.clone(),
|
||||
bracket_token: attr.bracket_token.clone(),
|
||||
meta: wrapped_meta,
|
||||
};
|
||||
wrapped.parse_args()
|
||||
} else {
|
||||
attr.parse_args()
|
||||
};
|
||||
match parsed {
|
||||
Ok(parsed) => {
|
||||
result.merge(parsed);
|
||||
}
|
||||
Err(e) => {
|
||||
// since we first parse our attrs as InputAttrs, and then the same attrs as either an Struct or EnumAttrs
|
||||
// when doing the first pass we expect a bunch of extra parameters
|
||||
let is_first_pass = type_name::<T>() == type_name::<InputAttrs>();
|
||||
if !e.to_string().starts_with("cannot find parameter") && !is_first_pass {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
44
bitbuffer_derive/src/params/struct.rs
Normal file
44
bitbuffer_derive/src/params/struct.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
use crate::params::field::FieldParam;
|
||||
use proc_macro2::{Ident, Span};
|
||||
use syn::{Attribute, DataStruct, Fields, Result};
|
||||
|
||||
pub struct StructParam {
|
||||
pub span: Span,
|
||||
pub ident: Ident,
|
||||
pub fields: Vec<FieldParam>,
|
||||
pub is_unit: bool,
|
||||
}
|
||||
|
||||
impl StructParam {
|
||||
pub fn size_can_be_predicted(&self) -> bool {
|
||||
self.fields
|
||||
.iter()
|
||||
.all(|field| field.size_can_be_predicted())
|
||||
}
|
||||
|
||||
pub fn parse(
|
||||
data: &DataStruct,
|
||||
ident: Ident,
|
||||
_attrs: &[Attribute],
|
||||
span: Span,
|
||||
) -> Result<StructParam> {
|
||||
let fields = data
|
||||
.fields
|
||||
.iter()
|
||||
.map(FieldParam::parse)
|
||||
.collect::<Result<Vec<FieldParam>>>()?;
|
||||
|
||||
let is_unit = matches!(data.fields, Fields::Unit);
|
||||
|
||||
Ok(StructParam {
|
||||
span,
|
||||
ident,
|
||||
fields,
|
||||
is_unit,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
143
bitbuffer_derive/src/params/variant.rs
Normal file
143
bitbuffer_derive/src/params/variant.rs
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
use crate::discriminant::Discriminant;
|
||||
use crate::err;
|
||||
use crate::params::field::FieldParam;
|
||||
use crate::params::{parse_attrs, Alignment, Size};
|
||||
use merge::Merge;
|
||||
use proc_macro2::{Ident, Span};
|
||||
use std::convert::TryFrom;
|
||||
use structmeta::StructMeta;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{Expr, ExprLit, Fields, Lit, LitInt, Result, Variant};
|
||||
|
||||
#[derive(Default, StructMeta, Merge)]
|
||||
struct VariantAttrs {
|
||||
size: Option<Expr>,
|
||||
size_bits: Option<LitInt>,
|
||||
#[merge(strategy = merge::bool::overwrite_false)]
|
||||
align: bool,
|
||||
discriminant: Option<Lit>,
|
||||
}
|
||||
|
||||
pub struct VariantParam {
|
||||
pub span: Span,
|
||||
pub variant_name: Ident,
|
||||
pub body: VariantBody,
|
||||
pub discriminant: Discriminant,
|
||||
}
|
||||
|
||||
pub enum VariantBodyType {
|
||||
Unit,
|
||||
Unnamed,
|
||||
Named,
|
||||
}
|
||||
|
||||
pub enum VariantBody {
|
||||
Unit,
|
||||
Fields(Vec<FieldParam>),
|
||||
}
|
||||
|
||||
impl VariantBody {
|
||||
pub fn body_type(&self) -> VariantBodyType {
|
||||
match self {
|
||||
VariantBody::Unit => VariantBodyType::Unit,
|
||||
VariantBody::Fields(fields) => {
|
||||
let named = fields.iter().any(|f| f.field_name.is_some());
|
||||
if named {
|
||||
VariantBodyType::Named
|
||||
} else {
|
||||
VariantBodyType::Unnamed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VariantParam {
|
||||
/// Whether the size of the variant can be determined without having to read further bits
|
||||
pub fn size_can_be_predicted(&self) -> bool {
|
||||
match &self.body {
|
||||
VariantBody::Fields(fields) => fields.iter().all(|field| field.size_can_be_predicted()),
|
||||
VariantBody::Unit => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(input: &Variant) -> Result<VariantParam> {
|
||||
let attrs: VariantAttrs = parse_attrs(&input.attrs)?;
|
||||
let variant_name = input.ident.clone();
|
||||
let align = attrs.align.into();
|
||||
let size = Size::from_attrs(attrs.size, attrs.size_bits, input.span())?;
|
||||
|
||||
if attrs.discriminant.is_some() && input.discriminant.is_some() {
|
||||
err(
|
||||
"variant has both discriminant and discriminant attribute set",
|
||||
input.span(),
|
||||
)?;
|
||||
}
|
||||
|
||||
let discriminant = attrs
|
||||
.discriminant
|
||||
.map(|lit| {
|
||||
Expr::Lit(ExprLit {
|
||||
attrs: Vec::new(),
|
||||
lit,
|
||||
})
|
||||
})
|
||||
.or_else(|| {
|
||||
input
|
||||
.discriminant
|
||||
.clone()
|
||||
.map(|(_, discriminant)| discriminant)
|
||||
})
|
||||
.map(Discriminant::try_from)
|
||||
.transpose()?
|
||||
.unwrap_or(Discriminant::Default);
|
||||
|
||||
let body = if matches!(input.fields, Fields::Unit) {
|
||||
if align == Alignment::Auto {
|
||||
err(
|
||||
"'align' attribute is not allowed on unit variants",
|
||||
input.span(),
|
||||
)?;
|
||||
}
|
||||
if size.is_some() {
|
||||
err(
|
||||
"'size' attribute is not allowed on unit variants",
|
||||
input.span(),
|
||||
)?;
|
||||
}
|
||||
VariantBody::Unit
|
||||
} else {
|
||||
let mut fields = input
|
||||
.fields
|
||||
.iter()
|
||||
.map(FieldParam::parse)
|
||||
.collect::<Result<Vec<FieldParam>>>()?;
|
||||
|
||||
// align and size attributes on the variant go to the first field
|
||||
match (fields.first_mut(), align) {
|
||||
(Some(field), Alignment::Auto) => {
|
||||
field.align = align;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
match (fields.first_mut(), size) {
|
||||
(Some(field), Some(size)) => {
|
||||
field.size = Some(size);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
VariantBody::Fields(fields)
|
||||
};
|
||||
|
||||
Ok(VariantParam {
|
||||
span: input.span(),
|
||||
variant_name,
|
||||
discriminant,
|
||||
body,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
}
|
||||
70
bitbuffer_derive/src/read/enum.rs
Normal file
70
bitbuffer_derive/src/read/enum.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
use crate::params::{EnumParam, VariantBody};
|
||||
use crate::read::field::read_struct_or_enum;
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use quote::quote_spanned;
|
||||
use syn::Path;
|
||||
|
||||
pub fn derive_encode_enum(params: &EnumParam, unchecked: bool) -> TokenStream {
|
||||
let discriminant_bits = params.discriminant_bits;
|
||||
let repr = params.discriminant_repr();
|
||||
let ident = params.ident.clone();
|
||||
let span = params.span;
|
||||
|
||||
let match_arms = params
|
||||
.variants
|
||||
.iter()
|
||||
.zip(params.read_discriminant_tokens())
|
||||
.map(|(variant, discriminant_token)| {
|
||||
let span = variant.span();
|
||||
let variant_name = &variant.variant_name;
|
||||
let mut variant_path = Path::from(params.ident.clone());
|
||||
variant_path
|
||||
.segments
|
||||
.push(variant.variant_name.clone().into());
|
||||
let read_variant = match &variant.body {
|
||||
VariantBody::Unit => quote_spanned! { span =>
|
||||
Ok(#ident::#variant_name)
|
||||
},
|
||||
VariantBody::Fields(fields) => {
|
||||
read_struct_or_enum(&variant_path, fields, span.clone(), unchecked)
|
||||
}
|
||||
};
|
||||
|
||||
quote_spanned! {span=>
|
||||
#discriminant_token => #read_variant,
|
||||
}
|
||||
});
|
||||
|
||||
let read_fn = Ident::new(
|
||||
if unchecked {
|
||||
"read_int_unchecked"
|
||||
} else {
|
||||
"read_int"
|
||||
},
|
||||
span,
|
||||
);
|
||||
let end_param = if unchecked {
|
||||
Some(quote_spanned!(span => end))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let error_handle = if unchecked {
|
||||
None
|
||||
} else {
|
||||
Some(quote_spanned!(span => ?))
|
||||
};
|
||||
|
||||
let name = ident.to_string();
|
||||
|
||||
quote_spanned! {span =>
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
let discriminant:#repr = __stream.#read_fn(#discriminant_bits as usize, #end_param)#error_handle;
|
||||
match discriminant {
|
||||
#(#match_arms)*
|
||||
_ => {
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
return Err(::bitbuffer::BitError::UnmatchedDiscriminant{discriminant: discriminant as usize, enum_name: #name.to_string()})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
82
bitbuffer_derive/src/read/field.rs
Normal file
82
bitbuffer_derive/src/read/field.rs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
use crate::params::FieldParam;
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::quote_spanned;
|
||||
use syn::Path;
|
||||
|
||||
pub fn read_struct_or_enum(
|
||||
struct_name: &Path,
|
||||
fields: &[FieldParam],
|
||||
span: Span,
|
||||
unchecked: bool,
|
||||
) -> TokenStream {
|
||||
let named = fields.iter().any(|f| f.field_name.is_some());
|
||||
let values = fields.iter().map(|f| {
|
||||
let align = &f.align;
|
||||
let field_type = &f.ty;
|
||||
let span = f.span();
|
||||
let read_fn = Ident::new(if unchecked { "read_unchecked" } else { "read" }, span);
|
||||
let read_sized_fn = Ident::new(
|
||||
if unchecked {
|
||||
"read_sized_unchecked"
|
||||
} else {
|
||||
"read_sized"
|
||||
},
|
||||
span,
|
||||
);
|
||||
let end_param = if unchecked {
|
||||
Some(quote_spanned!(span => end))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
match &f.size {
|
||||
Some(size) => {
|
||||
quote_spanned! { span =>
|
||||
{
|
||||
#align
|
||||
let _size: usize = #size;
|
||||
__stream.#read_sized_fn::<#field_type>(_size, #end_param)?
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
quote_spanned! { span =>
|
||||
{
|
||||
#align
|
||||
__stream.#read_fn::<#field_type>(#end_param)?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if named {
|
||||
let definitions = fields.iter().zip(values).map(|(f, value)| {
|
||||
let name = &f.field_name;
|
||||
quote_spanned! { span =>
|
||||
let #name = #value;
|
||||
}
|
||||
});
|
||||
let struct_definition = fields.iter().map(|f| {
|
||||
let name = f
|
||||
.field_name
|
||||
.as_ref()
|
||||
.expect("unnamed field in named struct?");
|
||||
quote_spanned! { span =>
|
||||
#name,
|
||||
}
|
||||
});
|
||||
quote_spanned! { span =>
|
||||
#(#definitions)*
|
||||
|
||||
Ok(#struct_name {
|
||||
#(#struct_definition)*
|
||||
})
|
||||
}
|
||||
} else {
|
||||
quote_spanned! { span =>
|
||||
Ok(#struct_name(
|
||||
#(#values ,)*
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
118
bitbuffer_derive/src/read/mod.rs
Normal file
118
bitbuffer_derive/src/read/mod.rs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
mod r#enum;
|
||||
mod field;
|
||||
mod r#struct;
|
||||
|
||||
use self::r#enum::derive_encode_enum;
|
||||
use self::r#struct::derive_encode_struct;
|
||||
use crate::params::{InputInnerParams, InputParams};
|
||||
use crate::size_hint::SizeHint;
|
||||
use crate::Derivable;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::Result;
|
||||
|
||||
fn parse_impl(params: &InputParams, unchecked: bool) -> Result<TokenStream> {
|
||||
Ok(match ¶ms.inner {
|
||||
InputInnerParams::Struct(inner) => derive_encode_struct(inner, unchecked),
|
||||
InputInnerParams::Enum(inner) => derive_encode_enum(inner, unchecked),
|
||||
})
|
||||
}
|
||||
|
||||
pub struct Read;
|
||||
|
||||
impl Derivable for Read {
|
||||
type Params = InputParams;
|
||||
|
||||
fn derive(params: Self::Params) -> Result<TokenStream> {
|
||||
let (impl_generics, ty_generics, where_clause) = params.generics_for_impl();
|
||||
|
||||
let parse = parse_impl(¶ms, false)?;
|
||||
let parse_unchecked = parse_impl(¶ms, true)?;
|
||||
let size = params.size_hint();
|
||||
let lifetime = params.lifetime.clone();
|
||||
let endianness = params.endianness();
|
||||
let name = params.ident.clone();
|
||||
let align = params.align;
|
||||
let span = params.span;
|
||||
|
||||
Ok(quote_spanned! {span =>
|
||||
impl #impl_generics ::bitbuffer::BitRead<#lifetime, #endianness> for #name #ty_generics #where_clause {
|
||||
#[allow(unused_braces, unused_variables)]
|
||||
fn read(__stream: &mut ::bitbuffer::BitReadStream<#lifetime, #endianness>) -> ::bitbuffer::Result<Self> {
|
||||
// if the read has a predicable size, we can do the bounds check in one go
|
||||
match <Self as ::bitbuffer::BitRead<#endianness>>::bit_size() {
|
||||
Some(size) => {
|
||||
let end = __stream.check_read(size)?;
|
||||
unsafe {
|
||||
<Self as ::bitbuffer::BitRead<#endianness>>::read_unchecked(__stream, end)
|
||||
}
|
||||
},
|
||||
None => {
|
||||
#align
|
||||
#parse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_braces, unused_variables)]
|
||||
unsafe fn read_unchecked(__stream: &mut ::bitbuffer::BitReadStream<#lifetime, #endianness>, end: bool) -> ::bitbuffer::Result<Self> {
|
||||
#align
|
||||
#parse_unchecked
|
||||
}
|
||||
|
||||
fn bit_size() -> Option<usize> {
|
||||
#size
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReadSized;
|
||||
|
||||
impl Derivable for ReadSized {
|
||||
type Params = InputParams;
|
||||
|
||||
fn derive(params: Self::Params) -> Result<TokenStream> {
|
||||
let (impl_generics, ty_generics, where_clause) = params.generics_for_impl();
|
||||
|
||||
let parse = parse_impl(¶ms, false)?;
|
||||
let parse_unchecked = parse_impl(¶ms, true)?;
|
||||
let size = params.size_hint();
|
||||
let lifetime = params.lifetime.clone();
|
||||
let endianness = params.endianness();
|
||||
let name = params.ident.clone();
|
||||
let align = params.align;
|
||||
|
||||
Ok(quote! {
|
||||
impl #impl_generics ::bitbuffer::BitReadSized<#lifetime, #endianness> for #name #ty_generics #where_clause {
|
||||
#[allow(unused_braces)]
|
||||
fn read(__stream: &mut ::bitbuffer::BitReadStream<#lifetime, #endianness>, input_size: usize) -> ::bitbuffer::Result<Self> {
|
||||
// if the read has a predicable size, we can do the bounds check in one go
|
||||
match <Self as ::bitbuffer::BitReadSized<#endianness>>::bit_size_sized(input_size) {
|
||||
Some(size) => {
|
||||
let end = __stream.check_read(size)?;
|
||||
unsafe {
|
||||
<Self as ::bitbuffer::BitReadSized<#endianness>>::read_unchecked(__stream, input_size, end)
|
||||
}
|
||||
},
|
||||
None => {
|
||||
#align
|
||||
#parse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_braces)]
|
||||
unsafe fn read_unchecked(__stream: &mut ::bitbuffer::BitReadStream<#lifetime, #endianness>, input_size: usize, end: bool) -> ::bitbuffer::Result<Self> {
|
||||
#align
|
||||
#parse_unchecked
|
||||
}
|
||||
|
||||
fn bit_size_sized(input_size: usize) -> Option<usize> {
|
||||
#size
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
14
bitbuffer_derive/src/read/struct.rs
Normal file
14
bitbuffer_derive/src/read/struct.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
use crate::params::StructParam;
|
||||
use crate::read::field::read_struct_or_enum;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::Path;
|
||||
|
||||
pub fn derive_encode_struct(params: &StructParam, unchecked: bool) -> TokenStream {
|
||||
let path = Path::from(params.ident.clone());
|
||||
if params.is_unit {
|
||||
quote!(Ok(#path))
|
||||
} else {
|
||||
read_struct_or_enum(&path, ¶ms.fields, params.span(), unchecked)
|
||||
}
|
||||
}
|
||||
88
bitbuffer_derive/src/size_hint.rs
Normal file
88
bitbuffer_derive/src/size_hint.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
use crate::params::{
|
||||
Alignment, EnumParam, FieldParam, InputInnerParams, InputParams, StructParam, VariantBody,
|
||||
VariantParam,
|
||||
};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, quote_spanned};
|
||||
|
||||
pub trait SizeHint {
|
||||
fn size_hint(&self) -> TokenStream;
|
||||
}
|
||||
|
||||
pub trait SizeHintSized {
|
||||
fn hint(&self) -> TokenStream;
|
||||
}
|
||||
|
||||
impl SizeHint for FieldParam {
|
||||
fn size_hint(&self) -> TokenStream {
|
||||
let span = self.span;
|
||||
let field_type = &self.ty;
|
||||
if !self.size_can_be_predicted() {
|
||||
return quote_spanned! { span => None::<usize>};
|
||||
}
|
||||
match &self.size {
|
||||
Some(size) => {
|
||||
quote_spanned! { span =>
|
||||
<#field_type as ::bitbuffer::BitReadSized<'_, ::bitbuffer::LittleEndian>>::bit_size_sized(#size)
|
||||
}
|
||||
}
|
||||
None => quote_spanned! { span =>
|
||||
<#field_type as ::bitbuffer::BitRead<'_, ::bitbuffer::LittleEndian>>::bit_size()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SizeHint for VariantParam {
|
||||
fn size_hint(&self) -> TokenStream {
|
||||
match &self.body {
|
||||
VariantBody::Unit => quote!(Some(0)),
|
||||
VariantBody::Fields(fields) => product_size_hint(&fields, self.span),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SizeHint for StructParam {
|
||||
fn size_hint(&self) -> TokenStream {
|
||||
product_size_hint(&self.fields, self.span)
|
||||
}
|
||||
}
|
||||
|
||||
impl SizeHint for EnumParam {
|
||||
fn size_hint(&self) -> TokenStream {
|
||||
let fields = sum_size_hint(&self.variants, self.span);
|
||||
let bits = self.discriminant_bits;
|
||||
quote_spanned!(self.span => {
|
||||
Some(#bits + #fields?)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl SizeHint for InputParams {
|
||||
fn size_hint(&self) -> TokenStream {
|
||||
match (self.align, &self.inner) {
|
||||
(Alignment::Auto, _) => quote!(None),
|
||||
(_, InputInnerParams::Struct(inner)) => inner.size_hint(),
|
||||
(_, InputInnerParams::Enum(inner)) => inner.size_hint(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn product_size_hint<T: SizeHint>(children: &[T], span: Span) -> TokenStream {
|
||||
let sizes = children.iter().map(|child| child.size_hint());
|
||||
quote_spanned!(span => Some(0usize)#(.and_then(|sum: usize| Some(sum + #sizes?)))*)
|
||||
}
|
||||
|
||||
// sum types have a fixed size if all children have the same fixed size
|
||||
fn sum_size_hint<T: SizeHint>(children: &[T], span: Span) -> TokenStream {
|
||||
// todo, some actual clever logic that can be const folded away
|
||||
let mut sizes = children.iter().map(|child| child.size_hint());
|
||||
let Some(first) = sizes.next() else {
|
||||
return quote!(Some(0));
|
||||
};
|
||||
quote_spanned!(span => #first#(.and_then(|prev: usize| if prev == #sizes? {
|
||||
Some(prev)
|
||||
} else {
|
||||
None
|
||||
}))*)
|
||||
}
|
||||
|
|
@ -1,338 +0,0 @@
|
|||
use crate::discriminant::Discriminant;
|
||||
use crate::{repr_for_bits, size};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{
|
||||
parse_macro_input, parse_quote, parse_str, Attribute, Data, DataStruct, DeriveInput, Expr,
|
||||
Fields, GenericParam, Ident, Index, Lit, LitInt, Member, Path, Type,
|
||||
};
|
||||
use syn_util::{contains_attribute, get_attribute_value};
|
||||
|
||||
pub fn derive_bitwrite_trait(
|
||||
input: proc_macro::TokenStream,
|
||||
trait_name: String,
|
||||
write_method_name: String,
|
||||
extra_param: Option<TokenStream>,
|
||||
) -> proc_macro::TokenStream {
|
||||
let input: DeriveInput = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
let name = &input.ident;
|
||||
|
||||
let endianness = get_attribute_value(&input.attrs, &["endianness"]);
|
||||
let mut trait_generics = input.generics.clone();
|
||||
// we need these separate generics to only add out Endianness param to the 'impl'
|
||||
let (_, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
let lifetime: Option<&GenericParam> = trait_generics
|
||||
.params
|
||||
.iter()
|
||||
.find(|param| matches!(param, GenericParam::Lifetime(_)));
|
||||
let _lifetime = match lifetime {
|
||||
Some(GenericParam::Lifetime(lifetime)) => lifetime.lifetime.clone(),
|
||||
_ => {
|
||||
// trait_generics.params.push(parse_quote!('a));
|
||||
parse_quote!('a)
|
||||
}
|
||||
};
|
||||
if endianness.is_none() {
|
||||
trait_generics
|
||||
.params
|
||||
.push(parse_quote!(_E: ::bitbuffer::Endianness));
|
||||
}
|
||||
let (impl_generics, _, _) = trait_generics.split_for_impl();
|
||||
let span = input.span();
|
||||
|
||||
let _size = size(
|
||||
input.data.clone(),
|
||||
name,
|
||||
&input.attrs,
|
||||
extra_param.is_some(),
|
||||
);
|
||||
let parsed = write(input.data.clone(), name, &input.attrs);
|
||||
let _parsed_unchecked = write(input.data.clone(), name, &input.attrs);
|
||||
|
||||
let endianness_placeholder = endianness.unwrap_or_else(|| "_E".to_owned());
|
||||
let trait_def_str = format!("::bitbuffer::{}<{}>", trait_name, &endianness_placeholder);
|
||||
let trait_def = parse_str::<Path>(&trait_def_str).expect("trait");
|
||||
|
||||
let endianness_ident = Ident::new(&endianness_placeholder, span);
|
||||
|
||||
let _size_extra_param = if extra_param.is_some() {
|
||||
Some(quote!(input_size: usize))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let _extra_param_call = if extra_param.is_some() {
|
||||
Some(quote!(input_size,))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let write_method = Ident::new(&write_method_name, span);
|
||||
|
||||
let expanded = quote! {
|
||||
#[allow(unused_braces)]
|
||||
impl #impl_generics #trait_def for #name #ty_generics #where_clause {
|
||||
fn #write_method(&self, __target__stream: &mut ::bitbuffer::BitWriteStream<#endianness_ident>#extra_param) -> ::bitbuffer::Result<()> {
|
||||
#parsed
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// panic!("{}", TokenStream::to_string(&expanded));
|
||||
|
||||
proc_macro::TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
fn write(data: Data, struct_name: &Ident, attrs: &[Attribute]) -> TokenStream {
|
||||
let span = struct_name.span();
|
||||
|
||||
let align = get_align(attrs);
|
||||
|
||||
match data {
|
||||
Data::Struct(DataStruct { fields, .. }) => {
|
||||
let expand = fields.iter().enumerate().map(|(i, field)| {
|
||||
let name = field
|
||||
.ident
|
||||
.clone()
|
||||
.unwrap_or_else(|| Ident::new(&format!("__{}", i), span));
|
||||
let member = field.ident.clone().map(Member::Named).unwrap_or_else(|| {
|
||||
Member::Unnamed(Index {
|
||||
index: i as u32,
|
||||
span: field.span(),
|
||||
})
|
||||
});
|
||||
// extract int fields to be used in size expressions
|
||||
if type_is_int(&field.ty) {
|
||||
quote_spanned! { field.span() =>
|
||||
#[allow(unused_variables)]
|
||||
let #name = self.#member;
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
}
|
||||
});
|
||||
|
||||
let writes = fields.iter().enumerate().map(|(i, f)| {
|
||||
// Get attributes `#[..]` on each field
|
||||
let size = get_field_size(&f.attrs, f.span());
|
||||
let align = get_align(&f.attrs);
|
||||
let span = f.span();
|
||||
let member = f.ident.clone().map(Member::Named).unwrap_or_else(|| {
|
||||
Member::Unnamed(Index {
|
||||
index: i as u32,
|
||||
span,
|
||||
})
|
||||
});
|
||||
match size {
|
||||
Some(size) => {
|
||||
quote_spanned! { span =>
|
||||
{
|
||||
#align;
|
||||
let _size: usize = #size;
|
||||
__target__stream.write_sized(&self.#member, _size)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
quote_spanned! { span => {
|
||||
{
|
||||
#align;
|
||||
__target__stream.write(&self.#member)?;
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quote_spanned! {span=>
|
||||
#align;
|
||||
#(#expand)*
|
||||
#(#writes)*
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Data::Enum(data) => {
|
||||
let discriminant_bits: u64 = match get_attribute_value(attrs, &["discriminant_bits"]) {
|
||||
Some(attr) => attr,
|
||||
None => {
|
||||
return quote! {span=>
|
||||
compile_error!("'discriminant_bits' attribute is required when deriving `BinWrite` for enums");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut last_discriminant = -1;
|
||||
|
||||
let max_discriminant = data
|
||||
.variants
|
||||
.iter()
|
||||
.map(|variant| match Discriminant::from(variant) {
|
||||
Discriminant::Int(discriminant) => {
|
||||
last_discriminant = discriminant as isize;
|
||||
discriminant
|
||||
}
|
||||
Discriminant::Wildcard => 0,
|
||||
Discriminant::Default => {
|
||||
let new_discriminant = (last_discriminant + 1) as usize;
|
||||
last_discriminant += 1;
|
||||
new_discriminant
|
||||
}
|
||||
})
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
|
||||
let mut last_discriminant = -1;
|
||||
|
||||
let discriminant_value = data.variants.iter().map(|variant| {
|
||||
let span = variant.span();
|
||||
let variant_name = &variant.ident;
|
||||
|
||||
let discriminant_token: TokenStream = match Discriminant::from(variant) {
|
||||
Discriminant::Int(discriminant) => {
|
||||
let lit = LitInt::new(&format!("{}", discriminant), span);
|
||||
last_discriminant = discriminant as isize;
|
||||
quote_spanned! { span => #lit }
|
||||
}
|
||||
Discriminant::Wildcard => {
|
||||
let free_discriminant = max_discriminant + 1;
|
||||
let lit = LitInt::new(&format!("{}", free_discriminant), span);
|
||||
quote_spanned! { span => #lit }
|
||||
}
|
||||
Discriminant::Default => {
|
||||
let new_discriminant = (last_discriminant + 1) as usize;
|
||||
let lit = LitInt::new(&format!("{}", new_discriminant), span);
|
||||
last_discriminant += 1;
|
||||
quote_spanned! { span => #lit }
|
||||
}
|
||||
};
|
||||
|
||||
match &variant.fields {
|
||||
Fields::Unit => quote_spanned! {span =>
|
||||
#struct_name::#variant_name => #discriminant_token
|
||||
},
|
||||
Fields::Unnamed(_f) => {
|
||||
quote_spanned! { span =>
|
||||
#struct_name::#variant_name(_) => #discriminant_token
|
||||
}
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
});
|
||||
|
||||
let write_inner = data.variants.iter().map(|variant| {
|
||||
let span = variant.span();
|
||||
let variant_name = &variant.ident;
|
||||
|
||||
match &variant.fields {
|
||||
Fields::Unit => {
|
||||
if contains_attribute(&variant.attrs, &["align"]) {
|
||||
return quote_spanned! { span =>
|
||||
compile_error!("'align' attribute is not allowed on unit variants");
|
||||
};
|
||||
}
|
||||
quote_spanned! {span =>
|
||||
#struct_name::#variant_name => {},
|
||||
}
|
||||
}
|
||||
Fields::Unnamed(f) => {
|
||||
let size = get_field_size(&variant.attrs, f.span());
|
||||
let align = get_align(&variant.attrs);
|
||||
match size {
|
||||
Some(size) => {
|
||||
quote_spanned! { span =>
|
||||
#struct_name::#variant_name(inner) => {
|
||||
#align;
|
||||
let size:usize = #size;
|
||||
__target__stream.write_sized(inner, size)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
quote_spanned! { span =>
|
||||
#struct_name::#variant_name(inner) => {
|
||||
#align;
|
||||
__target__stream.write(inner)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
});
|
||||
|
||||
let span = data.enum_token.span();
|
||||
let repr = repr_for_bits(discriminant_bits);
|
||||
|
||||
quote_spanned! {span=>
|
||||
#align;
|
||||
let discriminant:#repr = match &self {
|
||||
#(#discriminant_value),*
|
||||
};
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
__target__stream.write_int(discriminant, #discriminant_bits as usize)?;
|
||||
match &self {
|
||||
#(#write_inner)*
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_field_size(attrs: &[Attribute], span: Span) -> Option<TokenStream> {
|
||||
get_attribute_value(attrs, &["size"])
|
||||
.map(|size_lit| match size_lit {
|
||||
Lit::Int(size) => {
|
||||
quote_spanned! {span =>
|
||||
#size
|
||||
}
|
||||
}
|
||||
Lit::Str(size_field) => {
|
||||
let size = parse_str::<Expr>(&size_field.value()).expect("size");
|
||||
quote_spanned! {span => {
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
let __size = (#size) as usize;
|
||||
__size
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => panic!("Unsupported value for size attribute"),
|
||||
})
|
||||
.or_else(|| {
|
||||
get_attribute_value::<Lit>(attrs, &["size_bits"]).map(|_| {
|
||||
quote_spanned! {span =>
|
||||
compile_error!("#[size_bits] is not supported when deriving BitWrite or BitWriteSized")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn type_is_int(ty: &Type) -> bool {
|
||||
if let Type::Path(path) = ty {
|
||||
if let Some(ident) = path.path.get_ident() {
|
||||
let name = ident.to_string();
|
||||
matches!(
|
||||
name.as_str(),
|
||||
"u8" | "u16" | "u32" | "u64" | "usize" | "i8" | "i16" | "i32" | "i64" | "isize"
|
||||
)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn get_align(attrs: &[Attribute]) -> TokenStream {
|
||||
if contains_attribute(attrs, &["align"]) {
|
||||
quote! {
|
||||
__target__stream.align()
|
||||
}
|
||||
} else {
|
||||
quote! { () }
|
||||
}
|
||||
}
|
||||
63
bitbuffer_derive/src/write/enum.rs
Normal file
63
bitbuffer_derive/src/write/enum.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
use crate::params::{EnumParam, VariantBody, VariantBodyType};
|
||||
use crate::write::field::write_enum_variant;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote_spanned;
|
||||
use syn::Path;
|
||||
|
||||
pub fn derive_encode_enum(params: &EnumParam) -> TokenStream {
|
||||
let discriminant_bits = params.discriminant_bits;
|
||||
let repr = params.discriminant_repr();
|
||||
let ident = params.ident.clone();
|
||||
let span = params.span();
|
||||
|
||||
let discriminant_value = params
|
||||
.variants
|
||||
.iter()
|
||||
.zip(params.write_discriminant_tokens())
|
||||
.map(|(variant, discriminant_token)| {
|
||||
let span = variant.span();
|
||||
let variant_name = &variant.variant_name;
|
||||
match variant.body.body_type() {
|
||||
VariantBodyType::Unit => quote_spanned! {span =>
|
||||
#ident::#variant_name => #discriminant_token
|
||||
},
|
||||
VariantBodyType::Unnamed => {
|
||||
quote_spanned! { span =>
|
||||
#ident::#variant_name(_) => #discriminant_token
|
||||
}
|
||||
}
|
||||
VariantBodyType::Named => {
|
||||
quote_spanned! { span =>
|
||||
#ident::#variant_name{..} => #discriminant_token
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let write_inner = params.variants.iter().map(|variant| {
|
||||
let span = variant.span();
|
||||
let mut path = Path::from(ident.clone());
|
||||
path.segments.push(variant.variant_name.clone().into());
|
||||
|
||||
match &variant.body {
|
||||
VariantBody::Unit => {
|
||||
quote_spanned! {span =>
|
||||
#path => {},
|
||||
}
|
||||
}
|
||||
VariantBody::Fields(fields) => write_enum_variant(path, &fields, span),
|
||||
}
|
||||
});
|
||||
|
||||
quote_spanned! {span=>
|
||||
let discriminant:#repr = match &self {
|
||||
#(#discriminant_value),*
|
||||
};
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
__stream.write_int(discriminant, #discriminant_bits as usize)?;
|
||||
match &self {
|
||||
#(#write_inner)*
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
81
bitbuffer_derive/src/write/field.rs
Normal file
81
bitbuffer_derive/src/write/field.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
use crate::params::FieldParam;
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::quote_spanned;
|
||||
use syn::Path;
|
||||
|
||||
pub fn write_struct(fields: &[FieldParam], span: Span) -> TokenStream {
|
||||
let expand = fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.zip(names(fields))
|
||||
.map(|((index, field), name)| {
|
||||
let member = field.member(index as u32);
|
||||
let size_field = match (field.is_int(), field.field_name.as_ref()) {
|
||||
(true, Some(name)) => Some(quote_spanned! { field.span() =>
|
||||
#[allow(unused_variables)]
|
||||
let #name = self.#member;
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
quote_spanned! { field.span() =>
|
||||
#size_field
|
||||
#[allow(unused_variables)]
|
||||
let #name = &self.#member;
|
||||
}
|
||||
});
|
||||
let writes = writes(fields);
|
||||
|
||||
quote_spanned! {span=>
|
||||
#(#expand)*
|
||||
#(#writes)*
|
||||
}
|
||||
}
|
||||
|
||||
fn names(fields: &[FieldParam]) -> impl Iterator<Item = Ident> + '_ {
|
||||
fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, field)| Ident::new(&format!("__field_{}", index), field.span()))
|
||||
}
|
||||
|
||||
fn writes(fields: &[FieldParam]) -> impl Iterator<Item = TokenStream> + '_ {
|
||||
let names = names(fields);
|
||||
fields.iter().zip(names).map(|(field, name)| {
|
||||
let align = &field.align.write();
|
||||
let span = field.span();
|
||||
match &field.size {
|
||||
Some(size) => {
|
||||
quote_spanned! { span =>
|
||||
{
|
||||
#align
|
||||
let _size: usize = #size;
|
||||
__stream.write_sized(#name, _size)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
quote_spanned! { span =>
|
||||
{
|
||||
#align
|
||||
__stream.write(#name)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write_enum_variant(variant: Path, fields: &[FieldParam], span: Span) -> TokenStream {
|
||||
let names = names(fields);
|
||||
let named = fields.iter().any(|f| f.field_name.is_some());
|
||||
let writes = writes(fields);
|
||||
if named {
|
||||
quote_spanned!(span => #variant{#(#names,)*} => {
|
||||
#(#writes;)*
|
||||
})
|
||||
} else {
|
||||
quote_spanned!(span => #variant(#(#names,)*) => {
|
||||
#(#writes;)*
|
||||
})
|
||||
}
|
||||
}
|
||||
68
bitbuffer_derive/src/write/mod.rs
Normal file
68
bitbuffer_derive/src/write/mod.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
pub mod r#enum;
|
||||
pub mod field;
|
||||
pub mod r#struct;
|
||||
|
||||
use self::r#enum::derive_encode_enum;
|
||||
use self::r#struct::derive_encode_struct;
|
||||
use crate::params::{InputInnerParams, InputParams};
|
||||
use crate::Derivable;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::Result;
|
||||
|
||||
fn encode_impl(params: &InputParams) -> Result<TokenStream> {
|
||||
Ok(match ¶ms.inner {
|
||||
InputInnerParams::Struct(inner) => derive_encode_struct(inner),
|
||||
InputInnerParams::Enum(inner) => derive_encode_enum(inner),
|
||||
})
|
||||
}
|
||||
|
||||
pub struct Write;
|
||||
|
||||
impl Derivable for Write {
|
||||
type Params = InputParams;
|
||||
|
||||
fn derive(params: Self::Params) -> Result<TokenStream> {
|
||||
let (impl_generics, ty_generics, where_clause) = params.generics_for_impl();
|
||||
|
||||
let encode = encode_impl(¶ms)?;
|
||||
let endianness = params.endianness();
|
||||
let name = params.ident.clone();
|
||||
let align = params.align.write();
|
||||
|
||||
Ok(quote! {
|
||||
impl #impl_generics ::bitbuffer::BitWrite<#endianness> for #name #ty_generics #where_clause {
|
||||
#[allow(unused_braces)]
|
||||
fn write(&self, __stream: &mut ::bitbuffer::BitWriteStream<#endianness>) -> ::bitbuffer::Result<()> {
|
||||
#align
|
||||
#encode
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WriteSized;
|
||||
|
||||
impl Derivable for WriteSized {
|
||||
type Params = InputParams;
|
||||
|
||||
fn derive(params: Self::Params) -> Result<TokenStream> {
|
||||
let (impl_generics, ty_generics, where_clause) = params.generics_for_impl();
|
||||
|
||||
let encode = encode_impl(¶ms)?;
|
||||
let endianness = params.endianness();
|
||||
let name = params.ident.clone();
|
||||
let align = params.align.write();
|
||||
|
||||
Ok(quote! {
|
||||
impl #impl_generics ::bitbuffer::BitWriteSized<#endianness> for #name #ty_generics #where_clause {
|
||||
#[allow(unused_braces)]
|
||||
fn write_sized(&self, __stream: &mut ::bitbuffer::BitWriteStream<#endianness>, input_size: usize) -> ::bitbuffer::Result<()> {
|
||||
#align
|
||||
#encode
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
13
bitbuffer_derive/src/write/struct.rs
Normal file
13
bitbuffer_derive/src/write/struct.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
use crate::params::StructParam;
|
||||
use crate::write::field::write_struct;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
pub fn derive_encode_struct(params: &StructParam) -> TokenStream {
|
||||
let body = write_struct(¶ms.fields, params.span());
|
||||
|
||||
quote!(
|
||||
#body
|
||||
Ok(())
|
||||
)
|
||||
}
|
||||
|
|
@ -3,13 +3,8 @@
|
|||
#![allow(unused_imports)]
|
||||
|
||||
use bitbuffer::{BitReadStream, Endianness};
|
||||
use bitbuffer_derive::{BitRead, BitWrite, BitWriteSized};
|
||||
use bitbuffer_derive::{BitRead, BitReadSized, BitWrite, BitWriteSized};
|
||||
|
||||
#[derive(BitWrite)]
|
||||
#[discriminant_bits = 4]
|
||||
enum TestEnumRest {
|
||||
Foo,
|
||||
Bar,
|
||||
#[discriminant = "_"]
|
||||
Asd,
|
||||
}
|
||||
#[derive(BitWrite, PartialEq, Debug)]
|
||||
#[align]
|
||||
struct AlignStruct(u8);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue