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;
tokens.append_all(quote_spanned! {span => {
#[allow(clippy::unnecessary_cast)]
let __size = (#expr) as usize;
__size
}
});
}
Size::Bits(bits, span) => {
let span = *span;
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 {
#[merge(strategy = merge::option::overwrite_none)]
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(&self) -> (ImplGenerics, TypeGenerics, Option<&WhereClause>) {
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("_E"), self.span)
}
}
const BARE_ATTRS: &[&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.iter() {
let attr_path = attr.meta.path();
let parsed = if BARE_ATTRS.iter().any(|name| attr_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,
bracket_token: attr.bracket_token,
meta: wrapped_meta,
};
Some(wrapped.parse_args())
} else if attr_path.is_ident("bitbuffer") {
Some(attr.parse_args())
} else {
None
};
match parsed {
Some(Ok(parsed)) => {
result.merge(parsed);
}
Some(Err(e)) => {
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);
}
}
None => {}
}
}
Ok(result)
}