use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::parse::{Parse, ParseStream, Parser};
use syn::punctuated::Punctuated;
use syn::{
Data, DeriveInput, Fields, FieldsNamed, Ident, ItemStruct, Token, Type, parse_macro_input,
};
struct CtxParam {
name: Ident,
ty: Type,
}
impl Parse for CtxParam {
fn parse(input: ParseStream) -> syn::Result<Self> {
let name: Ident = input.parse()?;
input.parse::<Token![:]>()?;
let ty: Type = input.parse()?;
Ok(CtxParam { name, ty })
}
}
fn ctx_struct_ident(name: &Ident) -> Ident {
format_ident!("{}Ctx", name)
}
fn ctx_struct_ty(ty: &Type) -> syn::Result<TokenStream2> {
if let Type::Path(p) = ty {
let mut path = p.path.clone();
if let Some(last) = path.segments.last_mut() {
last.ident = ctx_struct_ident(&last.ident);
last.arguments = syn::PathArguments::None;
return Ok(quote!(#path));
}
}
Err(syn::Error::new_spanned(
ty,
"a `ctx`-parameterized field must have a path type (so its `…Ctx` struct can be named)",
))
}
const BYTE_ALIGNED_MSG: &str = "this struct's fields are all byte-aligned. The bare \
`#[derive(BitDecode/BitEncode)]` is the low-level bit codec; for a byte-aligned message use \
`#[bin]` — the unified codec (it handles byte-aligned data natively and adds \
magic/count/ctx/map/if/validate). The bare derive is for fields that straddle byte boundaries \
(e.g. a 108-bit payload). To keep the bare derive on an all-byte-aligned struct anyway, add \
`#[bit_stream(allow_byte_aligned)]`.";
fn named_struct(input: &DeriveInput) -> syn::Result<&FieldsNamed> {
if !input.generics.params.is_empty() {
return Err(syn::Error::new_spanned(
&input.generics,
"BitDecode/BitEncode do not support generic parameters yet",
));
}
match &input.data {
Data::Struct(s) => match &s.fields {
Fields::Named(f) => Ok(f),
_ => Err(syn::Error::new_spanned(
&input.ident,
"BitDecode/BitEncode require a struct with named fields",
)),
},
_ => Err(syn::Error::new_spanned(
&input.ident,
"BitDecode/BitEncode can only derive for structs",
)),
}
}
#[derive(Default)]
struct BitStreamAttrs {
allow_byte_aligned: bool,
lsb: bool,
little: bool,
magic: Option<syn::Expr>,
ctx: Vec<(Ident, Type)>,
}
fn parse_bit_stream(input: &DeriveInput) -> syn::Result<BitStreamAttrs> {
let mut attrs = BitStreamAttrs::default();
for attr in &input.attrs {
if attr.path().is_ident("bit_stream") {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("allow_byte_aligned") {
attrs.allow_byte_aligned = true;
Ok(())
} else if meta.path.is_ident("bit_order") {
let val: Ident = meta.value()?.parse()?;
match val.to_string().as_str() {
"msb" => attrs.lsb = false,
"lsb" => attrs.lsb = true,
_ => return Err(meta.error("expected `msb` or `lsb`")),
}
Ok(())
} else if meta.path.is_ident("byte_order") {
let val: Ident = meta.value()?.parse()?;
match val.to_string().as_str() {
"big" => attrs.little = false,
"little" => attrs.little = true,
_ => return Err(meta.error("expected `big` or `little`")),
}
Ok(())
} else if meta.path.is_ident("magic") {
attrs.magic = Some(meta.value()?.parse()?);
Ok(())
} else if meta.path.is_ident("ctx") {
let content;
syn::parenthesized!(content in meta.input);
let params = Punctuated::<CtxParam, Token![,]>::parse_terminated(&content)?;
attrs.ctx = params.into_iter().map(|p| (p.name, p.ty)).collect();
Ok(())
} else {
Err(meta.error(
"unknown `#[bit_stream(...)]` option; expected `allow_byte_aligned`, `bit_order = msb|lsb`, `byte_order = big|little`, `magic = <expr>`, or `ctx(name: Ty, …)`",
))
}
})?;
}
}
Ok(attrs)
}
fn layout_token(attrs: &BitStreamAttrs) -> TokenStream2 {
let bnb = crate::bnb_path();
let bit = if attrs.lsb {
quote!(#bnb::__private::BitOrder::Lsb)
} else {
quote!(#bnb::__private::BitOrder::Msb)
};
let byte = if attrs.little {
quote!(#bnb::__private::ByteOrder::Little)
} else {
quote!(#bnb::__private::ByteOrder::Big)
};
quote!(#bnb::__private::Layout { bit: #bit, byte: #byte })
}
fn is_nested(f: &syn::Field) -> bool {
f.attrs.iter().any(|a| a.path().is_ident("nested"))
}
fn byte_array_len(f: &syn::Field) -> Option<&syn::Expr> {
if let syn::Type::Array(arr) = &f.ty {
if let syn::Type::Path(p) = &*arr.elem {
if p.path.is_ident("u8") {
return Some(&arr.len);
}
}
}
None
}
fn vec_elem(f: &syn::Field) -> Option<&syn::Type> {
single_generic(&f.ty, "Vec")
}
fn option_elem(f: &syn::Field) -> Option<&syn::Type> {
single_generic(&f.ty, "Option")
}
fn single_generic<'a>(ty: &'a syn::Type, wrapper: &str) -> Option<&'a syn::Type> {
if let syn::Type::Path(p) = ty {
let seg = p.path.segments.last()?;
if seg.ident == wrapper {
if let syn::PathArguments::AngleBracketed(a) = &seg.arguments {
if let Some(syn::GenericArgument::Type(t)) = a.args.first() {
return Some(t);
}
}
}
}
None
}
#[derive(Default)]
struct FieldBr {
count: Option<syn::Expr>,
ctx: Option<Vec<Ident>>,
temp: bool,
cond: Option<syn::Expr>,
ignore: bool,
map: Option<syn::Expr>,
try_map: Option<syn::Expr>,
parse_with: Option<syn::Expr>,
calc: Option<syn::Expr>,
bw_map: Option<syn::Expr>,
write_with: Option<syn::Expr>,
pad_before: Option<syn::Expr>,
pad_after: Option<syn::Expr>,
align_before: bool,
align_after: bool,
restore_position: bool,
seek: Option<syn::Expr>,
dbg: bool,
}
enum BrDirective {
Count(syn::Expr),
Ctx(Vec<Ident>),
Temp,
If(syn::Expr),
Map(syn::Expr),
TryMap(syn::Expr),
ParseWith(syn::Expr),
PadBefore(syn::Expr),
PadAfter(syn::Expr),
AlignBefore,
AlignAfter,
RestorePosition,
Seek(syn::Expr),
Dbg,
}
impl Parse for BrDirective {
fn parse(input: ParseStream) -> syn::Result<Self> {
if input.peek(Token![if]) {
input.parse::<Token![if]>()?;
let content;
syn::parenthesized!(content in input);
Ok(BrDirective::If(content.parse()?))
} else {
let kw: Ident = input.parse()?;
match kw.to_string().as_str() {
"count" => {
input.parse::<Token![=]>()?;
Ok(BrDirective::Count(input.parse()?))
}
"temp" => Ok(BrDirective::Temp),
"ignore" => Err(syn::Error::new_spanned(
&kw,
"`ignore` marks a field as neither read nor written; write it as `#[brw(ignore)]`",
)),
"ctx" => {
let content;
syn::braced!(content in input);
let names = Punctuated::<Ident, Token![,]>::parse_terminated(&content)?;
Ok(BrDirective::Ctx(names.into_iter().collect()))
}
"map" => {
input.parse::<Token![=]>()?;
Ok(BrDirective::Map(input.parse()?))
}
"try_map" => {
input.parse::<Token![=]>()?;
Ok(BrDirective::TryMap(input.parse()?))
}
"parse_with" => {
input.parse::<Token![=]>()?;
Ok(BrDirective::ParseWith(input.parse()?))
}
"pad_before" => {
input.parse::<Token![=]>()?;
Ok(BrDirective::PadBefore(input.parse()?))
}
"pad_after" => {
input.parse::<Token![=]>()?;
Ok(BrDirective::PadAfter(input.parse()?))
}
"align_before" => Ok(BrDirective::AlignBefore),
"align_after" => Ok(BrDirective::AlignAfter),
"restore_position" => Ok(BrDirective::RestorePosition),
"seek" => {
input.parse::<Token![=]>()?;
Ok(BrDirective::Seek(input.parse()?))
}
"dbg" => Ok(BrDirective::Dbg),
_ => Err(syn::Error::new_spanned(
kw,
"unknown `#[br(...)]` directive; expected `count`, `ctx`, `temp`, `if`, `map`, `try_map`, `parse_with`, `pad_before/after`, `align_before/after`, `restore_position`, `seek = <bits>`, or `dbg`",
)),
}
}
}
}
fn parse_field_br(f: &syn::Field) -> syn::Result<FieldBr> {
if let Some(id) = &f.ident {
if id == "r" || id == "w" {
return Err(syn::Error::new_spanned(
id,
format!(
"a field named `{id}` collides with the codec's generated source/sink — rename it (e.g. `{id}_`)"
),
));
}
}
let mut br = FieldBr::default();
for attr in &f.attrs {
if attr.path().is_ident("br") {
let directives =
attr.parse_args_with(Punctuated::<BrDirective, Token![,]>::parse_terminated)?;
for d in directives {
match d {
BrDirective::Count(e) => br.count = Some(e),
BrDirective::Ctx(names) => br.ctx = Some(names),
BrDirective::Temp => br.temp = true,
BrDirective::If(e) => br.cond = Some(e),
BrDirective::Map(e) => br.map = Some(e),
BrDirective::TryMap(e) => br.try_map = Some(e),
BrDirective::ParseWith(e) => br.parse_with = Some(e),
BrDirective::PadBefore(e) => br.pad_before = Some(e),
BrDirective::PadAfter(e) => br.pad_after = Some(e),
BrDirective::AlignBefore => br.align_before = true,
BrDirective::AlignAfter => br.align_after = true,
BrDirective::RestorePosition => br.restore_position = true,
BrDirective::Seek(e) => br.seek = Some(e),
BrDirective::Dbg => br.dbg = true,
}
}
} else if attr.path().is_ident("bw") {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("calc") {
br.calc = Some(meta.value()?.parse()?);
Ok(())
} else if meta.path.is_ident("map") {
br.bw_map = Some(meta.value()?.parse()?);
Ok(())
} else if meta.path.is_ident("write_with") {
br.write_with = Some(meta.value()?.parse()?);
Ok(())
} else {
Err(meta.error(
"unknown `#[bw(...)]` directive; expected `calc = <expr>`, `map = <f>`, or `write_with = <f>`",
))
}
})?;
} else if attr.path().is_ident("brw") {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("ignore") {
br.ignore = true;
Ok(())
} else {
Err(meta.error("unknown `#[brw(...)]` directive; expected `ignore`"))
}
})?;
}
}
if br.map.is_some() && br.try_map.is_some() {
return Err(syn::Error::new_spanned(
f,
"`#[br(map = …)]` and `#[br(try_map = …)]` are mutually exclusive",
));
}
Ok(br)
}
fn field_is_temp(f: &syn::Field) -> bool {
parse_field_br(f).is_ok_and(|br| br.temp)
}
fn field_is_ignore(f: &syn::Field) -> bool {
parse_field_br(f).is_ok_and(|br| br.ignore)
}
fn br_indeterminate(br: &FieldBr) -> bool {
br.ctx.is_some()
|| br.cond.is_some()
|| br.map.is_some()
|| br.try_map.is_some()
|| br.bw_map.is_some()
|| br.parse_with.is_some()
|| br.write_with.is_some()
|| br.pad_before.is_some()
|| br.pad_after.is_some()
|| br.align_before
|| br.align_after
|| br.restore_position
|| br.seek.is_some()
}
enum Reserved {
Zero,
With(Box<syn::Expr>),
}
fn field_reserved(f: &syn::Field) -> syn::Result<Option<Reserved>> {
for attr in &f.attrs {
if attr.path().is_ident("reserved") {
return Ok(Some(Reserved::Zero));
}
if attr.path().is_ident("reserved_with") {
return Ok(Some(Reserved::With(Box::new(attr.parse_args()?))));
}
}
Ok(None)
}
fn reserved_spec_value(f: &syn::Field) -> syn::Result<Option<TokenStream2>> {
let bnb = crate::bnb_path();
let ty = &f.ty;
Ok(field_reserved(f)?.map(|reserved| match reserved {
Reserved::Zero => quote!(<#ty as #bnb::__private::Bits>::from_bits(0)),
Reserved::With(expr) => {
let expr = *expr;
quote!({ let __r: #ty = #expr; __r })
}
}))
}
fn field_is_reserved(f: &syn::Field) -> bool {
f.attrs
.iter()
.any(|a| a.path().is_ident("reserved") || a.path().is_ident("reserved_with"))
}
fn is_codec_field_attr(a: &syn::Attribute) -> bool {
[
"nested",
"br",
"bw",
"brw",
"builder",
"reserved",
"reserved_with",
]
.iter()
.any(|n| a.path().is_ident(n))
}
fn ctx_literal(
ctx_ty: &TokenStream2,
names: &[Ident],
field_set: Option<&[&Ident]>,
) -> TokenStream2 {
let inits = names.iter().map(|n| match field_set {
Some(fields) if fields.contains(&n) => quote!(#n: self.#n),
_ => quote!(#n),
});
quote!(#ctx_ty { #(#inits),* })
}
fn field_width(f: &syn::Field) -> TokenStream2 {
let bnb = crate::bnb_path();
let ty = &f.ty;
if field_is_ignore(f) {
return quote!(0u32); }
if let Some(elem) = vec_elem(f) {
if is_nested(f) {
quote!(<#elem as #bnb::__private::FixedBitLen>::BIT_LEN)
} else {
quote!(<#elem as #bnb::__private::Bits>::BITS)
}
} else if is_nested(f) {
quote!(<#ty as #bnb::__private::FixedBitLen>::BIT_LEN)
} else if let Some(len) = byte_array_len(f) {
quote!(((#len) as u32 * 8))
} else {
quote!(<#ty as #bnb::__private::Bits>::BITS)
}
}
fn pad_read_tokens(align: bool, pad: Option<&syn::Expr>) -> TokenStream2 {
let bnb = crate::bnb_path();
let align = align.then(|| quote!(#bnb::__private::align_read(r)?;));
let pad = pad.map(|n| quote!(#bnb::__private::skip_read(r, #n)?;));
quote!(#align #pad)
}
fn pad_write_tokens(align: bool, pad: Option<&syn::Expr>) -> TokenStream2 {
let bnb = crate::bnb_path();
let align = align.then(|| quote!(#bnb::__private::align_write(w)?;));
let pad = pad.map(|n| quote!(#bnb::__private::skip_write(w, #n)?;));
quote!(#align #pad)
}
fn field_read_stmt(f: &syn::Field, br: &FieldBr) -> syn::Result<TokenStream2> {
let bnb = crate::bnb_path();
let pre = pad_read_tokens(br.align_before, br.pad_before.as_ref());
let post = pad_read_tokens(br.align_after, br.pad_after.as_ref());
let seek = br
.seek
.as_ref()
.map(|e| quote!(#bnb::__private::Source::seek_to_bit(r, (#e) as usize)?;));
let mut core = field_read_core(f, br)?;
if br.dbg {
let id = f.ident.as_ref().expect("named field");
core = quote! {
let __dbg_at = #bnb::__private::Source::bit_pos(r);
#core
#bnb::__private::tracing::trace!(
target: "bnb::dbg",
field = ::core::stringify!(#id),
at_bit = __dbg_at,
value = ?#id,
);
};
}
let mut body = quote!(#seek #core);
if br.restore_position {
body = quote! {
let __pos = #bnb::__private::Source::bit_pos(r);
#body
#bnb::__private::Source::seek_to_bit(r, __pos)?;
};
}
Ok(quote!(#pre #body #post))
}
fn field_read_core(f: &syn::Field, br: &FieldBr) -> syn::Result<TokenStream2> {
let bnb = crate::bnb_path();
let id = f.ident.as_ref().expect("named field");
let ty = &f.ty;
if br.ignore {
return Ok(quote!(let #id = ::core::default::Default::default();));
}
if let Some(cond) = &br.cond {
let inner = option_elem(f).ok_or_else(|| {
syn::Error::new_spanned(f, "`#[br(if(...))]` requires an `Option<_>` field")
})?;
let read_inner = if is_nested(f) {
quote!(<#inner as #bnb::__private::BitDecode>::bit_decode(r)
.map_err(|e| e.in_field(::core::stringify!(#id)))?)
} else {
quote! {{
let __v: #inner = #bnb::__private::Source::read(r)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;
__v
}}
};
return Ok(quote! {
let #id = if (#cond) {
::core::option::Option::Some(#read_inner)
} else {
::core::option::Option::None
};
});
}
if let Some(map) = &br.map {
return Ok(quote!(let #id: #ty = #bnb::__private::read_mapped(r, #map)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;));
}
if let Some(try_map) = &br.try_map {
return Ok(
quote!(let #id: #ty = #bnb::__private::read_try_mapped(r, #try_map)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;),
);
}
if let Some(f) = &br.parse_with {
return Ok(quote!(let #id: #ty = (#f)(r)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;));
}
if let Some(elem) = vec_elem(f) {
let count = br.count.as_ref().ok_or_else(|| {
syn::Error::new_spanned(f, "a `Vec<_>` field needs `#[br(count = <expr>)]`")
})?;
let read_elem = if let Some(names) = &br.ctx {
let lit = ctx_literal(&ctx_struct_ty(elem)?, names, None);
quote! {
let __e = <#elem>::decode_with(r, #lit)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;
}
} else if is_nested(f) {
quote! {
let __e = <#elem as #bnb::__private::BitDecode>::bit_decode(r)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;
}
} else {
quote! {
let __e: #elem = #bnb::__private::Source::read(r)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;
}
};
Ok(quote! {
let #id = {
let __n = (#count) as usize;
let mut __v: #bnb::__private::Vec<#elem> = #bnb::__private::Vec::new();
for _ in 0..__n {
#read_elem
__v.push(__e);
}
__v
};
})
} else {
if br.count.is_some() {
return Err(syn::Error::new_spanned(
f,
"`#[br(count = …)]` applies only to a `Vec<_>` field",
));
}
if let Some(names) = &br.ctx {
let lit = ctx_literal(&ctx_struct_ty(ty)?, names, None);
Ok(quote!(let #id = <#ty>::decode_with(r, #lit)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;))
} else if is_nested(f) {
Ok(
quote!(let #id = <#ty as #bnb::__private::BitDecode>::bit_decode(r)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;),
)
} else if byte_array_len(f).is_some() {
Ok(quote!(let #id = #bnb::__private::read_byte_array(r)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;))
} else {
Ok(quote!(let #id: #ty = r.read().map_err(|e| e.in_field(::core::stringify!(#id)))?;))
}
}
}
fn field_write_stmt(
f: &syn::Field,
br: &FieldBr,
field_set: &[&Ident],
spec: bool,
) -> syn::Result<TokenStream2> {
let pre = pad_write_tokens(br.align_before, br.pad_before.as_ref());
let post = pad_write_tokens(br.align_after, br.pad_after.as_ref());
let core = if br.restore_position {
quote!()
} else {
field_write_core(f, br, field_set, spec)?
};
Ok(quote!(#pre #core #post))
}
fn field_write_core(
f: &syn::Field,
br: &FieldBr,
field_set: &[&Ident],
spec: bool,
) -> syn::Result<TokenStream2> {
let bnb = crate::bnb_path();
let id = f.ident.as_ref().expect("named field");
let ty = &f.ty;
if spec {
if let Some(value) = reserved_spec_value(f)? {
return Ok(quote!(#bnb::__private::Sink::write(w, #value)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;));
}
}
if br.ignore {
return Ok(quote!());
}
if let Some(calc) = &br.calc {
if br.temp {
return Ok(quote! {
let #id: #ty = #calc;
#bnb::__private::Sink::write(w, #id)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;
});
}
return Ok(quote! {
{
let __calc: #ty = #calc;
#bnb::__private::Sink::write(w, __calc)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;
}
});
}
if br.temp {
return Err(syn::Error::new_spanned(
f,
"a `#[br(temp)]` field is not stored, so it needs `#[bw(calc = <expr>)]` to encode",
));
}
if let Some(bw_map) = &br.bw_map {
return Ok(quote!(#bnb::__private::write_mapped(w, &self.#id, #bw_map)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;));
}
if let Some(f) = &br.write_with {
return Ok(quote!((#f)(&self.#id, w)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;));
}
if br.map.is_some() || br.try_map.is_some() {
return Err(syn::Error::new_spanned(
f,
"a `#[br(map = …)]`/`#[br(try_map = …)]` field needs the inverse `#[bw(map = <f>)]` to encode",
));
}
if br.parse_with.is_some() {
return Err(syn::Error::new_spanned(
f,
"a `#[br(parse_with = …)]` field needs the inverse `#[bw(write_with = <f>)]` to encode",
));
}
if br.cond.is_some() {
let inner = option_elem(f).ok_or_else(|| {
syn::Error::new_spanned(f, "`#[br(if(...))]` requires an `Option<_>` field")
})?;
let write_inner = if is_nested(f) {
quote!(<#inner as #bnb::__private::BitEncode>::bit_encode(__v, w)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;)
} else {
quote!(#bnb::__private::Sink::write(w, *__v)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;)
};
return Ok(quote! {
if let ::core::option::Option::Some(__v) = &self.#id {
#write_inner
}
});
}
if let Some(elem) = vec_elem(f) {
let write_elem = if let Some(names) = &br.ctx {
let elem_ctx = ctx_struct_ty(elem)?;
let lit = ctx_literal(&elem_ctx, names, Some(field_set));
quote!(<#elem as #bnb::EncodeWith<#elem_ctx>>::encode_with(__e, w, #lit)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;)
} else if is_nested(f) {
quote!(<#elem as #bnb::__private::BitEncode>::bit_encode(__e, w)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;)
} else {
quote!(#bnb::__private::Sink::write(w, *__e)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;)
};
Ok(quote! {
for __e in &self.#id {
#write_elem
}
})
} else if let Some(names) = &br.ctx {
let child_ctx = ctx_struct_ty(ty)?;
let lit = ctx_literal(&child_ctx, names, Some(field_set));
Ok(
quote!(<#ty as #bnb::EncodeWith<#child_ctx>>::encode_with(&self.#id, w, #lit)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;),
)
} else if is_nested(f) {
Ok(
quote!(<#ty as #bnb::__private::BitEncode>::bit_encode(&self.#id, w)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;),
)
} else if byte_array_len(f).is_some() {
Ok(quote!(#bnb::__private::write_byte_array(&self.#id, w)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;))
} else {
Ok(quote!(w.write(self.#id).map_err(|e| e.in_field(::core::stringify!(#id)))?;))
}
}
fn alignment_guard(fields: &FieldsNamed, allow: bool, magic: Option<&syn::Expr>) -> TokenStream2 {
if allow || (fields.named.is_empty() && magic.is_none()) {
return quote!();
}
let bnb = crate::bnb_path();
let mut terms: Vec<TokenStream2> = fields
.named
.iter()
.map(|f| {
let w = field_width(f);
quote!((#w % 8 == 0))
})
.collect();
if let Some(m) = magic {
terms.push(quote!((#bnb::__private::bits_of(&#m) % 8 == 0)));
}
quote! {
const _: () = {
assert!(!(true #(&& #terms)*), #BYTE_ALIGNED_MSG);
};
}
}
pub(crate) fn expand_decode(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
match decode_inner(&input) {
Ok(ts) => ts.into(),
Err(e) => e.to_compile_error().into(),
}
}
fn decode_inner(input: &DeriveInput) -> syn::Result<TokenStream2> {
gen_decode(
&input.ident,
named_struct(input)?,
&parse_bit_stream(input)?,
)
}
fn gen_decode(
name: &Ident,
fields: &FieldsNamed,
attrs: &BitStreamAttrs,
) -> syn::Result<TokenStream2> {
let bnb = crate::bnb_path();
let brs: Vec<FieldBr> = fields
.named
.iter()
.map(parse_field_br)
.collect::<syn::Result<Vec<_>>>()?;
let indeterminate = !attrs.ctx.is_empty() || brs.iter().any(br_indeterminate);
let guard = alignment_guard(
fields,
attrs.allow_byte_aligned || indeterminate,
attrs.magic.as_ref(),
);
let layout = layout_token(attrs);
let (magic_read, magic_bits) = match &attrs.magic {
Some(m) => (
quote! {
#bnb::__private::verify_magic(r, #m).map_err(|e| e.in_field("magic"))?;
},
quote!(#bnb::__private::bits_of(&#m) +),
),
None => (quote!(), quote!()),
};
let ids: Vec<&Ident> = fields
.named
.iter()
.zip(&brs)
.filter(|(_, br)| !br.temp)
.map(|(f, _)| f.ident.as_ref().expect("named field"))
.collect();
let read_stmts = fields
.named
.iter()
.zip(&brs)
.map(|(f, br)| field_read_stmt(f, br))
.collect::<syn::Result<Vec<_>>>()?;
if !attrs.ctx.is_empty() {
let ctx_name = ctx_struct_ident(name);
let ctx_binds = attrs.ctx.iter().map(|(n, _)| quote!(let #n = __ctx.#n;));
return Ok(quote! {
#guard
impl #name {
#[doc = "Decode from a bit source, given the context this type declares via `ctx(...)`."]
#[allow(unused_variables)] pub fn decode_with<S: #bnb::__private::Source>(
r: &mut S,
__ctx: #ctx_name,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#(#ctx_binds)*
#magic_read
#(#read_stmts)*
::core::result::Result::Ok(Self { #(#ids),* })
}
#[doc = "Decode from bytes with context, requiring every whole byte consumed."]
pub fn decode_with_exact(
bytes: &[u8],
__ctx: #ctx_name,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#bnb::__private::decode_exact_with(bytes, #layout, |r| Self::decode_with(r, __ctx))
}
}
impl #bnb::DecodeWith<#ctx_name> for #name {
fn decode_with<S: #bnb::__private::Source>(
r: &mut S,
args: #ctx_name,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
<#name>::decode_with(r, args)
}
}
});
}
let variable = indeterminate || fields.named.iter().any(|f| vec_elem(f).is_some());
let fixed_bit_len = if variable {
quote!()
} else {
let widths = fields.named.iter().map(field_width);
quote! {
impl #bnb::__private::FixedBitLen for #name {
const BIT_LEN: u32 = #magic_bits 0 #(+ #widths)*;
}
}
};
let seeks = brs
.iter()
.any(|br| br.restore_position || br.seek.is_some());
let from_bound = if seeks {
quote!(#bnb::__private::SeekSource)
} else {
quote!(#bnb::__private::Source)
};
let from_doc = if seeks {
"Decode from an explicit **seekable** bit source. This message uses a seeking \
directive (`restore_position`/`seek`), so a forward-only stream is rejected at \
compile time."
} else {
"Decode from an explicit bit source (a `BitReader` cursor or a streaming reader)."
};
let mut reserved_overwrites = Vec::new();
for f in &fields.named {
if let Some(v) = reserved_spec_value(f)? {
let id = f.ident.as_ref().expect("named field");
reserved_overwrites.push(quote!(__v.#id = #v;));
}
}
let spec_decode = if reserved_overwrites.is_empty() {
quote!()
} else {
quote! {
impl #name {
#[doc = "Like `decode_exact`, but reserved fields are set to their spec value instead of the bytes on the wire."]
pub fn spec_decode_exact(bytes: &[u8]) -> ::core::result::Result<Self, #bnb::__private::BitError> {
let mut __v = Self::decode_exact(bytes)?;
#(#reserved_overwrites)*
::core::result::Result::Ok(__v)
}
#[doc = "Like `peek`, but reserved fields are set to their spec value."]
pub fn spec_peek(bytes: &[u8]) -> ::core::result::Result<Self, #bnb::__private::BitError> {
let mut __v = Self::peek(bytes)?;
#(#reserved_overwrites)*
::core::result::Result::Ok(__v)
}
#[doc = "Like `decode_from`, but reserved fields are set to their spec value."]
pub fn spec_decode_from<S: #from_bound>(
r: &mut S,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
let mut __v = Self::decode_from(r)?;
#(#reserved_overwrites)*
::core::result::Result::Ok(__v)
}
}
}
};
Ok(quote! {
#guard
#fixed_bit_len
impl #bnb::BitDecode for #name {
fn bit_decode<S: #bnb::__private::Source>(
r: &mut S,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#magic_read
#(#read_stmts)*
::core::result::Result::Ok(Self { #(#ids),* })
}
}
impl #name {
#[doc = "Decode one message from the front of `buf`, advancing it past the bytes consumed (the tail stays in `buf`; transactional on error)."]
pub fn decode(buf: &mut &[u8]) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#bnb::__private::decode_consume(buf, #layout)
}
#[doc = "Decode one message from `bytes` without consuming the caller's buffer (tail-tolerant)."]
pub fn peek(bytes: &[u8]) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#bnb::__private::decode_peek(bytes, #layout)
}
#[doc = "Decode and require every whole byte consumed (errors with `ErrorKind::TrailingBytes` otherwise)."]
pub fn decode_exact(bytes: &[u8]) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#bnb::__private::decode_exact(bytes, #layout)
}
#[doc = #from_doc]
pub fn decode_from<S: #from_bound>(
r: &mut S,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
<Self as #bnb::BitDecode>::bit_decode(r)
}
}
#spec_decode
})
}
fn tokens_mention(ts: TokenStream2, names: &[&Ident]) -> bool {
ts.into_iter().any(|tt| match tt {
proc_macro2::TokenTree::Ident(id) => names.iter().any(|n| **n == id),
proc_macro2::TokenTree::Group(g) => tokens_mention(g.stream(), names),
_ => false,
})
}
pub(crate) fn expand_encode(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
match encode_inner(&input) {
Ok(ts) => ts.into(),
Err(e) => e.to_compile_error().into(),
}
}
fn encode_inner(input: &DeriveInput) -> syn::Result<TokenStream2> {
gen_encode(
&input.ident,
named_struct(input)?,
&parse_bit_stream(input)?,
)
}
fn gen_encode(
name: &Ident,
fields: &FieldsNamed,
attrs: &BitStreamAttrs,
) -> syn::Result<TokenStream2> {
let bnb = crate::bnb_path();
let brs: Vec<FieldBr> = fields
.named
.iter()
.map(parse_field_br)
.collect::<syn::Result<Vec<_>>>()?;
let indeterminate = !attrs.ctx.is_empty() || brs.iter().any(br_indeterminate);
let guard = alignment_guard(
fields,
attrs.allow_byte_aligned || indeterminate,
attrs.magic.as_ref(),
);
let layout = layout_token(attrs);
let magic_write = match &attrs.magic {
Some(m) => quote! {
#bnb::__private::Sink::write(w, #m).map_err(|e| e.in_field("magic"))?;
},
None => quote!(),
};
let field_set: Vec<&Ident> = fields
.named
.iter()
.zip(&brs)
.filter(|(_, br)| !br.temp)
.map(|(f, _)| f.ident.as_ref().expect("named field"))
.collect();
let writes = fields
.named
.iter()
.zip(&brs)
.map(|(f, br)| field_write_stmt(f, br, &field_set, false))
.collect::<syn::Result<Vec<_>>>()?;
let ctx_names: Vec<&Ident> = attrs.ctx.iter().map(|(n, _)| n).collect();
let encode_uses_ctx =
!ctx_names.is_empty() && writes.iter().any(|w| tokens_mention(w.clone(), &ctx_names));
if encode_uses_ctx {
let ctx_name = ctx_struct_ident(name);
let ctx_binds = attrs.ctx.iter().map(|(n, _)| quote!(let #n = __ctx.#n;));
return Ok(quote! {
#guard
impl #name {
#[doc = "Encode to a bit sink, given the context this type declares via `ctx(...)`."]
#[allow(unused_variables)] pub fn encode_with<K: #bnb::__private::Sink>(
&self,
w: &mut K,
__ctx: #ctx_name,
) -> ::core::result::Result<(), #bnb::__private::BitError> {
#(#ctx_binds)*
#magic_write
#(#writes)*
::core::result::Result::Ok(())
}
#[doc = "Encode to a `Vec<u8>` with context."]
pub fn to_bytes_with(
&self,
__ctx: #ctx_name,
) -> ::core::result::Result<#bnb::__private::Vec<u8>, #bnb::__private::BitError> {
#bnb::__private::encode_to_vec_with(#layout, |w| self.encode_with(w, __ctx))
}
}
impl #bnb::EncodeWith<#ctx_name> for #name {
fn encode_with<K: #bnb::__private::Sink>(
&self,
w: &mut K,
args: #ctx_name,
) -> ::core::result::Result<(), #bnb::__private::BitError> {
<#name>::encode_with(self, w, args)
}
}
});
}
let has_reserved = fields.named.iter().any(field_is_reserved);
let spec_encode = if !has_reserved {
quote!()
} else {
let writes_spec = fields
.named
.iter()
.zip(&brs)
.map(|(f, br)| field_write_stmt(f, br, &field_set, true))
.collect::<syn::Result<Vec<_>>>()?;
quote! {
impl #bnb::SpecEncode for #name {
const SPEC_LAYOUT: #bnb::Layout = #layout;
fn spec_bit_encode<K: #bnb::__private::Sink>(
&self,
w: &mut K,
) -> ::core::result::Result<(), #bnb::__private::BitError> {
#magic_write
#(#writes_spec)*
::core::result::Result::Ok(())
}
}
impl #name {
#[doc = "Encode with reserved fields written as their spec value (ignoring any stored"]
#[doc = "override), to a `Vec<u8>`. For a `std::io::Write` sink, bring"]
#[doc = "[`SpecEncodeExt`](::bnb::SpecEncodeExt) into scope and call `.spec_encode(&mut w)` (the `std` feature)."]
pub fn to_spec_bytes(&self) -> ::core::result::Result<#bnb::__private::Vec<u8>, #bnb::__private::BitError> {
#bnb::__private::encode_to_vec_with(
#layout,
|w| <Self as #bnb::SpecEncode>::spec_bit_encode(self, w),
)
}
#[doc = "Encode with reserved fields written as their spec value, into an explicit bit sink."]
pub fn spec_encode_into<K: #bnb::__private::Sink>(
&self,
w: &mut K,
) -> ::core::result::Result<(), #bnb::__private::BitError> {
<Self as #bnb::SpecEncode>::spec_bit_encode(self, w)
}
}
}
};
let encode_with_trait = if attrs.ctx.is_empty() {
quote!()
} else {
let ctx_name = ctx_struct_ident(name);
quote! {
impl #bnb::EncodeWith<#ctx_name> for #name {
#[allow(unused_variables)]
fn encode_with<K: #bnb::__private::Sink>(
&self,
w: &mut K,
args: #ctx_name,
) -> ::core::result::Result<(), #bnb::__private::BitError> {
<Self as #bnb::BitEncode>::bit_encode(self, w)
}
}
}
};
Ok(quote! {
#guard
impl #bnb::BitEncode for #name {
const LAYOUT: #bnb::Layout = #layout;
fn bit_encode<K: #bnb::__private::Sink>(
&self,
w: &mut K,
) -> ::core::result::Result<(), #bnb::__private::BitError> {
#magic_write
#(#writes)*
::core::result::Result::Ok(())
}
}
impl #name {
#[doc = "Encode to a `Vec<u8>`. To encode to a `std::io::Write` sink, bring"]
#[doc = "[`EncodeExt`](::bnb::EncodeExt) into scope and call `.encode(&mut w)` (the `std` feature)."]
pub fn to_bytes(&self) -> ::core::result::Result<#bnb::__private::Vec<u8>, #bnb::__private::BitError> {
#bnb::__private::encode_to_vec(self, #layout)
}
#[doc = "Encode into an explicit bit sink (a `BitWriter`)."]
pub fn encode_into<K: #bnb::__private::Sink>(
&self,
w: &mut K,
) -> ::core::result::Result<(), #bnb::__private::BitError> {
<Self as #bnb::BitEncode>::bit_encode(self, w)
}
}
#encode_with_trait
#spec_encode
})
}
#[derive(Default)]
struct BinArgs {
read_only: bool,
write_only: bool,
no_builder: bool,
forward_only: bool,
lsb: bool,
little: bool,
magic: Option<syn::Expr>,
ctx: Vec<(Ident, Type)>,
validate: Option<syn::Path>,
tag: Option<Ident>,
}
pub(crate) fn expand_bin(attr: TokenStream, item: TokenStream) -> TokenStream {
match bin_inner(attr, item) {
Ok(ts) => ts.into(),
Err(e) => e.to_compile_error().into(),
}
}
fn bin_inner(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream2> {
let mut args = BinArgs::default();
let parser = syn::meta::parser(|meta| {
if meta.path.is_ident("read_only") {
args.read_only = true;
} else if meta.path.is_ident("write_only") {
args.write_only = true;
} else if meta.path.is_ident("no_builder") {
args.no_builder = true;
} else if meta.path.is_ident("forward_only") {
args.forward_only = true;
} else if meta.path.is_ident("bit_order") {
let v: Ident = meta.value()?.parse()?;
match v.to_string().as_str() {
"msb" => args.lsb = false,
"lsb" => args.lsb = true,
_ => return Err(meta.error("expected `msb` or `lsb`")),
}
} else if meta.path.is_ident("big") {
args.little = false;
} else if meta.path.is_ident("little") {
args.little = true;
} else if meta.path.is_ident("magic") {
args.magic = Some(meta.value()?.parse()?);
} else if meta.path.is_ident("ctx") {
let content;
syn::parenthesized!(content in meta.input);
let params = Punctuated::<CtxParam, Token![,]>::parse_terminated(&content)?;
args.ctx = params.into_iter().map(|p| (p.name, p.ty)).collect();
} else if meta.path.is_ident("validate") {
args.validate = Some(meta.value()?.parse()?);
} else if meta.path.is_ident("tag") {
args.tag = Some(meta.value()?.parse()?);
} else {
return Err(meta.error(
"unknown `#[bin(...)]` option; expected one of: read_only, write_only, \
no_builder, forward_only, big, little, bit_order = msb|lsb, magic = <expr>, \
ctx(name: Ty, …), validate = <path>, tag = <ctx-param>",
));
}
Ok(())
});
Parser::parse(parser, attr)?;
if args.read_only && args.write_only {
return Err(syn::Error::new(
::proc_macro2::Span::call_site(),
"`read_only` and `write_only` are mutually exclusive",
));
}
match syn::parse::<syn::Item>(item)? {
syn::Item::Struct(s) => bin_struct(&args, &s),
syn::Item::Enum(e) => bin_enum(&args, &e),
other => Err(syn::Error::new_spanned(
other,
"#[bin] requires a struct or an enum",
)),
}
}
fn bin_struct(args: &BinArgs, s: &ItemStruct) -> syn::Result<TokenStream2> {
let bnb = crate::bnb_path();
if args.tag.is_some() {
return Err(syn::Error::new_spanned(
&s.ident,
"`tag` (variant dispatch) applies to a `#[bin]` enum, not a struct",
));
}
if !s.generics.params.is_empty() {
return Err(syn::Error::new_spanned(
&s.generics,
"#[bin] does not support generic parameters yet",
));
}
let full_fields = match &s.fields {
Fields::Named(n) => n,
_ => {
return Err(syn::Error::new_spanned(
&s.ident,
"#[bin] requires a struct with named fields",
));
}
};
if args.forward_only {
for f in &full_fields.named {
let Ok(br) = parse_field_br(f) else { continue };
let seeking = if br.restore_position {
Some("restore_position")
} else if br.seek.is_some() {
Some("seek = …")
} else {
None
};
if let Some(name) = seeking {
return Err(syn::Error::new_spanned(
f,
format!(
"`#[br({name})]` needs to seek, but the struct is `#[bin(forward_only)]`"
),
));
}
}
}
let attrs = BitStreamAttrs {
allow_byte_aligned: true,
lsb: args.lsb,
little: args.little,
magic: args.magic.clone(),
ctx: args.ctx.clone(),
};
let decode = if args.write_only {
quote!()
} else {
gen_decode(&s.ident, full_fields, &attrs)?
};
let encode = if args.read_only {
quote!()
} else {
gen_encode(&s.ident, full_fields, &attrs)?
};
let mut clean = s.clone();
if let Fields::Named(named) = &mut clean.fields {
named.named = named
.named
.iter()
.filter(|f| !field_is_temp(f))
.cloned()
.map(|mut f| {
f.attrs.retain(|a| !is_codec_field_attr(a));
f
})
.collect();
}
let builder = if args.read_only || args.no_builder {
if args.validate.is_some() {
return Err(syn::Error::new_spanned(
&s.ident,
"`validate` needs the builder; it is incompatible with `read_only`/`no_builder`",
));
}
quote!()
} else {
let post_build = args.validate.as_ref().map(|path| {
quote! {
(#path)(&__value)
.map_err(|__e| #bnb::BuilderError::invalid(__e.to_string()))?;
}
});
let mut bfields = Vec::new();
for f in &full_fields.named {
if field_is_temp(f) {
continue; }
let ident = f.ident.clone().expect("named field");
let ty = f.ty.clone();
let mut default = match reserved_spec_value(f)? {
Some(spec) => crate::builder::FieldDefault::DefaultExpr(syn::parse2(spec)?),
None => crate::builder::FieldDefault::Required,
};
for attr in &f.attrs {
if let Some(d) = crate::builder::parse_builder_attr(attr)? {
default = d;
}
}
bfields.push(crate::builder::BField { ident, ty, default });
}
crate::builder::generate(
&s.ident,
&s.vis,
&bfields,
crate::builder::BuildKind::Plain,
post_build.as_ref(),
)
};
let ctx_struct = if args.ctx.is_empty() {
quote!()
} else {
let ctx_name = ctx_struct_ident(&s.ident);
let vis = &s.vis;
let decls = args.ctx.iter().map(|(n, t)| quote!(#vis #n: #t));
let params = args.ctx.iter().map(|(n, t)| quote!(#n: #t));
let names = args.ctx.iter().map(|(n, _)| n);
quote! {
#[derive(Clone)]
#[doc = "Context for the matching `#[bin(ctx(...))]` type — pass it to `decode_with`."]
#vis struct #ctx_name { #(#decls),* }
impl #ctx_name {
#[doc = "Construct the context positionally, in declaration order."]
#vis fn new(#(#params),*) -> Self {
Self { #(#names),* }
}
}
}
};
Ok(quote! {
#ctx_struct
#clean
#builder
#decode
#encode
})
}
fn variant_bind_idents(fields: &Fields) -> Vec<Ident> {
fields
.iter()
.enumerate()
.map(|(i, f)| f.ident.clone().unwrap_or_else(|| format_ident!("__f{}", i)))
.collect()
}
fn variant_path_fields(
name: &Ident,
vid: &Ident,
fields: &Fields,
idents: &[Ident],
) -> TokenStream2 {
match fields {
Fields::Named(_) => quote!(#name::#vid { #(#idents),* }),
Fields::Unnamed(_) => quote!(#name::#vid( #(#idents),* )),
Fields::Unit => quote!(#name::#vid),
}
}
fn ctx_literal_variant(ctx_ty: &TokenStream2, names: &[Ident], stored: &[Ident]) -> TokenStream2 {
let inits = names.iter().map(|n| {
if stored.contains(n) {
quote!(#n: *#n)
} else {
quote!(#n)
}
});
quote!(#ctx_ty { #(#inits),* })
}
fn variant_field_write(
f: &syn::Field,
br: &FieldBr,
id: &Ident,
stored: &[Ident],
) -> syn::Result<TokenStream2> {
let bnb = crate::bnb_path();
let ty = &f.ty;
let pre = pad_write_tokens(br.align_before, br.pad_before.as_ref());
let post = pad_write_tokens(br.align_after, br.pad_after.as_ref());
if br.restore_position {
return Ok(quote!(#pre #post));
}
if let Some(calc) = &br.calc {
let core = if br.temp {
quote! {
let #id: #ty = #calc;
#bnb::__private::Sink::write(w, #id)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;
}
} else {
quote! {{
let __v: #ty = #calc;
#bnb::__private::Sink::write(w, __v)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;
}}
};
return Ok(quote!(#pre #core #post));
}
if br.temp {
return Err(syn::Error::new_spanned(
f,
"a `#[br(temp)]` variant field is not stored, so it needs `#[bw(calc = <expr>)]` to encode",
));
}
if let (Some(names), None) = (&br.ctx, vec_elem(f)) {
let child_ctx = ctx_struct_ty(ty)?;
let lit = ctx_literal_variant(&child_ctx, names, stored);
let core = quote!(<#ty as #bnb::EncodeWith<#child_ctx>>::encode_with(#id, w, #lit)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;);
return Ok(quote!(#pre #core #post));
}
let core = if br.ignore {
quote!()
} else if let Some(bw_map) = &br.bw_map {
quote!(#bnb::__private::write_mapped(w, #id, #bw_map)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;)
} else if let Some(wf) = &br.write_with {
quote!((#wf)(#id, w).map_err(|e| e.in_field(::core::stringify!(#id)))?;)
} else if br.map.is_some() || br.try_map.is_some() {
return Err(syn::Error::new_spanned(
f,
"a `#[br(map = …)]`/`#[br(try_map = …)]` variant field needs the inverse `#[bw(map = <f>)]`",
));
} else if br.parse_with.is_some() {
return Err(syn::Error::new_spanned(
f,
"a `#[br(parse_with = …)]` variant field needs the inverse `#[bw(write_with = <f>)]`",
));
} else if br.cond.is_some() {
let inner = option_elem(f).ok_or_else(|| {
syn::Error::new_spanned(f, "`#[br(if(...))]` requires an `Option<_>`")
})?;
let write_inner = if is_nested(f) {
quote!(<#inner as #bnb::__private::BitEncode>::bit_encode(__v, w)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;)
} else {
quote!(#bnb::__private::Sink::write(w, *__v)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;)
};
quote!(if let ::core::option::Option::Some(__v) = #id { #write_inner })
} else if let Some(elem) = vec_elem(f) {
let write_elem = if let Some(names) = &br.ctx {
let elem_ctx = ctx_struct_ty(elem)?;
let lit = ctx_literal_variant(&elem_ctx, names, stored);
quote!(<#elem as #bnb::EncodeWith<#elem_ctx>>::encode_with(__e, w, #lit)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;)
} else if is_nested(f) {
quote!(<#elem as #bnb::__private::BitEncode>::bit_encode(__e, w)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;)
} else {
quote!(#bnb::__private::Sink::write(w, *__e)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;)
};
quote!(for __e in #id { #write_elem })
} else if is_nested(f) {
quote!(<#ty as #bnb::__private::BitEncode>::bit_encode(#id, w)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;)
} else if byte_array_len(f).is_some() {
quote!(#bnb::__private::write_byte_array(#id, w)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;)
} else {
quote!(#bnb::__private::Sink::write(w, *#id)
.map_err(|e| e.in_field(::core::stringify!(#id)))?;)
};
Ok(quote!(#pre #core #post))
}
enum Magic {
Bytes(Vec<u8>),
Int { value: Box<syn::Expr>, width: usize },
}
fn int_type_for_width(width: usize) -> TokenStream2 {
match width {
1 => quote!(u8),
2 => quote!(u16),
4 => quote!(u32),
8 => quote!(u64),
16 => quote!(u128),
_ => unreachable!("magic int width is validated to 1/2/4/8/16"),
}
}
impl Magic {
fn byte_len(&self) -> usize {
match self {
Magic::Bytes(b) => b.len(),
Magic::Int { width, .. } => *width,
}
}
fn kind(&self) -> u8 {
match self {
Magic::Bytes(_) => 0,
Magic::Int { .. } => 1,
}
}
fn read_type(&self) -> TokenStream2 {
match self {
Magic::Bytes(b) => {
let n = b.len();
quote!([u8; #n])
}
Magic::Int { width, .. } => int_type_for_width(*width),
}
}
fn const_expr(&self) -> TokenStream2 {
match self {
Magic::Bytes(b) => {
let bytes = b.iter();
quote!([#(#bytes),*])
}
Magic::Int { value, .. } => quote!(#value),
}
}
fn read_into(&self, binding: &Ident) -> TokenStream2 {
let bnb = crate::bnb_path();
let ty = self.read_type();
match self {
Magic::Bytes(_) => quote!(
let #binding: #ty = #bnb::__private::read_byte_array(r).map_err(|e| e.in_field("magic"))?;
),
Magic::Int { .. } => quote!(
let #binding: #ty = #bnb::__private::Source::read(r).map_err(|e| e.in_field("magic"))?;
),
}
}
fn verify(&self, what: &str) -> TokenStream2 {
let bnb = crate::bnb_path();
let binding = format_ident!("__vm");
let read = self.read_into(&binding);
let expected = self.const_expr();
let msg = format!("magic mismatch ({what})");
quote! {
#read
if #binding != #expected {
return ::core::result::Result::Err(
#bnb::__private::BitError::convert(
#bnb::__private::String::from(#msg),
#bnb::__private::Source::bit_pos(r),
).in_field("magic"),
);
}
}
}
fn write_value(&self, value: &TokenStream2) -> TokenStream2 {
let bnb = crate::bnb_path();
match self {
Magic::Bytes(_) => quote!(
#bnb::__private::write_byte_array(&#value, w).map_err(|e| e.in_field("magic"))?;
),
Magic::Int { .. } => quote!(
#bnb::__private::Sink::write(w, #value).map_err(|e| e.in_field("magic"))?;
),
}
}
fn write_const(&self) -> TokenStream2 {
let c = self.const_expr();
self.write_value(&c)
}
}
fn parse_magic(expr: &syn::Expr) -> syn::Result<Magic> {
if let syn::Expr::Lit(syn::ExprLit { lit, .. }) = expr {
match lit {
syn::Lit::ByteStr(s) => return Ok(Magic::Bytes(s.value())),
syn::Lit::Byte(b) => return Ok(Magic::Bytes(vec![b.value()])),
syn::Lit::Int(li) => {
let width = match li.suffix() {
"u8" => 1usize,
"u16" => 2,
"u32" => 4,
"u64" => 8,
"u128" => 16,
"" => {
return Err(syn::Error::new_spanned(
expr,
"a `magic` integer needs a width suffix so its wire size is unambiguous, e.g. `0x01u16`",
));
}
other => {
return Err(syn::Error::new_spanned(
expr,
format!(
"`{other}` is not a valid `magic` type; use a byte-aligned unsigned integer (u8/u16/u32/u64/u128) or a byte string"
),
));
}
};
return Ok(Magic::Int {
value: Box::new(expr.clone()),
width,
});
}
_ => {}
}
}
Err(syn::Error::new_spanned(
expr,
"a `magic` must be a byte string (`b\"…\"`) or a byte-aligned unsigned integer literal (`0x01u16`)",
))
}
#[derive(PartialEq, Eq, Debug)]
enum VariantRole {
TagOnly,
TagAndMagic,
MagicOnly,
Fallback,
CatchAll,
}
struct VariantDispatch<'a> {
variant: &'a syn::Variant,
tag: Option<syn::Expr>,
magic: Option<Magic>,
catch_all: bool,
}
impl VariantDispatch<'_> {
fn role(&self) -> VariantRole {
if self.catch_all {
VariantRole::CatchAll
} else {
match (self.tag.is_some(), self.magic.is_some()) {
(true, true) => VariantRole::TagAndMagic,
(true, false) => VariantRole::TagOnly,
(false, true) => VariantRole::MagicOnly,
(false, false) => VariantRole::Fallback,
}
}
}
}
#[derive(PartialEq, Eq, Debug)]
enum MagicWidth {
None,
Uniform(usize),
Mixed,
}
struct EnumDispatch<'a> {
selector: Option<Ident>,
prefix: Option<Magic>,
variants: Vec<VariantDispatch<'a>>,
}
impl<'a> EnumDispatch<'a> {
fn parse(
e: &'a syn::ItemEnum,
selector: Option<Ident>,
prefix: Option<Magic>,
) -> syn::Result<Self> {
let mut variants = Vec::new();
let mut catch_alls = 0u32;
let mut fallbacks = 0u32;
for v in &e.variants {
let mut tag = None;
let mut magic = None;
let mut catch_all = false;
for a in &v.attrs {
if a.path().is_ident("catch_all") {
catch_all = true;
} else if a.path().is_ident("bin") {
a.parse_nested_meta(|m| {
if m.path.is_ident("tag") {
tag = Some(m.value()?.parse()?);
Ok(())
} else if m.path.is_ident("magic") {
let expr: syn::Expr = m.value()?.parse()?;
magic = Some(parse_magic(&expr)?);
Ok(())
} else {
Err(m.error(
"expected `tag = <value>` or `magic = <literal>` on a variant",
))
}
})?;
}
}
let vd = VariantDispatch {
variant: v,
tag,
magic,
catch_all,
};
match vd.role() {
VariantRole::CatchAll => catch_alls += 1,
VariantRole::Fallback => fallbacks += 1,
VariantRole::TagOnly | VariantRole::TagAndMagic if selector.is_none() => {
return Err(syn::Error::new_spanned(
&v.ident,
"a variant with `tag = …` needs the enum to declare the selector via `#[bin(tag = <ctx-param>)]`",
));
}
_ => {}
}
variants.push(vd);
}
if catch_alls > 1 {
return Err(syn::Error::new_spanned(
&e.ident,
"a `#[bin]` enum may have at most one `#[catch_all]` variant",
));
}
if fallbacks > 1 {
return Err(syn::Error::new_spanned(
&e.ident,
"a `#[bin]` enum may have at most one no-tag/no-magic fallback variant",
));
}
Ok(EnumDispatch {
selector,
prefix,
variants,
})
}
fn magic_width(&self) -> MagicWidth {
let mut width: Option<usize> = None;
let mut mixed = false;
for v in &self.variants {
if v.role() == VariantRole::MagicOnly {
let len = v.magic.as_ref().expect("MagicOnly has a magic").byte_len();
match width {
None => width = Some(len),
Some(w) if w != len => mixed = true,
_ => {}
}
}
}
match (width, mixed) {
(_, true) => MagicWidth::Mixed,
(Some(w), false) => MagicWidth::Uniform(w),
(None, false) => MagicWidth::None,
}
}
fn uniform_magic_read_type(&self) -> Option<TokenStream2> {
let mut shape: Option<(u8, usize)> = None;
let mut ty = None;
for v in &self.variants {
if v.role() == VariantRole::MagicOnly {
let m = v.magic.as_ref().expect("MagicOnly has a magic");
let s = (m.kind(), m.byte_len());
match shape {
None => {
shape = Some(s);
ty = Some(m.read_type());
}
Some(prev) if prev != s => return None,
_ => {}
}
}
}
ty
}
}
fn variant_field_codec(
name: &Ident,
v: &syn::Variant,
catch_capture: Option<&TokenStream2>,
) -> syn::Result<(Vec<TokenStream2>, TokenStream2, Vec<TokenStream2>)> {
let vid = &v.ident;
let idents = variant_bind_idents(&v.fields);
let stored_idents: Vec<Ident> = v
.fields
.iter()
.zip(&idents)
.filter(|(f, _)| !field_is_temp(f))
.map(|(_, id)| id.clone())
.collect();
let path = variant_path_fields(name, vid, &v.fields, &stored_idents);
let is_catch = catch_capture.is_some();
if is_catch && v.fields.is_empty() {
return Err(syn::Error::new_spanned(
vid,
"a `#[catch_all]` variant needs a first field to hold the captured discriminant",
));
}
let mut reads = Vec::new();
for (i, f) in v.fields.iter().enumerate() {
let id = &idents[i];
let br = parse_field_br(f)?;
if is_catch && i == 0 {
if br.temp {
return Err(syn::Error::new_spanned(
f,
"the `#[catch_all]` first field holds the captured discriminant, so it can't be `#[br(temp)]`",
));
}
let cap = catch_capture.expect("catch capture present");
reads.push(quote!(let #id = #cap;));
} else {
let mut nf = f.clone();
nf.ident = Some(id.clone());
reads.push(field_read_stmt(&nf, &br)?);
}
}
let mut writes = Vec::new();
for (i, f) in v.fields.iter().enumerate() {
if is_catch && i == 0 {
continue; }
let id = &idents[i];
let br = parse_field_br(f)?;
writes.push(variant_field_write(f, &br, id, &stored_idents)?);
}
Ok((reads, path, writes))
}
fn snake_case(ident: &Ident) -> String {
let s = ident.to_string();
let mut out = String::with_capacity(s.len() + 4);
for (i, ch) in s.char_indices() {
if ch.is_ascii_uppercase() {
if i != 0 {
out.push('_');
}
out.push(ch.to_ascii_lowercase());
} else {
out.push(ch);
}
}
out
}
fn bin_enum(args: &BinArgs, e: &syn::ItemEnum) -> syn::Result<TokenStream2> {
let bnb = crate::bnb_path();
let name = &e.ident;
let vis = &e.vis;
if !e.generics.params.is_empty() {
return Err(syn::Error::new_spanned(
&e.generics,
"#[bin] does not support generic parameters yet",
));
}
if args.validate.is_some() {
return Err(syn::Error::new_spanned(
name,
"`validate` needs the builder; a `#[bin]` enum has none",
));
}
let prefix = args.magic.as_ref().map(parse_magic).transpose()?;
let dispatch = EnumDispatch::parse(e, args.tag.clone(), prefix)?;
let has_selector = dispatch.selector.is_some();
let magic_dispatch = dispatch
.variants
.iter()
.any(|v| v.role() == VariantRole::MagicOnly);
let fallback_variant = dispatch
.variants
.iter()
.find(|v| v.role() == VariantRole::Fallback);
let catch_variant = dispatch
.variants
.iter()
.find(|v| v.role() == VariantRole::CatchAll);
let hybrid = has_selector && magic_dispatch;
if !has_selector && !magic_dispatch {
return Err(syn::Error::new_spanned(
name,
"a `#[bin]` enum dispatches on `tag = <ctx-param>` (variant `tag`s) or per-variant `magic`s; it has neither",
));
}
if fallback_variant.is_some() && catch_variant.is_some() {
return Err(syn::Error::new_spanned(
name,
"use either a typed fallback variant (no tag/magic) or a `#[catch_all]`, not both",
));
}
let mixed = magic_dispatch && dispatch.magic_width() == MagicWidth::Mixed;
let use_peek = magic_dispatch && (mixed || fallback_variant.is_some());
if use_peek
&& dispatch.variants.iter().any(|v| {
v.role() == VariantRole::MagicOnly
&& matches!(v.magic.as_ref().expect("magic-only"), Magic::Int { .. })
})
{
return Err(syn::Error::new_spanned(
name,
"variable-width / fallback magic dispatch needs byte-string magics (so an unmatched discriminant can be re-read)",
));
}
let selector_ty = dispatch
.selector
.as_ref()
.map(|sel| {
args.ctx
.iter()
.find(|(n, _)| n == sel)
.map(|(_, t)| t.clone())
.ok_or_else(|| {
syn::Error::new_spanned(
sel,
"`tag = <ctx-param>` must name a `ctx(...)` parameter",
)
})
})
.transpose()?;
let rep_magic = dispatch
.variants
.iter()
.find_map(|v| (v.role() == VariantRole::MagicOnly).then(|| v.magic.as_ref().unwrap()));
let tail_variant = fallback_variant.or(catch_variant);
let tail_capture: Option<TokenStream2> = if fallback_variant.is_some() {
None
} else if catch_variant.is_some() {
if use_peek {
None
} else if magic_dispatch {
Some(quote!(__m))
} else {
Some(quote!(__other))
}
} else {
None
};
let gen_accessor = !mixed && fallback_variant.is_none() && !hybrid;
let mut encode_arms = Vec::new();
let mut accessor_arms = Vec::new();
for v in &dispatch.variants {
let is_tail = matches!(v.role(), VariantRole::Fallback | VariantRole::CatchAll);
let cap = if is_tail { tail_capture.clone() } else { None };
let (_, pat, writes) = variant_field_codec(name, v.variant, cap.as_ref())?;
let write_disc = if v.role() == VariantRole::CatchAll && cap.is_some() && magic_dispatch {
let first = &variant_bind_idents(&v.variant.fields)[0];
match rep_magic.expect("magic dispatch").kind() {
0 => {
quote!(#bnb::__private::write_byte_array(#first, w).map_err(|e| e.in_field("magic"))?;)
}
_ => {
quote!(#bnb::__private::Sink::write(w, *#first).map_err(|e| e.in_field("magic"))?;)
}
}
} else if let Some(m) = v.magic.as_ref().filter(|_| !is_tail) {
m.write_const()
} else {
quote!()
};
encode_arms.push(quote!(#pat => { #write_disc #(#writes)* }));
if gen_accessor {
let acc = if is_tail {
let first = &variant_bind_idents(&v.variant.fields)[0];
quote!(*#first)
} else if magic_dispatch {
v.magic.as_ref().expect("magic variant").const_expr()
} else {
let tagval = v.tag.as_ref().expect("tag variant");
quote!(#tagval)
};
accessor_arms.push(quote!(#pat => #acc,));
}
}
let disc_field = if magic_dispatch { "magic" } else { "tag" };
let tail_body = if let Some(tv) = tail_variant {
let (reads, ctor, _) = variant_field_codec(name, tv.variant, tail_capture.as_ref())?;
quote! {{
#(#reads)*
::core::result::Result::Ok(#ctor)
}}
} else {
quote! {{
::core::result::Result::Err(#bnb::__private::BitError::convert(
#bnb::__private::String::from(concat!("unrecognized ", stringify!(#name), " discriminant")),
#bnb::__private::Source::bit_pos(r),
).in_field(#disc_field))
}}
};
let magic_block = if !magic_dispatch {
quote!()
} else if use_peek {
let max = dispatch
.variants
.iter()
.filter(|v| v.role() == VariantRole::MagicOnly)
.map(|v| v.magic.as_ref().unwrap().byte_len())
.max()
.expect("at least one magic-only variant");
let mut chain = tail_body.clone();
for v in dispatch.variants.iter().rev() {
if v.role() == VariantRole::MagicOnly {
let Magic::Bytes(bytes) = v.magic.as_ref().unwrap() else {
unreachable!("peek path validated to byte-string magics");
};
let len = bytes.len();
let bytes = bytes.iter();
let (reads, ctor, _) = variant_field_codec(name, v.variant, None)?;
chain = quote! {
if __peek.starts_with(&[#(#bytes),*]) {
#bnb::__private::Source::seek_to_bit(r, #bnb::__private::Source::bit_pos(r) + #len * 8)?;
#(#reads)*
::core::result::Result::Ok(#ctor)
} else #chain
};
}
}
quote! {
let __peek = #bnb::__private::peek_bytes(r, #max)?;
#chain
}
} else {
let read = rep_magic
.expect("magic dispatch has a representative magic")
.read_into(&format_ident!("__m"));
let mut chain = tail_body.clone();
for v in dispatch.variants.iter().rev() {
if v.role() == VariantRole::MagicOnly {
let c = v.magic.as_ref().unwrap().const_expr();
let (reads, ctor, _) = variant_field_codec(name, v.variant, None)?;
chain = quote! {
if __m == #c {
#(#reads)*
::core::result::Result::Ok(#ctor)
} else #chain
};
}
}
quote!(#read #chain)
};
let dispatch_decode = if has_selector {
let sel = dispatch
.selector
.as_ref()
.expect("tag dispatch has a selector");
let mut arms = Vec::new();
for v in &dispatch.variants {
if matches!(v.role(), VariantRole::TagOnly | VariantRole::TagAndMagic) {
let tagval = v.tag.as_ref().unwrap();
let verify = v
.magic
.as_ref()
.map(|m| m.verify(&v.variant.ident.to_string()));
let (reads, ctor, _) = variant_field_codec(name, v.variant, None)?;
arms.push(quote! {
#tagval => {
#verify
#(#reads)*
::core::result::Result::Ok(#ctor)
}
});
}
}
let else_body = if magic_dispatch {
quote!({ #magic_block })
} else {
tail_body.clone()
};
let catch_pat = if !magic_dispatch && tail_variant.is_some() {
quote!(__other)
} else {
quote!(_)
};
quote!(match #sel { #(#arms)* #catch_pat => #else_body })
} else {
magic_block
};
let prefix_verify = dispatch.prefix.as_ref().map(|m| m.verify("prefix"));
let prefix_write = dispatch.prefix.as_ref().map(|m| m.write_const());
let decode_body = quote! {
#prefix_verify
#dispatch_decode
};
let encode_body = quote! {
#prefix_write
match self {
#(#encode_arms)*
}
::core::result::Result::Ok(())
};
let seeks = use_peek
|| e.variants
.iter()
.flat_map(|v| &v.fields)
.any(|f| parse_field_br(f).is_ok_and(|br| br.restore_position || br.seek.is_some()));
if args.forward_only && seeks {
let reason = if use_peek {
"variable-width / fallback magic dispatch peeks (it needs to seek)"
} else {
"a seeking variant field (`seek`/`restore_position`)"
};
return Err(syn::Error::new_spanned(
name,
format!("{reason} is incompatible with `#[bin(forward_only)]`"),
));
}
let from_bound = if seeks {
quote!(#bnb::__private::SeekSource)
} else {
quote!(#bnb::__private::Source)
};
let attrs = BitStreamAttrs {
allow_byte_aligned: true,
lsb: args.lsb,
little: args.little,
magic: None,
ctx: args.ctx.clone(),
};
let layout = layout_token(&attrs);
let want_decode = !args.write_only;
let want_encode = !args.read_only;
let is_ctx_type = !args.ctx.is_empty();
let ctx_binds: Vec<TokenStream2> = args
.ctx
.iter()
.map(|(n, _)| quote!(let #n = __ctx.#n;))
.collect();
let accessor_fn = if !gen_accessor {
quote!()
} else if magic_dispatch {
let magic_ty = dispatch
.uniform_magic_read_type()
.expect("uniform magic dispatch has a read type");
quote! {
impl #name {
#[doc = "The wire magic this value encodes as."]
#[allow(unused_variables)]
pub fn magic(&self) -> #magic_ty {
match self {
#(#accessor_arms)*
}
}
}
}
} else {
let sel_ty = selector_ty
.as_ref()
.expect("tag dispatch has a selector type");
quote! {
impl #name {
#[doc = "The selector this value dispatches as (the off-wire `tag`)."]
#[allow(unused_variables)]
pub fn tag(&self) -> #sel_ty {
match self {
#(#accessor_arms)*
}
}
}
}
};
let ctx_name = ctx_struct_ident(name);
let mut helper_methods = Vec::new();
for v in &dispatch.variants {
if v.role() == VariantRole::CatchAll {
continue; }
let mname = format_ident!("decode_as_{}", snake_case(&v.variant.ident));
let (reads, ctor, _) = variant_field_codec(name, v.variant, None)?;
let magic_verify = v
.magic
.as_ref()
.map(|m| m.verify(&v.variant.ident.to_string()));
let doc = format!(
"Decode `bytes` as the `{}` variant — its `magic` (if any) then its payload, requiring every whole byte consumed.",
v.variant.ident
);
let body = quote! {
#prefix_verify
#magic_verify
#(#reads)*
::core::result::Result::Ok(#ctor)
};
if is_ctx_type {
helper_methods.push(quote! {
#[doc = #doc]
#[allow(unused_variables)]
pub fn #mname(bytes: &[u8], __ctx: #ctx_name) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#bnb::__private::decode_exact_with(bytes, #layout, |r| { #(#ctx_binds)* #body })
}
});
} else {
helper_methods.push(quote! {
#[doc = #doc]
pub fn #mname(bytes: &[u8]) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#bnb::__private::decode_exact_with(bytes, #layout, |r| { #body })
}
});
}
}
if !magic_dispatch && want_decode && args.ctx.len() == 1 {
let sel = dispatch
.selector
.as_ref()
.expect("tag dispatch has a selector");
let sel_ty = selector_ty
.as_ref()
.expect("tag dispatch has a selector type");
helper_methods.push(quote! {
#[doc = "Decode `bytes` with the given selector (tag), then dispatch — sugar for `decode_with_exact`."]
pub fn decode_tagged(#sel: #sel_ty, bytes: &[u8]) -> ::core::result::Result<Self, #bnb::__private::BitError> {
Self::decode_with_exact(bytes, #ctx_name { #sel })
}
});
}
let kind_name = format_ident!("{}Kind", name);
let kind_enum = if magic_dispatch && !has_selector && want_decode {
let tail_kind = tail_variant
.map(|tv| {
let k = &tv.variant.ident;
quote!(::core::result::Result::Ok(#kind_name::#k))
})
.unwrap_or_else(|| {
quote!(::core::result::Result::Err(
#bnb::__private::BitError::convert(
#bnb::__private::String::from(concat!(
"unrecognized ",
stringify!(#name),
" discriminant"
)),
#bnb::__private::Source::bit_pos(r),
)
.in_field("magic")
))
});
let decision = if use_peek {
let max = dispatch
.variants
.iter()
.filter(|v| v.role() == VariantRole::MagicOnly)
.map(|v| v.magic.as_ref().unwrap().byte_len())
.max()
.expect("at least one magic-only variant");
let mut chain = quote!({ #tail_kind });
for v in dispatch.variants.iter().rev() {
if v.role() == VariantRole::MagicOnly {
let Magic::Bytes(bytes) = v.magic.as_ref().unwrap() else {
unreachable!("peek path validated to byte-string magics");
};
let bytes = bytes.iter();
let k = &v.variant.ident;
chain = quote!(if __peek.starts_with(&[#(#bytes),*]) { ::core::result::Result::Ok(#kind_name::#k) } else #chain);
}
}
quote! {
let __peek = #bnb::__private::peek_bytes(r, #max)?;
#chain
}
} else {
let read = rep_magic
.expect("magic dispatch has a representative magic")
.read_into(&format_ident!("__m"));
let mut chain = quote!({ #tail_kind });
for v in dispatch.variants.iter().rev() {
if v.role() == VariantRole::MagicOnly {
let c = v.magic.as_ref().unwrap().const_expr();
let k = &v.variant.ident;
chain = quote!(if __m == #c { ::core::result::Result::Ok(#kind_name::#k) } else #chain);
}
}
quote!(#read #chain)
};
helper_methods.push(quote! {
#[doc = "Identify which variant `bytes` is from the wire magic, without parsing the payload."]
pub fn peek_variant(bytes: &[u8]) -> ::core::result::Result<#kind_name, #bnb::__private::BitError> {
#bnb::__private::decode_peek_with(bytes, #layout, |r| {
#prefix_verify
#decision
})
}
});
let kvars = dispatch.variants.iter().map(|v| &v.variant.ident);
quote! {
#[doc = "The variant kind of a value, from `peek_variant` (the dispatch decision only)."]
#[derive(::core::clone::Clone, ::core::marker::Copy, ::core::fmt::Debug, ::core::cmp::PartialEq, ::core::cmp::Eq)]
#vis enum #kind_name { #(#kvars),* }
}
} else {
quote!()
};
let helpers = if helper_methods.is_empty() {
quote!()
} else {
quote!(impl #name { #(#helper_methods)* })
};
let ctx_param_names: Vec<&Ident> = args.ctx.iter().map(|(n, _)| n).collect();
let enum_encode_uses_ctx = is_ctx_type && tokens_mention(encode_body.clone(), &ctx_param_names);
let decode = if !want_decode {
quote!()
} else if is_ctx_type {
quote! {
impl #name {
#[doc = "Decode from a bit source, given the context this type declares via `ctx(...)`."]
#[allow(unused_variables)]
pub fn decode_with<S: #from_bound>(
r: &mut S,
__ctx: #ctx_name,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#(#ctx_binds)*
#decode_body
}
#[doc = "Decode from bytes with context, requiring every whole byte consumed."]
pub fn decode_with_exact(
bytes: &[u8],
__ctx: #ctx_name,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#bnb::__private::decode_exact_with(bytes, #layout, |r| Self::decode_with(r, __ctx.clone()))
}
}
impl #bnb::DecodeWith<#ctx_name> for #name {
fn decode_with<S: #bnb::__private::Source>(
r: &mut S,
args: #ctx_name,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
<#name>::decode_with(r, args)
}
}
}
} else {
quote! {
impl #bnb::BitDecode for #name {
fn bit_decode<S: #bnb::__private::Source>(
r: &mut S,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#decode_body
}
}
impl #name {
#[doc = "Decode one message from the front of `buf`, advancing it past the bytes consumed."]
pub fn decode(buf: &mut &[u8]) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#bnb::__private::decode_consume(buf, #layout)
}
#[doc = "Decode one message from `bytes` without consuming the caller's buffer (tail-tolerant)."]
pub fn peek(bytes: &[u8]) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#bnb::__private::decode_peek(bytes, #layout)
}
#[doc = "Decode and require every whole byte consumed."]
pub fn decode_exact(bytes: &[u8]) -> ::core::result::Result<Self, #bnb::__private::BitError> {
#bnb::__private::decode_exact(bytes, #layout)
}
#[doc = "Decode from an explicit bit source (a seekable one if a variant seeks)."]
pub fn decode_from<S: #from_bound>(
r: &mut S,
) -> ::core::result::Result<Self, #bnb::__private::BitError> {
<Self as #bnb::BitDecode>::bit_decode(r)
}
}
}
};
let encode = if !want_encode {
quote!()
} else if enum_encode_uses_ctx {
quote! {
impl #name {
#[doc = "Encode to a bit sink, given the context this type declares via `ctx(...)`."]
#[allow(unused_variables)]
pub fn encode_with<K: #bnb::__private::Sink>(
&self,
w: &mut K,
__ctx: #ctx_name,
) -> ::core::result::Result<(), #bnb::__private::BitError> {
#(#ctx_binds)*
#encode_body
}
#[doc = "Encode to a `Vec<u8>` with context."]
pub fn to_bytes_with(
&self,
__ctx: #ctx_name,
) -> ::core::result::Result<#bnb::__private::Vec<u8>, #bnb::__private::BitError> {
#bnb::__private::encode_to_vec_with(#layout, |w| self.encode_with(w, __ctx.clone()))
}
}
impl #bnb::EncodeWith<#ctx_name> for #name {
fn encode_with<K: #bnb::__private::Sink>(
&self,
w: &mut K,
args: #ctx_name,
) -> ::core::result::Result<(), #bnb::__private::BitError> {
<#name>::encode_with(self, w, args)
}
}
}
} else {
let encode_with_trait = is_ctx_type.then(|| {
quote! {
impl #bnb::EncodeWith<#ctx_name> for #name {
#[allow(unused_variables)]
fn encode_with<K: #bnb::__private::Sink>(
&self,
w: &mut K,
args: #ctx_name,
) -> ::core::result::Result<(), #bnb::__private::BitError> {
<Self as #bnb::BitEncode>::bit_encode(self, w)
}
}
}
});
quote! {
impl #bnb::BitEncode for #name {
const LAYOUT: #bnb::Layout = #layout;
fn bit_encode<K: #bnb::__private::Sink>(
&self,
w: &mut K,
) -> ::core::result::Result<(), #bnb::__private::BitError> {
#encode_body
}
}
impl #name {
#[doc = "Encode to a `Vec<u8>`. To encode to a `std::io::Write` sink, bring"]
#[doc = "[`EncodeExt`](::bnb::EncodeExt) into scope and call `.encode(&mut w)` (the `std` feature)."]
pub fn to_bytes(&self) -> ::core::result::Result<#bnb::__private::Vec<u8>, #bnb::__private::BitError> {
#bnb::__private::encode_to_vec(self, #layout)
}
#[doc = "Encode into an explicit bit sink (a `BitWriter`)."]
pub fn encode_into<K: #bnb::__private::Sink>(
&self,
w: &mut K,
) -> ::core::result::Result<(), #bnb::__private::BitError> {
<Self as #bnb::BitEncode>::bit_encode(self, w)
}
}
#encode_with_trait
}
};
let ctx_struct = if is_ctx_type {
let decls = args.ctx.iter().map(|(n, t)| quote!(#vis #n: #t));
let params = args.ctx.iter().map(|(n, t)| quote!(#n: #t));
let names = args.ctx.iter().map(|(n, _)| n);
quote! {
#[derive(Clone)]
#[doc = "Context for the matching `#[bin(ctx(...))]` type — pass it to `decode_with`."]
#vis struct #ctx_name { #(#decls),* }
impl #ctx_name {
#[doc = "Construct the context positionally, in declaration order."]
#vis fn new(#(#params),*) -> Self {
Self { #(#names),* }
}
}
}
} else {
quote!()
};
let strip = |f: &syn::Field| -> Option<syn::Field> {
(!field_is_temp(f)).then(|| {
let mut f = f.clone();
f.attrs.retain(|a| !is_codec_field_attr(a));
f
})
};
let mut clean = e.clone();
for v in &mut clean.variants {
v.attrs
.retain(|a| !(a.path().is_ident("bin") || a.path().is_ident("catch_all")));
match &mut v.fields {
Fields::Named(n) => n.named = n.named.iter().filter_map(&strip).collect(),
Fields::Unnamed(u) => u.unnamed = u.unnamed.iter().filter_map(&strip).collect(),
Fields::Unit => {}
}
}
Ok(quote! {
#ctx_struct
#clean
#kind_enum
#accessor_fn
#helpers
#decode
#encode
})
}
#[cfg(test)]
mod dispatch_tests {
#![allow(clippy::needless_raw_string_hashes)]
use super::{EnumDispatch, Magic, MagicWidth, VariantRole};
fn enum_of(src: &str) -> syn::ItemEnum {
syn::parse_str(src).expect("valid enum source")
}
fn sel(name: &str) -> syn::Ident {
syn::parse_str(name).expect("valid ident")
}
#[test]
fn integer_magic_widths_inferred_from_suffix() {
let src = r#"enum E { #[bin(magic = 0xCAFEu16)] A(u32), #[bin(magic = 0x01u16)] B }"#;
let e = enum_of(src);
let d = EnumDispatch::parse(&e, None, None).unwrap();
assert_eq!(d.variants.len(), 2);
assert_eq!(d.variants[0].magic.as_ref().unwrap().byte_len(), 2);
assert_eq!(d.variants[0].role(), VariantRole::MagicOnly);
assert_eq!(d.magic_width(), MagicWidth::Uniform(2));
}
#[test]
fn byte_string_magic_widths_and_mixed_detection() {
let e = enum_of(r#"enum E { #[bin(magic = b"IHDR")] A, #[bin(magic = b"END")] B }"#);
let d = EnumDispatch::parse(&e, None, None).unwrap();
assert_eq!(d.variants[0].magic.as_ref().unwrap().byte_len(), 4);
assert_eq!(d.variants[1].magic.as_ref().unwrap().byte_len(), 3);
assert_eq!(d.magic_width(), MagicWidth::Mixed);
}
#[test]
fn tag_variant_requires_a_selector() {
let e = enum_of(r#"enum E { #[bin(tag = 1)] A(u8) }"#);
assert!(EnumDispatch::parse(&e, None, None).is_err());
let d = EnumDispatch::parse(&e, Some(sel("kind")), None).unwrap();
assert_eq!(d.variants[0].role(), VariantRole::TagOnly);
}
#[test]
fn tag_and_magic_compose_on_one_variant() {
let e = enum_of(r#"enum E { #[bin(tag = 1, magic = b"LI")] A(u32) }"#);
let d = EnumDispatch::parse(&e, Some(sel("kind")), None).unwrap();
assert_eq!(d.variants[0].role(), VariantRole::TagAndMagic);
}
#[test]
fn fallback_and_catch_all_roles() {
let e = enum_of(r#"enum E { #[bin(magic = b"X")] A, Plain(u32), #[catch_all] Other(u8) }"#);
let d = EnumDispatch::parse(&e, None, None).unwrap();
assert_eq!(d.variants[1].role(), VariantRole::Fallback);
assert_eq!(d.variants[2].role(), VariantRole::CatchAll);
}
#[test]
fn rejects_invalid_magic_values() {
let bad = [
r#"enum E { #[bin(magic = SOME_CONST)] A }"#, r#"enum E { #[bin(magic = 1)] A }"#, r#"enum E { #[bin(magic = 1usize)] A }"#, r#"enum E { #[bin(magic = u4::new(1))] A }"#, ];
for src in bad {
assert!(
EnumDispatch::parse(&enum_of(src), None, None).is_err(),
"should reject: {src}"
);
}
}
#[test]
fn rejects_two_catch_alls_and_two_fallbacks() {
let two_catch = r#"enum E { #[catch_all] A(u8), #[catch_all] B(u8) }"#;
assert!(EnumDispatch::parse(&enum_of(two_catch), None, None).is_err());
let two_fallback = r#"enum E { A(u8), B(u8) }"#;
assert!(EnumDispatch::parse(&enum_of(two_fallback), None, None).is_err());
}
#[test]
fn prefix_is_recorded() {
let e = enum_of(r#"enum E { #[bin(magic = b"X")] A }"#);
let d = EnumDispatch::parse(&e, None, Some(Magic::Bytes(b"PRE".to_vec()))).unwrap();
assert_eq!(d.prefix.as_ref().unwrap().byte_len(), 3);
}
}