mirror of
https://codeberg.org/icewind/bitbuffer.git
synced 2026-06-03 16:44:06 +02:00
rewrite derive macro
This commit is contained in:
parent
0701318120
commit
3852f09dd5
21 changed files with 1548 additions and 840 deletions
86
Cargo.lock
generated
86
Cargo.lock
generated
|
|
@ -28,10 +28,11 @@ name = "bitbuffer_derive"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitbuffer",
|
"bitbuffer",
|
||||||
|
"merge",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"structmeta",
|
||||||
"syn_util",
|
"syn 2.0.22",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -50,7 +51,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"rustversion",
|
"rustversion",
|
||||||
"syn",
|
"syn 1.0.92",
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -78,6 +79,28 @@ version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "merge"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10bbef93abb1da61525bbc45eeaff6473a41907d19f8f9aa5168d214e10693e9"
|
||||||
|
dependencies = [
|
||||||
|
"merge_derive",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "merge_derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "209d075476da2e63b4b29e72a2ef627b840589588e71400a25e3565c4f849d07"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.92",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
|
|
@ -96,7 +119,7 @@ dependencies = [
|
||||||
"proc-macro-error-attr",
|
"proc-macro-error-attr",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.92",
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -113,18 +136,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.37"
|
version = "1.0.63"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1"
|
checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-xid",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.18"
|
version = "1.0.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
|
checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
@ -162,7 +185,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"serde_derive_internals",
|
"serde_derive_internals",
|
||||||
"syn",
|
"syn 1.0.92",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -182,7 +205,7 @@ checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.92",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -193,7 +216,7 @@ checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.92",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -207,6 +230,29 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "structmeta"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78ad9e09554f0456d67a69c1584c9798ba733a5b50349a6c0d0948710523922d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"structmeta-derive",
|
||||||
|
"syn 2.0.22",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "structmeta-derive"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.22",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.92"
|
version = "1.0.92"
|
||||||
|
|
@ -219,14 +265,14 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn_util"
|
name = "syn"
|
||||||
version = "0.4.2"
|
version = "2.0.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6754c4559b79657554e9d8a0d56e65e490c76d382b9c23108364ec4125dea23c"
|
checksum = "2efbeae7acf4eabd6bcdcbd11c92f45231ddda7539edc7806bd1a04a03b24616"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -237,10 +283,16 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.92",
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,11 @@ name = "bitbuffer_derive"
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
syn = "1.0"
|
syn = { version = "2.0.22", features = ["extra-traits"] }
|
||||||
quote = "1.0"
|
quote = "1.0.29"
|
||||||
proc-macro2 = "1.0"
|
proc-macro2 = "1.0.63"
|
||||||
syn_util = "0.4"
|
structmeta = "0.2.0"
|
||||||
|
merge = "0.1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
bitbuffer = { version = "0.10", path = ".." }
|
bitbuffer = { version = "0.10", path = ".." }
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,105 @@
|
||||||
use syn::{Expr, Lit, Variant};
|
use crate::err;
|
||||||
use syn_util::get_attribute_value;
|
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 {
|
pub enum Discriminant {
|
||||||
Int(usize),
|
Int(usize),
|
||||||
Default,
|
Default,
|
||||||
Wildcard,
|
Wildcard,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Lit> for Discriminant {
|
impl TryFrom<Expr> for Discriminant {
|
||||||
fn from(lit: Lit) -> Self {
|
type Error = Error;
|
||||||
match lit {
|
|
||||||
Lit::Int(lit) => Discriminant::Int(lit.base10_parse::<usize>().unwrap()),
|
fn try_from(value: Expr) -> Result<Self, Self::Error> {
|
||||||
Lit::Str(lit) => match lit.value().as_str() {
|
match value {
|
||||||
"_" => Discriminant::Wildcard,
|
Expr::Lit(ExprLit { lit, .. }) => lit.try_into(),
|
||||||
_ => panic!("discriminant is required to be an integer literal or \"_\""),
|
_ => err("non literal discriminant", value.span())?,
|
||||||
},
|
|
||||||
_ => panic!("discriminant is required to be an integer literal or \"_\""),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Variant> for Discriminant {
|
impl TryFrom<Lit> for Discriminant {
|
||||||
fn from(variant: &Variant) -> Self {
|
type Error = Error;
|
||||||
variant
|
|
||||||
.discriminant
|
fn try_from(value: Lit) -> Result<Self, Self::Error> {
|
||||||
.as_ref()
|
let span = value.span();
|
||||||
.map(|(_, expr)| match expr {
|
match value {
|
||||||
Expr::Lit(expr_lit) => expr_lit.lit.clone(),
|
Lit::Int(lit) => Ok(Discriminant::Int(lit.base10_parse()?)),
|
||||||
_ => panic!("discriminant is required to be an integer literal"),
|
Lit::Str(lit) => match lit.value().as_str() {
|
||||||
})
|
"_" => Ok(Discriminant::Wildcard),
|
||||||
.or_else(|| get_attribute_value(&variant.attrs, &["discriminant"]))
|
_ => err(
|
||||||
.map(Discriminant::from)
|
"discriminant is required to be an integer literal or \"_\"",
|
||||||
.unwrap_or(Discriminant::Default)
|
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 discriminant;
|
||||||
|
mod params;
|
||||||
|
mod read;
|
||||||
|
mod size_hint;
|
||||||
mod write;
|
mod write;
|
||||||
|
|
||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
|
|
||||||
use crate::write::derive_bitwrite_trait;
|
use crate::read::{Read, ReadSized};
|
||||||
use discriminant::Discriminant;
|
use crate::write::{Write, WriteSized};
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
use quote::{quote, quote_spanned};
|
use std::fmt::Display;
|
||||||
use syn::spanned::Spanned;
|
use syn::{parse_macro_input, DeriveInput, Error, Result};
|
||||||
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};
|
|
||||||
|
|
||||||
/// See the [crate documentation](index.html) for details
|
/// See the [crate documentation](index.html) for details
|
||||||
#[proc_macro_derive(
|
#[proc_macro_derive(
|
||||||
BitRead,
|
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 {
|
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
|
/// See the [crate documentation](index.html) for details
|
||||||
#[proc_macro_derive(
|
#[proc_macro_derive(
|
||||||
BitReadSized,
|
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 {
|
pub fn derive_bitread_sized(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
let extra_param = parse_str::<TokenStream>(", input_size: usize").unwrap();
|
derive_trait::<ReadSized>(input)
|
||||||
derive_bitread_trait(input, "BitReadSized".to_owned(), Some(extra_param))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See the [crate documentation](index.html) for details
|
/// See the [crate documentation](index.html) for details
|
||||||
#[proc_macro_derive(
|
#[proc_macro_derive(
|
||||||
BitWrite,
|
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 {
|
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
|
/// See the [crate documentation](index.html) for details
|
||||||
#[proc_macro_derive(
|
#[proc_macro_derive(
|
||||||
BitWriteSized,
|
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 {
|
pub fn derive_bitwrite_sized(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
let extra_param = parse_str::<TokenStream>(", input_size: usize").unwrap();
|
derive_trait::<WriteSized>(input)
|
||||||
derive_bitwrite_trait(
|
|
||||||
input,
|
|
||||||
"BitWriteSized".into(),
|
|
||||||
"write_sized".into(),
|
|
||||||
Some(extra_param),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn derive_bitread_trait(
|
/// Basic wrapper for error handling
|
||||||
input: proc_macro::TokenStream,
|
fn derive_trait<Trait: Derivable>(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
trait_name: String,
|
|
||||||
extra_param: Option<TokenStream>,
|
|
||||||
) -> proc_macro::TokenStream {
|
|
||||||
let input: DeriveInput = parse_macro_input!(input as DeriveInput);
|
let input: DeriveInput = parse_macro_input!(input as DeriveInput);
|
||||||
|
derive_trait_inner::<Trait>(input)
|
||||||
let name = &input.ident;
|
.unwrap_or_else(|err| err.into_compile_error())
|
||||||
|
.into()
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(data: Data, struct_name: &Ident, attrs: &[Attribute], unchecked: bool) -> TokenStream {
|
fn derive_trait_inner<Trait: Derivable>(input: DeriveInput) -> Result<TokenStream> {
|
||||||
let span = struct_name.span();
|
let params = Trait::Params::parse(&input)?;
|
||||||
|
Trait::derive(params)
|
||||||
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 size(data: Data, struct_name: &Ident, attrs: &[Attribute], has_input_size: bool) -> TokenStream {
|
trait Derivable {
|
||||||
let span = struct_name.span();
|
type Params: DeriveParams;
|
||||||
|
|
||||||
if contains_attribute(attrs, &["align"]) {
|
fn derive(params: Self::Params) -> Result<TokenStream>;
|
||||||
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 is_const_size(attrs: &[Attribute], has_input_size: bool) -> bool {
|
trait DeriveParams: Sized {
|
||||||
if get_attribute_value::<Lit>(attrs, &["size_bits"]).is_some() {
|
fn parse(input: &DeriveInput) -> Result<Self>;
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_field_size(attrs: &[Attribute], span: Span) -> Option<TokenStream> {
|
fn err<R, Msg: Display>(msg: Msg, span: Span) -> Result<R> {
|
||||||
get_attribute_value(attrs, &["size"])
|
return Err(Error::new(span, msg));
|
||||||
.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! { () }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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)]
|
#![allow(unused_imports)]
|
||||||
|
|
||||||
use bitbuffer::{BitReadStream, Endianness};
|
use bitbuffer::{BitReadStream, Endianness};
|
||||||
use bitbuffer_derive::{BitRead, BitWrite, BitWriteSized};
|
use bitbuffer_derive::{BitRead, BitReadSized, BitWrite, BitWriteSized};
|
||||||
|
|
||||||
#[derive(BitWrite)]
|
#[derive(BitWrite, PartialEq, Debug)]
|
||||||
#[discriminant_bits = 4]
|
#[align]
|
||||||
enum TestEnumRest {
|
struct AlignStruct(u8);
|
||||||
Foo,
|
|
||||||
Bar,
|
|
||||||
#[discriminant = "_"]
|
|
||||||
Asd,
|
|
||||||
}
|
|
||||||
|
|
|
||||||
14
flake.nix
14
flake.nix
|
|
@ -73,7 +73,19 @@
|
||||||
];
|
];
|
||||||
|
|
||||||
devShells = let
|
devShells = let
|
||||||
tools = with pkgs; [bacon cargo-edit cargo-outdated];
|
tools = with pkgs; [
|
||||||
|
bacon
|
||||||
|
cargo-edit
|
||||||
|
cargo-outdated
|
||||||
|
(writeShellApplication {
|
||||||
|
name = "cargo-expand";
|
||||||
|
runtimeInputs = [cargo-expand toolchain];
|
||||||
|
text = ''
|
||||||
|
# shellcheck disable=SC2068
|
||||||
|
RUSTC_BOOTSTRAP=1 cargo-expand $@
|
||||||
|
'';
|
||||||
|
})
|
||||||
|
];
|
||||||
in {
|
in {
|
||||||
default = mkShell {
|
default = mkShell {
|
||||||
nativeBuildInputs = [toolchain] ++ tools;
|
nativeBuildInputs = [toolchain] ++ tools;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue