irox_structs_derive/
lib.rs1mod shared;
6
7use proc_macro::{Literal, TokenStream};
8
9use quote::{quote, ToTokens};
10use syn::{parse_macro_input, Data, DeriveInput, Fields, FieldsNamed};
11
12use irox_types::{PrimitiveType, Primitives, VariableType};
13
14use irox_derive_helpers::DeriveMethods;
15
16const TYPES_STRICT_SIZING_INCOMPATIBLE: [Primitives; 1] = [Primitives::null];
17
18struct Config {
19 strict_sizing: bool,
20 big_endian: bool,
21}
22
23fn get_endian_method_for_prim(ty: Primitives, read: bool, big_endian: bool) -> String {
24 let rw = if read { "read" } else { "write" };
25 let be = if big_endian { "be" } else { "le" };
26 let base = match ty {
27 Primitives::u8 | Primitives::bool | Primitives::char => "u8".to_string(),
28 Primitives::i8 => "i8".to_string(),
29
30 _ => {
31 format!("{be}_{ty:?}")
32 }
33 };
34 format!("{rw}_{base}")
35}
36
37fn get_endian_method_for_varbl(ty: VariableType, read: bool, big_endian: bool) -> String {
38 let rw = if read { "read" } else { "write" };
39 let be = if big_endian { "be" } else { "le" };
40 let base = match ty {
41 VariableType::str => "str_u32_blob".to_string(),
42 _ => {
43 format!("{be}_{ty:?}")
44 }
45 };
46 format!("{rw}_{base}")
47}
48
49fn create_write_to_fn(n: &FieldsNamed, config: &Config, sizing: &mut StructSizing) -> TokenStream {
50 let mut ts = TokenStream::new();
51 ts.extend::<TokenStream>(
52 quote!(
53 fn write_to<T: irox_structs::MutBits>(&self, out: &mut T) -> Result<(), irox_structs::Error>
54 )
55 .into(),
56 );
57
58 let mut method = TokenStream::new();
59
60 for x in &n.named {
61 let Some(ident) = &x.ident else {
62 return irox_derive_helpers::compile_error(&x, "No ident");
63 };
64 match PrimitiveType::try_from(x) {
65 Ok(field) => {
66 if let Some(size) = field.bytes_length() {
67 sizing.size += size;
68 }
69 match field {
70 PrimitiveType::Primitive(input) => {
71 if config.strict_sizing && TYPES_STRICT_SIZING_INCOMPATIBLE.contains(&input)
72 {
73 return irox_derive_helpers::compile_error(
74 &x,
75 "Type is not compatible with strict sizing",
76 );
77 };
78 method.add_ident("out");
79 method.add_punc('.');
80 method.add_ident(&get_endian_method_for_prim(
81 input,
82 false,
83 config.big_endian,
84 ));
85 method.add_parens({
86 let mut ts = TokenStream::new();
87 ts.add_ident("self");
88 ts.add_punc('.');
89 ts.add_ident(&ident.to_string());
90 ts
91 });
92 method.add_punc('?');
93 method.add_punc(';');
94 }
95 PrimitiveType::Array(input, len) => {
96 if config.strict_sizing && TYPES_STRICT_SIZING_INCOMPATIBLE.contains(&input)
97 {
98 return irox_derive_helpers::compile_error(
99 &x,
100 "Type is not compatible with strict sizing",
101 );
102 };
103 method.add_ident("for");
104 method.add_ident("elem");
105 method.add_ident("in");
106 method.add_ident("self");
107 method.add_punc('.');
108 method.add_ident(&ident.to_string());
109 method.wrap_braces({
110 let mut ts = TokenStream::new();
111 for _ in 0..len {
112 ts.add_ident("out");
113 ts.add_punc('.');
114 ts.add_ident(&get_endian_method_for_prim(
115 input,
116 false,
117 config.big_endian,
118 ));
119 ts.add_parens(TokenStream::create_ident("elem"));
120 ts.add_punc('?');
121 ts.add_punc(';');
122 }
123 ts
124 })
125 }
126 PrimitiveType::DynamicallySized(dy) => {
127 if config.strict_sizing {
128 return irox_derive_helpers::compile_error(
129 &x,
130 "Type is not compatible with strict sizing",
131 );
132 };
133 method.add_ident("out");
134 method.add_punc('.');
135 method.add_ident(&get_endian_method_for_varbl(
136 dy,
137 false,
138 config.big_endian,
139 ));
140 method.add_parens({
141 let mut ts = TokenStream::new();
142 ts.add_punc('&');
143 ts.add_ident("self");
144 ts.add_punc('.');
145 ts.add_ident(&ident.to_string());
146 ts
147 });
148 method.add_punc('?');
149 method.add_punc(';');
150 }
151 }
152 }
153 Err(_e) => {
154 let mut ts = TokenStream::new();
156 ts.wrap_generics({
157 let mut ts = TokenStream::new();
158 ts.extend::<TokenStream>(x.ty.to_token_stream().into());
159 ts.add_ident("as");
160 ts.add_ident("irox_structs");
161 ts.add_punc2(':', ':');
162 ts.add_ident("Struct");
163 ts
164 });
165 ts.add_punc2(':', ':');
166 ts.add_ident("write_to");
167 ts.add_parens({
168 let mut ts = TokenStream::new();
169 ts.add_punc('&');
170 ts.add_ident("self");
171 ts.add_punc('.');
172 ts.add_ident(&ident.to_string());
173 ts.add_punc(',');
174 ts.add_ident("out");
175 ts
176 });
177 ts.add_punc('?');
178 ts.add_punc(';');
179 method.extend(ts);
180 }
181 }
182 }
183 method.add_ident("Ok");
184 method.add_parens(TokenStream::create_empty_type());
185 ts.wrap_braces(method);
186 ts
187}
188
189fn create_parse_from_fn(n: &FieldsNamed, config: &Config) -> TokenStream {
190 let mut ts = TokenStream::new();
191 ts.extend::<TokenStream>(quote!(
192 fn parse_from<T: irox_structs::Bits>(input: &mut T) -> Result<Self::ImplType, irox_structs::Error>
193 ).into());
194
195 let mut inits = TokenStream::new();
196
197 for x in &n.named {
198 let Some(ident) = &x.ident else {
199 return irox_derive_helpers::compile_error(&x, "No ident");
200 };
201
202 match PrimitiveType::try_from(x) {
203 Ok(field) => match field {
204 PrimitiveType::Primitive(input) => {
205 if config.strict_sizing && TYPES_STRICT_SIZING_INCOMPATIBLE.contains(&input) {
206 return irox_derive_helpers::compile_error(
207 &x,
208 "Type is not compatible with strict sizing",
209 );
210 };
211 inits.add_ident(&ident.to_string());
212 inits.add_punc(':');
213 inits.add_ident("input");
214 inits.add_punc('.');
215 inits.add_ident(&get_endian_method_for_prim(input, true, config.big_endian));
216 inits.add_parens(TokenStream::new());
217 inits.add_punc('?');
218 inits.add_punc(',');
219 }
220 PrimitiveType::Array(input, len) => {
221 if config.strict_sizing && TYPES_STRICT_SIZING_INCOMPATIBLE.contains(&input) {
222 return irox_derive_helpers::compile_error(
223 &x,
224 "Type is not compatible with strict sizing",
225 );
226 };
227 inits.add_ident(&ident.to_string());
228 inits.add_punc(':');
229 inits.wrap_brackets({
230 let mut ts = TokenStream::new();
231 for _ in 0..len {
232 ts.add_ident("input");
233 ts.add_punc('.');
234 ts.add_ident(&get_endian_method_for_prim(
235 input,
236 true,
237 config.big_endian,
238 ));
239 ts.add_parens(TokenStream::new());
240 ts.add_punc('?');
241 ts.add_punc(',');
242 }
243 ts
244 });
245 inits.add_punc('.');
246 inits.add_ident("into");
247 inits.add_parens(TokenStream::new());
248 inits.add_punc(',');
249 }
250 PrimitiveType::DynamicallySized(ds) => {
251 if config.strict_sizing {
252 return irox_derive_helpers::compile_error(
253 &x,
254 "Type is not compatible with strict sizing",
255 );
256 };
257
258 inits.add_ident(&ident.to_string());
259 inits.add_punc(':');
260 inits.add_ident("input");
261 inits.add_punc('.');
262 inits.add_ident(&get_endian_method_for_varbl(ds, true, config.big_endian));
263 inits.add_parens(TokenStream::new());
264 inits.add_punc('?');
265 inits.add_punc(',');
266 }
267 },
268 Err(_e) => {
269 let ty = x.ty.to_token_stream();
271 inits.add_ident(&ident.to_string());
272 inits.add_punc(':');
273 inits.extend::<TokenStream>(
274 quote! {
275 <#ty as irox_structs::Struct>::parse_from(input)?,
276 }
277 .into(),
278 );
279 }
280 }
281 }
282
283 let mut method = TokenStream::new();
284 method.add_ident("Ok");
285 method.add_parens({
286 let mut ts = TokenStream::new();
287 ts.add_ident("Self");
288 ts.wrap_braces(inits);
289 ts
290 });
291 ts.wrap_braces(method);
292 ts
293}
294
295#[derive(Default)]
296struct StructSizing {
297 size: usize,
298}
299
300#[proc_macro_derive(Struct, attributes(little_endian, big_endian, strict_sizing))]
301pub fn struct_derive(input: TokenStream) -> TokenStream {
302 let input = parse_macro_input!(input as DeriveInput);
303 let mut config = Config {
304 big_endian: true,
305 strict_sizing: false,
306 };
307
308 for attr in &input.attrs {
309 let Ok(ident) = attr.meta.path().require_ident() else {
310 return irox_derive_helpers::compile_error(
311 &attr,
312 "This attribute is unnamed.".to_string(),
313 );
314 };
315 if ident.eq("little_endian") {
316 config.big_endian = false;
317 } else if ident.eq("big_endian") {
318 config.big_endian = true;
319 } else if ident.eq("strict_sizing") {
320 config.strict_sizing = true;
321 }
322 }
323
324 let struct_name = &input.ident;
325
326 let Data::Struct(s) = input.data else {
327 return irox_derive_helpers::compile_error(&input, "Can only derive on struct type");
328 };
329 let Fields::Named(n) = s.fields else {
330 return irox_derive_helpers::compile_error(&s.fields, "Can only derive on named fields.");
331 };
332
333 let mut ts = TokenStream::new();
334 let mut sizing = StructSizing::default();
335 ts.add_ident("impl");
336 ts.add_path(&["irox_structs", "Struct"]);
337 ts.add_ident("for");
338 ts.add_ident(&struct_name.to_string());
339 ts.wrap_braces({
340 let mut ts = TokenStream::new();
341 ts.add_ident("type");
342 ts.add_ident("ImplType");
343 ts.add_punc('=');
344 ts.add_ident(&struct_name.to_string());
345 ts.add_punc(';');
346
347 ts.extend(create_write_to_fn(&n, &config, &mut sizing));
348 ts.extend(create_parse_from_fn(&n, &config));
349 ts
350 });
351 if config.strict_sizing {
352 ts.add_ident("impl");
353 ts.add_ident(&struct_name.to_string());
354 ts.wrap_braces({
355 let mut ts = TokenStream::new();
356 ts.add_ident("pub");
357 ts.add_ident("const");
358 ts.add_ident("STRUCT_SIZE");
359 ts.add_punc(':');
360 ts.add_ident("usize");
361 ts.add_punc('=');
362 ts.add_literal(Literal::usize_suffixed(sizing.size));
363 ts.add_punc(';');
364 ts
365 });
366 }
367 ts
368}
369
370#[proc_macro_derive(Shared, attributes(shared))]
371pub fn shared_derive(input: TokenStream) -> TokenStream {
372 shared::shared_derive(input)
373}