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);