1use proc_macro::TokenStream;
2use quote::{quote, ToTokens};
3use syn::{parse::Parser, parse_macro_input, punctuated::Punctuated, ItemStruct, Path, Token};
4
5#[proc_macro_attribute]
6pub fn frame(attr: TokenStream, item: TokenStream) -> TokenStream {
7 let args = Punctuated::<Path, Token![,]>::parse_terminated
8 .parse(attr)
9 .unwrap();
10
11 let skip_constructor = args.iter().any(|arg| arg.is_ident("no_constructor"));
12
13 let input = parse_macro_input!(item as ItemStruct);
15
16 let item_attr = input.attrs;
17 let name = input.ident;
18
19 let fields = input
20 .fields
21 .iter()
22 .filter(|field| field.attrs.iter().any(|attr| attr.path().is_ident("field")))
23 .map(|field| {
24 let ident = field.ident.as_ref().unwrap();
25 let ty = &field.ty;
26 quote! {
27 #ident: #ty
28 }
29 });
30
31 let mut f = quote! {
32 #(#item_attr)*
33 pub struct #name<T: AsRef<[u8]>> {
34 buffer: T,
35 #(#fields),*
36 }
37 };
38
39 let mut impls = vec![];
40
41 if !skip_constructor {
42 impls.push(quote! {
43 pub fn new(buffer: T) -> Result<Self> {
45 let s = Self::new_unchecked(buffer);
46
47 if !s.check_len() {
48 return Err(Error);
49 }
50
51 Ok(s)
52 }
53
54 fn check_len(&self) -> bool {
56 self.buffer.as_ref().len() >= Self::size()
57 }
58
59 pub fn new_unchecked(buffer: T) -> Self {
61 Self { buffer }
62 }
63 });
64 }
65
66 let mut offset = 0;
67 let mut bits_offset = 0;
68
69 for field in input.fields {
70 let fnname = field.ident.unwrap();
71 let ty = field.ty;
72
73 let doc = field.attrs.iter().find(|attr| attr.path().is_ident("doc"));
74
75 if field.attrs.iter().any(|attr| attr.path().is_ident("field")) {
76 impls.push(quote! {
77 #doc
78 pub fn #fnname(&self) -> #ty {
79 self.#fnname
80 }
81 });
82 continue;
83 }
84
85 let condition = field
86 .attrs
87 .iter()
88 .find(|attr| attr.path().is_ident("condition"))
89 .map(|attr| attr.parse_args::<syn::Expr>().unwrap());
90
91 let into = field
92 .attrs
93 .iter()
94 .find(|attr| attr.path().is_ident("into"))
95 .map(|attr| attr.parse_args::<syn::Expr>().unwrap());
96
97 let bytes = field
98 .attrs
99 .iter()
100 .find(|attr| attr.path().is_ident("bytes"))
101 .map(|attr| {
102 attr.parse_args::<syn::LitInt>()
103 .unwrap()
104 .base10_parse::<usize>()
105 .unwrap()
106 });
107
108 let bytes = if bytes.is_none() {
109 match ty.to_token_stream().to_string().as_str() {
110 "bool" => Some(1),
111 "u8" => Some(1),
112 "u16" => Some(2),
113 "i16" => Some(2),
114 "u32" => Some(4),
115 "i32" => Some(4),
116 "u64" => Some(8),
117 _ => None,
118 }
119 } else {
120 bytes
121 };
122
123 let bits = field
124 .attrs
125 .iter()
126 .find(|attr| attr.path().is_ident("bits"))
127 .map(|attr| {
128 attr.parse_args::<syn::LitInt>()
129 .unwrap()
130 .base10_parse::<usize>()
131 .unwrap()
132 });
133
134 if !fnname.to_string().contains("reserved") {
135 let getter = match ty.to_token_stream().to_string().as_str() {
136 "bool" => quote! {
137 let buffer = &self.buffer.as_ref()[#offset..];
138 ((buffer[0] >> #bits_offset) & 0b1) != 0
139 },
140 "u8" => {
141 if let Some(bits) = bits {
142 quote! {
143 let buffer = &self.buffer.as_ref()[#offset..];
144 (buffer[0] >> #bits_offset) & ((1 << #bits) - 1)
145 }
146 } else {
147 quote! {
148 self.buffer.as_ref()[#offset..][0]
149 }
150 }
151 }
152 "u16" => {
153 quote! {
154 let buffer = &self.buffer.as_ref()[#offset..];
155 u16::from_le_bytes([buffer[0], buffer[1]])
156 }
157 }
158 "i16" => {
159 quote! {
160 let buffer = &self.buffer.as_ref()[#offset..];
161 i16::from_le_bytes([buffer[0], buffer[1]])
162 }
163 }
164 "u32" => {
165 if bytes == Some(3) {
166 quote! {
167 let buffer = &self.buffer.as_ref()[#offset..];
168 u32::from_le_bytes([0, buffer[0], buffer[1], buffer[2]])
169 }
170 } else {
171 quote! {
172 let buffer = &self.buffer.as_ref()[#offset..];
173 u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]])
174 }
175 }
176 }
177 "i32" => {
178 quote! {
179 let buffer = &self.buffer.as_ref()[#offset..];
180 i32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]])
181 }
182 }
183 "u64" => {
184 quote! {
185 let buffer = &self.buffer.as_ref()[#offset..];
186 u64::from_le_bytes([
187 buffer[0],
188 buffer[1],
189 buffer[2],
190 buffer[3],
191 buffer[4],
192 buffer[5],
193 buffer[6],
194 buffer[7],
195 ])
196 }
197 }
198 "& [u8]" => {
199 if bytes == Some(0) {
200 quote! {
201 &self.buffer.as_ref()[#offset..]
202 }
203 } else {
204 quote! {
205 &self.buffer.as_ref()[#offset..][..#bytes]
206 }
207 }
208 }
209 _ => {
210 quote! {
211 #ty::new(&self.buffer.as_ref()[#offset..][..#ty::<&[u8]>::size()])
212 }
213 }
214 };
215
216 let getter = if let Some(ref condition) = condition {
217 quote! {
218 if #condition {
219 Some({
220 #getter
221 })
222 } else {
223 None
224 }
225 }
226 } else {
227 getter
228 };
229
230 let getter = if let Some(ref into) = into {
231 quote! {
232 #into::from({
233 #getter
234 })
235 }
236 } else {
237 getter
238 };
239
240 let return_type = match ty.to_token_stream().to_string().as_str() {
241 "bool" | "u8" | "u16" | "u32" | "u64" | "& [u8]" if into.is_some() => {
242 let into = into.unwrap();
243 quote! { #into }
244 }
245 "bool" | "u8" | "u16" | "u32" | "u64" | "& [u8]" => quote! { #ty },
246 _ => quote! { Result<#ty<&[u8]>> },
247 };
248
249 if condition.is_some() {
250 impls.push(quote! {
251 #doc
252 pub fn #fnname(&self) -> Option<#return_type> {
253 #getter
254 }
255 });
256 } else {
257 impls.push(quote! {
258 #doc
259 pub fn #fnname(&self) -> #return_type {
260 #getter
261 }
262 });
263 }
264 }
265
266 for attr in field.attrs {
267 if attr.path().is_ident("bytes") {
268 offset += attr
269 .parse_args::<syn::LitInt>()
270 .unwrap()
271 .base10_parse::<usize>()
272 .unwrap();
273 } else if attr.path().is_ident("bits") {
274 bits_offset += attr
275 .parse_args::<syn::LitInt>()
276 .unwrap()
277 .base10_parse::<usize>()
278 .unwrap();
279
280 if bits_offset % 8 == 0 && bits_offset > 0 {
281 offset += 1;
282 bits_offset = 0;
283 }
284 }
285 }
286 }
287
288 f.extend(quote! {
289 impl<T: AsRef<[u8]>> #name<T> {
290 #(#impls)*
291
292 pub const fn size() -> usize {
294 #offset
295 }
296 }
297 });
298
299 f.into()
300}