rbitpack/
lib.rs

1extern crate proc_macro;
2extern crate quote;
3extern crate syn;
4use proc_macro::TokenStream;
5use quote::{quote, ToTokens};
6use syn::{parse_macro_input, Attribute, Data, DeriveInput, Field, Lit, Meta, NestedMeta, Type};
7fn get_attribute_value<T>(attrs: &[Attribute], key: &str) -> Option<T>
8where
9    T: syn::parse::Parse,
10{
11    for attr in attrs {
12        // Parse the attribute to Meta
13        let meta = attr.parse_meta().ok()?;
14
15        // Check if the attribute is a list
16        let Meta::List(meta_list) = meta else {
17            continue;
18        };
19
20        // Check if the attribute is `rbitpack`
21        if !meta_list.path.is_ident("rbitpack") {
22            continue;
23        }
24
25        // Find the specified key-value pair
26        for nested_meta in meta_list.nested.iter() {
27            if let NestedMeta::Meta(Meta::NameValue(name_value)) = nested_meta {
28                if name_value.path.is_ident(key) {
29                    return syn::parse2(name_value.lit.to_token_stream()).ok();
30                }
31            }
32        }
33    }
34
35    None
36}
37
38fn get_packing_type(attrs: &[Attribute]) -> Option<String> {
39    get_attribute_value::<Lit>(attrs, "size").and_then(|lit| match lit {
40        Lit::Str(lit_str) => Some(lit_str.value()),
41        _ => None,
42    })
43}
44
45fn get_overflow_type(attrs: &[Attribute]) -> Option<bool> {
46    get_attribute_value::<Lit>(attrs, "overflow").and_then(|lit| match lit {
47        Lit::Bool(lit_bool) => Some(lit_bool.value),
48        _ => None,
49    })
50}
51
52/// Macro to derive bitwise packing and unpacking methods for a struct with boolean fields.
53///
54/// # Attributes
55///
56/// - `rbitpack(size = "i32", overflow = true)`: Configures the packing options.
57///     - `size`: Specifies the type of integer to use for packing (`"i8"`, `"i16"`, `"i32"`, `"i64"`, or `"auto"`).
58///     - `overflow`: A boolean indicating whether to allow packing more boolean fields than the bit capacity of the chosen integer type (default is `false`).
59///
60/// # Example
61///
62/// ```rust
63/// use rbitpack::BitwisePackable;
64///
65/// #[derive(BitwisePackable)]
66/// #[rbitpack(size = "i32", overflow = true)]
67/// struct MyStruct {
68///     field1: bool,
69///     field2: bool,
70///     field3: bool,
71/// }
72/// ```
73#[proc_macro_derive(BitwisePackable, attributes(rbitpack))]
74pub fn bitwise_packable(input: TokenStream) -> TokenStream {
75    let input = parse_macro_input!(input as DeriveInput);
76    let name = &input.ident;
77    let data = match &input.data {
78        Data::Struct(data) => data,
79        _ => panic!("BitwisePackable can only be used with structs"),
80    };
81
82    // Collect boolean fields
83    let fields: Vec<&Field> = data
84        .fields
85        .iter()
86        .filter(
87            |f| matches!(&f.ty, Type::Path(syn::TypePath { path, .. }) if path.is_ident("bool")),
88        )
89        .collect();
90
91    let num_fields = fields.len();
92    let field_names: Vec<_> = fields
93        .iter()
94        .map(|f| f.ident.as_ref().unwrap())
95        .collect::<Vec<_>>();
96
97    let fields_idx: Vec<usize> = (0..num_fields).collect();
98
99    let attrs = get_packing_type(&input.attrs);
100    let overflow = get_overflow_type(&input.attrs).unwrap_or(false);
101    let size = attrs.unwrap_or_else(|| "auto".to_string());
102
103    let (pack_code, unpack_code) = match size.as_str() {
104        "i8" => (
105            quote! {
106                impl #name {
107                    /// Packs the boolean fields of the struct into an 8-bit unsigned integer (u8).
108                    /// This method sets each bit in the resulting u8 to represent each boolean field.
109                    /// If the struct has more than 8 boolean fields, and overflow is not allowed, it will panic.
110                    ///
111                    /// # Returns
112                    /// - A `u8` where each bit represents the state of a boolean field in the struct.
113                    pub fn pack(&self) -> u8 {
114                        let mut result = 0u8;
115                        let mut bit_index = 0;
116                        let max_bits = 8;
117
118                        // Single overflow check
119                        if #num_fields > max_bits && !#overflow {
120                            panic!(
121                                "Overflow occurred during packing: struct '{}' has more boolean fields than can be packed in an u8 (8 bits).",
122                                stringify!(#name)
123                            );
124                        }
125
126                        #(
127                            if bit_index < max_bits {
128                                result |= (self.#field_names as u8) << bit_index;
129                                bit_index += 1;
130                            } // No additional else condition needed
131                        )*
132                        result
133                    }
134
135                    /// Unpacks an 8-bit unsigned integer (u8) into the boolean fields of the struct.
136                    /// This method reads each bit from the given u8 and assigns it to the corresponding boolean field.
137                    /// If the struct has more than 8 boolean fields, and overflow is not allowed, it will panic.
138                    ///
139                    /// # Parameters
140                    /// - `packed`: A `u8` where each bit represents the state of a boolean field to be unpacked.
141                    ///
142                    /// # Returns
143                    /// - A new instance of the struct with its boolean fields set according to the bits in `packed`.
144                    pub fn unpack(packed: u8) -> Self {
145                        let mut bit_index = 0;
146
147                        // Overflow check
148                        if #num_fields > 8 && !#overflow {
149                            panic!(
150                                "Overflow occurred during unpacking: struct '{}' has more boolean fields than can be unpacked from an u8 (8 bits).",
151                                stringify!(#name)
152                            );
153                        }
154
155                        #(
156                            let #field_names = if bit_index < 8 {
157                                (packed & (1 << bit_index)) != 0
158                            } else {
159                                false
160                            };
161                            bit_index += 1;
162                        )*
163                        Self {
164                            #(#field_names),*
165                        }
166                    }
167                }
168            },
169            quote! {},
170        ),
171
172        "i16" => (
173            quote! {
174                impl #name {
175                    /// Packs the boolean fields of the struct into a 16-bit unsigned integer (u16).
176                    /// This method sets each bit in the resulting u16 to represent each boolean field.
177                    /// If the struct has more than 16 boolean fields, and overflow is not allowed, it will panic.
178                    ///
179                    /// # Returns
180                    /// - A `u16` where each bit represents the state of a boolean field in the struct.
181                    pub fn pack(&self) -> u16 {
182                        let mut result = 0u16;
183                        let mut bit_index = 0;
184                        let max_bits = 16;
185
186                        // Single overflow check
187                        if #num_fields > max_bits && !#overflow {
188                            panic!(
189                                "Overflow occurred during packing: struct '{}' has more boolean fields than can be packed in an u16 (16 bits).",
190                                stringify!(#name)
191                            );
192                        }
193
194                        #(
195                            if bit_index < max_bits {
196                                result |= (self.#field_names as u16) << bit_index;
197                                bit_index += 1;
198                            } // No additional else condition needed
199                        )*
200                        result
201                    }
202
203                    /// Unpacks a 16-bit unsigned integer (u16) into the boolean fields of the struct.
204                    /// This method reads each bit from the given u16 and assigns it to the corresponding boolean field.
205                    /// If the struct has more than 16 boolean fields, and overflow is not allowed, it will panic.
206                    ///
207                    /// # Parameters
208                    /// - `packed`: A `u16` where each bit represents the state of a boolean field to be unpacked.
209                    ///
210                    /// # Returns
211                    /// - A new instance of the struct with its boolean fields set according to the bits in `packed`.
212                    pub fn unpack(packed: u16) -> Self {
213                        let mut bit_index = 0;
214
215                        // Overflow check
216                        if #num_fields > 16 && !#overflow {
217                            panic!(
218                                "Overflow occurred during unpacking: struct '{}' has more boolean fields than can be unpacked from an u16 (16 bits).",
219                                stringify!(#name)
220                            );
221                        }
222
223                        #(
224                            let #field_names = if bit_index < 16 {
225                                (packed & (1 << bit_index)) != 0
226                            } else {
227                                false
228                            };
229                            bit_index += 1;
230                        )*
231                        Self {
232                            #(#field_names),*
233                        }
234                    }
235
236                }
237            },
238            quote! {},
239        ),
240
241        "i32" => (
242            quote! {
243                impl #name {
244                    /// Packs the boolean fields of the struct into a 32-bit unsigned integer (u32).
245                    /// This method sets each bit in the resulting u32 to represent each boolean field.
246                    /// If the struct has more than 32 boolean fields, and overflow is not allowed, it will panic.
247                    ///
248                    /// # Returns
249                    /// - A `u32` where each bit represents the state of a boolean field in the struct.
250                    pub fn pack(&self) -> u32 {
251                        let mut result = 0u32;
252                        let mut bit_index = 0;
253                        let max_bits = 32;
254
255                        // Single overflow check
256                        if #num_fields > max_bits && !#overflow {
257                            panic!(
258                                "Overflow occurred during packing: struct '{}' has more boolean fields than can be packed in an u32 (32 bits).",
259                                stringify!(#name)
260                            );
261                        }
262
263                        #(
264                            if bit_index < max_bits {
265                                result |= (self.#field_names as u32) << bit_index;
266                                bit_index += 1;
267                            } // No additional else condition needed
268                        )*
269                        result
270                    }
271
272                    /// Unpacks a 32-bit unsigned integer (u32) into the boolean fields of the struct.
273                    /// This method reads each bit from the given u32 and assigns it to the corresponding boolean field.
274                    /// If the struct has more than 32 boolean fields, and overflow is not allowed, it will panic.
275                    ///
276                    /// # Parameters
277                    /// - `packed`: A `u32` where each bit represents the state of a boolean field to be unpacked.
278                    ///
279                    /// # Returns
280                    /// - A new instance of the struct with its boolean fields set according to the bits in `packed`.
281                    pub fn unpack(packed: u32) -> Self {
282                        let mut bit_index = 0;
283
284                        // Overflow check
285                        if #num_fields > 32 && !#overflow {
286                            panic!(
287                                "Overflow occurred during unpacking: struct '{}' has more boolean fields than can be unpacked from an u32 (32 bits).",
288                                stringify!(#name)
289                            );
290                        }
291
292                        #(
293                            let #field_names = if bit_index < 32 {
294                                (packed & (1 << bit_index)) != 0
295                            } else {
296                                false
297                            };
298                            bit_index += 1;
299                        )*
300                        Self {
301                            #(#field_names),*
302                        }
303                    }
304
305                }
306            },
307            quote! {},
308        ),
309
310        "i64" => (
311            quote! {
312                impl #name {
313                    /// Packs the boolean fields of the struct into a 64-bit unsigned integer (u64).
314                    /// This method sets each bit in the resulting u64 to represent each boolean field.
315                    /// If the struct has more than 64 boolean fields, and overflow is not allowed, it will panic.
316                    ///
317                    /// # Returns
318                    /// - A `u64` where each bit represents the state of a boolean field in the struct.
319                    pub fn pack(&self) -> u64 {
320                        let mut result = 0u64;
321                        let mut bit_index = 0;
322                        let max_bits = 64;
323
324                        // Single overflow check
325                        if #num_fields > max_bits && !#overflow {
326                            panic!(
327                                "Overflow occurred during packing: struct '{}' has more boolean fields than can be packed in an u64 (64 bits).",
328                                stringify!(#name)
329                            );
330                        }
331
332                        #(
333                            if bit_index < max_bits {
334                                result |= (self.#field_names as u64) << bit_index;
335                                bit_index += 1;
336                            } // No additional else condition needed
337                        )*
338                        result
339                    }
340
341                    /// Unpacks a 64-bit unsigned integer (u64) into the boolean fields of the struct.
342                    /// This method reads each bit from the given u64 and assigns it to the corresponding boolean field.
343                    /// If the struct has more than 64 boolean fields, and overflow is not allowed, it will panic.
344                    ///
345                    /// # Parameters
346                    /// - `packed`: A `u64` where each bit represents the state of a boolean field to be unpacked.
347                    ///
348                    /// # Returns
349                    /// - A new instance of the struct with its boolean fields set according to the bits in `packed`.
350                    pub fn unpack(packed: u64) -> Self {
351                        let mut bit_index = 0;
352
353                        // Overflow check
354                        if #num_fields > 64 && !#overflow {
355                            panic!(
356                                "Overflow occurred during unpacking: struct '{}' has more boolean fields than can be unpacked from an u64 (64 bits).",
357                                stringify!(#name)
358                            );
359                        }
360
361                        #(
362                            let #field_names = if bit_index < 64 {
363                                (packed & (1 << bit_index)) != 0
364                            } else {
365                                false
366                            };
367                            bit_index += 1;
368                        )*
369                        Self {
370                            #(#field_names),*
371                        }
372                    }
373
374                }
375            },
376            quote! {},
377        ),
378
379        _ => (
380            quote! {
381                impl #name {
382                    /// Packs the boolean fields of the struct into a vector of 64-bit unsigned integers (Vec<u64>).
383                    /// This method sets each bit in the resulting vector to represent each boolean field.
384                    /// The size of the vector is determined by the number of boolean fields divided by 64, rounded up.
385                    /// If overflow is not allowed, it will panic if the struct has more boolean fields than can be packed in the vector.
386                    ///
387                    /// # Returns
388                    /// - A `Vec<u64>` where each bit represents the state of a boolean field in the struct.
389                    pub fn pack(&self) -> Vec<u64> {
390                        let num_fields = #num_fields;
391                        let mut bitfield = Bitfield::new(num_fields);
392
393                        // Single overflow check
394                        if num_fields > bitfield.parts.len() * 64 && !#overflow {
395                            panic!(
396                                "Overflow occurred during packing: struct '{}' has more boolean fields than can be packed in the provided Bitfield size.",
397                                stringify!(#name)
398                            );
399                        }
400
401                        let mut bit_index = 0;
402                        #(
403                            if bit_index < num_fields {
404                                bitfield.set(bit_index, self.#field_names);
405                                bit_index += 1;
406                            }
407                        )*
408
409                        bitfield.parts
410                    }
411
412                    /// Unpacks a vector of 64-bit unsigned integers (Vec<u64>) into the boolean fields of the struct.
413                    /// This method reads each bit from the given vector and assigns it to the corresponding boolean field.
414                    /// If overflow is not allowed, it will panic if the struct has more boolean fields than can be unpacked from the vector.
415                    ///
416                    /// # Parameters
417                    /// - `packed`: A `Vec<u64>` where each bit represents the state of a boolean field to be unpacked.
418                    ///
419                    /// # Returns
420                    /// - A new instance of the struct with its boolean fields set according to the bits in `packed`.
421                    pub fn unpack(packed: Vec<u64>) -> Self {
422                        let num_fields = #num_fields;
423                        let bitfield = Bitfield {
424                            parts: packed,
425                        };
426
427                        // Overflow check
428                        if num_fields > bitfield.parts.len() * 64 && !#overflow {
429                            panic!(
430                                "Overflow occurred during unpacking: struct '{}' has more boolean fields than can be unpacked from the provided Bitfield size.",
431                                stringify!(#name)
432                            );
433                        }
434
435                        let mut booleans = vec![false; num_fields];
436                        for i in 0..num_fields {
437                            booleans[i] = bitfield.get(i);
438                        }
439
440                        Self {
441                            #(
442                                #field_names: booleans[#fields_idx],
443                            )*
444                        }
445                    }
446                }
447            },
448            quote! {},
449        ),
450    };
451
452    let expanded = quote! {
453        #pack_code
454        #unpack_code
455    };
456
457    TokenStream::from(expanded)
458}