dot15d4_macros/
lib.rs

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    // Get the name of the frame element.
14    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            /// Create a new [`#name`] reader/writer from a given buffer.
44            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            /// Returns `false` if the buffer is too short to contain this structure.
55            fn check_len(&self) -> bool {
56                self.buffer.as_ref().len() >= Self::size()
57            }
58
59            /// Create a new [`#name`] reader/writer from a given buffer without length checking.
60            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            /// Returns the size of this structure in bytes.
293            pub const fn size() -> usize {
294                #offset
295            }
296        }
297    });
298
299    f.into()
300}