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}