byte_struct_derive/
lib.rs

1//! # Derive macro for Byte Struct
2//!
3//! This crate provides macros for deriving the [`ByteStruct`] trait
4//! defined in the [`byte_struct` crate](https://docs.rs/byte_struct).
5//!
6//! See [`#[derive(ByteStruct)]`](derive.ByteStruct.html) for using the macro.
7//!
8//! [`ByteStruct`]: https://docs.rs/byte_struct/*/byte_struct/trait.ByteStruct.html
9
10#![recursion_limit = "128"]
11extern crate proc_macro;
12
13use crate::proc_macro::TokenStream;
14use proc_macro2::Span;
15use quote::quote;
16
17#[derive(Clone, Copy)]
18enum Endianness {
19    Little,
20    Big,
21    Unspecified,
22}
23
24/// Derives trait [`ByteStruct`] for a data structure.
25///
26/// Requires all members to implement [`ByteStructUnspecifiedByteOrder`].
27/// This includes most primitive types and nested structures with [`ByteStruct`] derived
28/// (because [`ByteStructUnspecifiedByteOrder`] is automatically implemented for [`ByteStruct`] types)
29///
30/// Byte order attributes `#[byte_struct_le]` or `#[byte_struct_be]` can be attached to individual fields
31/// and/or the entire structure.
32///
33/// When a byte order attribute are attached to a field, it selects which byte order version
34/// of [`ByteStructUnspecifiedByteOrder`] member functions to use on the field
35/// In other words, the attribute specifies the byte order on an byte-order-unspecified type.
36/// These attributes have no effect on fields that implements [`ByteStruct`],
37/// because they always have the same byte packing method regardless of externally specified byte order.
38///
39/// When a byte order attribute is attached to the entire struct,
40/// it works as if it is attached to all fields that don't have byte order attributes.
41///
42/// If a field has no byte order attribute specified
43/// (either explicitly attached to the field or implicitly by the attribute on the entire structure),
44/// it must implement [`ByteStruct`] as well, so that its packing method is not byte-order-dependent.
45/// This is true for all `ByteStruct`-derived structures, but not for primitive types.
46///
47/// [`ByteStruct`]: https://docs.rs/byte_struct/*/byte_struct/trait.ByteStruct.html
48/// [`ByteStructUnspecifiedByteOrder`]: https://docs.rs/byte_struct/*/byte_struct/trait.ByteStructUnspecifiedByteOrder.html
49///
50/// ## Example
51/// ```ignore
52/// #[derive(ByteStruct)]
53/// #[byte_struct_le]
54/// struct Struct1 {
55///     // Packed as little-endian.
56///     a: u32,
57///
58///     // Packed as little-endian as well.
59///     // Redundant attributes doesn't hurt.
60///     #[byte_struct_le]
61///     b: i16,
62///
63///     // Packed as big-endian.
64///     // Attributes on fields override top-level attributes.
65///     #[byte_struct_be]
66///     c: u16,
67/// }
68///
69/// // This struct has no top-level byte order attribute
70/// #[derive(ByteStruct)]
71/// struct Struct2 {
72///     // Packed as big-endian.
73///     // If the attribute is missing here, it won't compile.
74///     #[byte_struct_be]
75///     d: i64,
76///
77///     // Packed as little-endian.
78///     #[byte_struct_le]
79///     e: f32,
80/// }
81///
82/// // This struct has no top-level byte order attribute either
83/// #[derive(ByteStruct)]
84/// struct Struct3 {
85///     // Nested structures don't need attributes.
86///     f: Struct1,
87///
88///     // Even if you give one, it has no effect.
89///     // The endianness of fields inside Struct2 still remain as specified above.
90///     #[byte_struct_le]
91///     g: Struct2,
92/// }
93/// ```
94#[proc_macro_derive(ByteStruct, attributes(byte_struct_le, byte_struct_be))]
95pub fn byte_struct_macro_derive(input: TokenStream) -> TokenStream {
96    byte_struct_macro_derive_impl(input, Endianness::Unspecified)
97}
98
99/// Same effect as [`#[derive(ByteStruct)] #[byte_struct_le]`](derive.ByteStruct.html)
100///
101/// But doesn't support byte order attributes on fields
102#[deprecated]
103#[proc_macro_derive(ByteStructLE)]
104pub fn byte_struct_le_macro_derive(input: TokenStream) -> TokenStream {
105    byte_struct_macro_derive_impl(input, Endianness::Little)
106}
107
108/// Same effect as [`#[derive(ByteStruct)] #[byte_struct_be]`](derive.ByteStruct.html)
109///
110/// But doesn't support byte order attributes on fields
111#[deprecated]
112#[proc_macro_derive(ByteStructBE)]
113pub fn byte_struct_be_macro_derive(input: TokenStream) -> TokenStream {
114    byte_struct_macro_derive_impl(input, Endianness::Big)
115}
116
117fn byte_struct_macro_derive_impl(input: TokenStream, endianness_input: Endianness) -> TokenStream {
118    let ast: syn::DeriveInput = syn::parse(input).unwrap();
119
120    let mut found_le = false;
121    let mut found_be = false;
122    for attr in ast.attrs {
123        let syn::Attribute{meta: syn::Meta::Path(syn::Path{segments, ..}), ..} = attr else {continue};
124        if segments.len() != 1 {
125            continue;
126        }
127        match segments[0].ident.to_string().as_str() {
128            "byte_struct_le" => found_le = true,
129            "byte_struct_be" => found_be = true,
130            _ => ()
131        };
132    }
133    if found_be && found_le {
134        panic!("Found conflicting byte_struct_le and byte_struct_be attributes");
135    }
136    let endianness = if found_le {
137        Endianness::Little
138    } else if found_be {
139        Endianness::Big
140    } else {
141        endianness_input
142    };
143
144    let name = &ast.ident;
145    if let syn::Data::Struct(syn::DataStruct{fields: syn::Fields::Named(
146        syn::FieldsNamed{named, ..}), ..}) = ast.data {
147
148        let mut ty0 = Vec::<syn::Type>::new();
149        let mut ident1 = Vec::<syn::Ident>::new();
150        let mut field_endianness = Vec::<Endianness>::new();
151        for n in named {
152            ty0.push(n.ty.clone());
153            ident1.push(n.ident.unwrap().clone());
154            let mut found_le = false;
155            let mut found_be = false;
156            for attr in n.attrs {
157                let syn::Attribute{meta: syn::Meta::Path(syn::Path{segments, ..}), ..} = attr else {continue};
158                if segments.len() != 1 {
159                    continue;
160                }
161                match segments[0].ident.to_string().as_str() {
162                    "byte_struct_le" => found_le = true,
163                    "byte_struct_be" => found_be = true,
164                    _ => ()
165                };
166            }
167            if found_be && found_le {
168                panic!("Found conflicting byte_struct_le and byte_struct_be attributes");
169            }
170            if found_be {
171                field_endianness.push(Endianness::Big);
172            } else if found_le {
173                field_endianness.push(Endianness::Little);
174            } else {
175                field_endianness.push(endianness);
176            }
177        }
178
179        let (write_bytes_fn, read_bytes_fn): (Vec<_>, Vec<_>) =
180            field_endianness.iter().map(|e| {
181                let name_str = match e {
182                    Endianness::Little => ("write_bytes_default_le", "read_bytes_default_le"),
183                    Endianness::Big => ("write_bytes_default_be", "read_bytes_default_be"),
184                    Endianness::Unspecified => ("write_bytes", "read_bytes"),
185                };
186                (syn::Ident::new(name_str.0, Span::call_site()),
187                syn::Ident::new(name_str.1, Span::call_site()))
188            }).unzip();
189
190        // quote! seems not liking using the same object twice in the content
191        let ty1 = ty0.clone();
192        let ty2 = ty0.clone();
193        let ty3 = ty0.clone();
194        let ident2 = ident1.clone();
195        let ident3 = ident1.clone();
196        let gen = quote! {
197            impl ByteStruct for #name {
198                fn write_bytes(&self, bytes: &mut [u8]) {
199                    let mut cur: usize = 0;
200                    #({
201                        let len = <#ty1>::BYTE_LEN;
202                        self.#ident1.#write_bytes_fn(&mut bytes[cur .. (cur + len)]);
203                        cur += len;
204                    })*
205                }
206                fn read_bytes(bytes: &[u8]) -> Self {
207                    let mut cur: usize = 0;
208                    #(
209                        let len = <#ty2>::BYTE_LEN;
210                        let #ident2 = <#ty3>::#read_bytes_fn(&bytes[cur .. (cur + len)]);
211                        cur += len;
212                    )*
213                    #name { #(#ident3),* }
214                }
215            }
216
217            impl ByteStructLen for #name {
218                const BYTE_LEN: usize = #(<#ty0>::BYTE_LEN)+*;
219            }
220        };
221        gen.into()
222
223    } else {
224        panic!("Only support struct with named fields!");
225    }
226}