bitpattern/
lib.rs

1use proc_macro2::{TokenStream, TokenTree};
2use quote::quote;
3
4/// bitwise pattern matching and extracting.
5///
6/// # Example
7///
8///```rust
9/// use bitpattern::bitpattern;
10///
11/// let x = 0xacu8; // 10101100
12///
13/// // '0' means the bit must be 0.
14/// // '1' means the bit must be 1.
15/// // '_' can be uses as separator.
16/// assert_eq!(bitpattern!("1010_1100", x), Some(()));
17/// assert_eq!(bitpattern!("1010_0100", x), None);
18///
19/// // '?' means the bit can be 0 or 1.
20/// assert_eq!(bitpattern!("1?10_1?00", x), Some(()));
21///
22/// // Other charactors can be used for extracting.
23/// // 'a' extracts a single bit.
24/// assert_eq!(bitpattern!("1a10_1100", x), Some(0));
25/// assert_eq!(bitpattern!("10a0_1100", x), Some(1));
26///
27/// // Multi-bit extracting by continuous charactors.
28/// assert_eq!(bitpattern!("1aaa_a100", x), Some(5));
29///
30/// // Multiple extracting.
31/// assert_eq!(bitpattern!("1aa0_aa00", x), Some((1, 3)));
32///
33/// // If the extracting fields are adjacent, the different charactors can be used.
34/// assert_eq!(bitpattern!("1aab_bccc", x), Some((1, 1, 4)));
35///```
36#[proc_macro]
37pub fn bitpattern(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
38    let input: TokenStream = input.into();
39
40    let mut input = input.into_iter();
41    let pattern = input.next().expect("too less arguments");
42    let comma = input.next().expect("too less arguments");
43    let expr: TokenStream = input.collect();
44
45    let pattern = match pattern {
46        TokenTree::Literal(x) => x.to_string(),
47        _ => {
48            panic!("1st argument must be string literal");
49        }
50    };
51    let pattern = if pattern.starts_with('\"') & pattern.ends_with('\"') {
52        String::from(&pattern[1..pattern.len() - 1]).replace("_", "")
53    } else {
54        panic!("1st argument must be string literal");
55    };
56
57    match comma {
58        TokenTree::Punct(x) => {
59            if x.as_char() != ',' {
60                panic!("',' is required");
61            }
62        }
63        _ => {
64            panic!("',' is required");
65        }
66    }
67
68    match pattern.len() {
69        1..=8 => gen_code_u8(pattern, expr),
70        9..=16 => gen_code_u16(pattern, expr),
71        17..=32 => gen_code_u32(pattern, expr),
72        33..=64 => gen_code_u64(pattern, expr),
73        65..=128 => gen_code_u128(pattern, expr),
74        _ => {
75            panic!("unsupported pattern length: {}", pattern.len());
76        }
77    }
78}
79
80macro_rules! gen_code {
81    ($x:ty) => {
82        paste::item! {
83            fn [<gen_code_$x>](pattern: String, expr: TokenStream) -> proc_macro::TokenStream {
84                let mut bit_mask: $x = 0;
85                let mut bit_pattern: $x = 0;
86
87                let mut args_pos = Vec::new();
88                let mut args_mask = Vec::new();
89
90                let mut prev = None;
91                let mut count = 0;
92
93                for (i, bit) in pattern.chars().enumerate() {
94                    bit_mask <<= 1;
95                    bit_pattern <<= 1;
96                    match bit {
97                        '0' => {
98                            bit_mask |= 1;
99                            bit_pattern |= 0;
100                        }
101                        '1' => {
102                            bit_mask |= 1;
103                            bit_pattern |= 1;
104                        }
105                        '_' => {
106                            bit_mask |= 0;
107                            bit_pattern |= 0;
108                        }
109                        _ => {
110                            bit_mask |= 0;
111                            bit_pattern |= 0;
112                        }
113                    }
114                    if let Some(x) = prev {
115                        if x != bit && x != '0' && x != '1' && x != '?' {
116                            let pos = (pattern.len() - i) as $x;
117                            let mut mask: $x = 0;
118                            for _ in 0..count {
119                                mask <<= 1;
120                                mask |= 1;
121                            }
122                            args_pos.push(pos);
123                            args_mask.push(mask);
124
125                            count = 0;
126                        } else if x != bit {
127                            count = 0;
128                        }
129                    }
130                    count += 1;
131                    prev = Some(bit);
132                }
133                if let Some(x) = prev {
134                    if x != '0' && x != '1' && x != '?' {
135                        let pos = 0 as $x;
136                        let mut mask: $x = 0;
137                        for _ in 0..count {
138                            mask <<= 1;
139                            mask |= 1;
140                        }
141                        args_pos.push(pos);
142                        args_mask.push(mask);
143                    }
144                }
145
146                let gen = quote! {
147                    {
148                        let value = (#expr) as $x;
149                        if value & #bit_mask == #bit_pattern {
150                            Some((
151                                    #(
152                                        (value >> #args_pos) & #args_mask
153                                    ),*
154                                ))
155                        } else {
156                            None
157                        }
158                    }
159                };
160
161                gen.into()
162            }
163        }
164    };
165}
166
167gen_code!(u8);
168gen_code!(u16);
169gen_code!(u32);
170gen_code!(u64);
171gen_code!(u128);