Skip to main content

cvlr_derive/
lib.rs

1use {
2    proc_macro::TokenStream,
3    proc_macro2::Span,
4    quote::quote,
5    syn::{
6        parse_macro_input,
7        Data::{Enum, Struct, Union},
8        DeriveInput,
9        Fields::{self, Named, Unnamed},
10        FieldsNamed, FieldsUnnamed, Ident, Index, Variant,
11    },
12};
13
14fn of_named_fields(n: &Ident, named_fields: &FieldsNamed) -> proc_macro2::TokenStream {
15    let initialize = named_fields.named.iter().map(|f| {
16        let name = f.ident.as_ref().unwrap();
17        quote! {
18            #name: ::cvlr::nondet::nondet(),
19        }
20    });
21
22    quote! {
23        #n {
24            #( #initialize )*
25        }
26    }
27}
28
29fn of_unnamed_fields(n: &Ident, unnamed: &FieldsUnnamed) -> proc_macro2::TokenStream {
30    let initialize = unnamed.unnamed.iter().map(|_| {
31        quote! { ::cvlr::nondet::nondet(), }
32    });
33
34    quote! {
35        #n (
36            #( #initialize )*
37        )
38    }
39}
40
41fn of_enum_variant(variant: &Variant, enum_name: &Ident) -> proc_macro2::TokenStream {
42    let variant_name = &variant.ident;
43    match &variant.fields {
44        Fields::Unit => quote! {
45            #enum_name::#variant_name
46        },
47        Fields::Unnamed(unnamed) => {
48            let initialize = unnamed.unnamed.iter().map(|_| {
49                quote! { ::cvlr::nondet::nondet(), }
50            });
51            quote! {
52                #enum_name::#variant_name(
53                    #( #initialize )*
54                )
55            }
56        }
57        Fields::Named(named) => {
58            let initialize = named.named.iter().map(|f| {
59                let field_name = f.ident.as_ref().unwrap();
60                quote! {
61                    #field_name: ::cvlr::nondet::nondet(),
62                }
63            });
64            quote! {
65                #enum_name::#variant_name {
66                    #( #initialize )*
67                }
68            }
69        }
70    }
71}
72
73/// Derive macro for implementing the `Nondet` trait
74///
75/// This macro generates an implementation of `Nondet` for structs and enums,
76/// allowing them to be created with non-deterministic (symbolic) values.
77///
78/// # Example
79///
80/// ```ignore
81/// use cvlr_derive::Nondet;
82/// use cvlr::prelude::*;
83///
84/// #[derive(Nondet)]
85/// struct Point {
86///     x: u64,
87///     y: u64,
88/// }
89///
90/// #[derive(Nondet)]
91/// enum MyEnum {
92///     Variant1,
93///     Variant2(u64),
94///     Variant3 { x: u64 },
95/// }
96///
97/// let p = Point::nondet();
98/// let e = MyEnum::nondet();
99/// ```
100#[proc_macro_derive(Nondet)]
101pub fn derive_nondet(item: TokenStream) -> TokenStream {
102    let input = parse_macro_input!(item as DeriveInput);
103    let name = input.ident;
104    match input.data {
105        Enum(data_enum) => {
106            let variants = &data_enum.variants;
107            let variant_count = variants.len();
108
109            if variant_count == 0 {
110                return quote! {
111                    compile_error!("Enum must have at least one variant");
112                }
113                .into();
114            }
115
116            let mut match_arms = Vec::new();
117            for (index, variant) in variants.iter().enumerate() {
118                let variant_expr = of_enum_variant(variant, &name);
119                if index == variant_count - 1 {
120                    // Last variant is catch-all
121                    match_arms.push(quote! {
122                        _ => #variant_expr,
123                    });
124                } else {
125                    let index_lit = index as u64;
126                    match_arms.push(quote! {
127                        #index_lit => #variant_expr,
128                    });
129                }
130            }
131
132            quote! {
133                impl ::cvlr::nondet::Nondet for #name {
134                    fn nondet() -> #name {
135                        match ::cvlr::nondet::nondet::<u64>() {
136                            #( #match_arms )*
137                        }
138                    }
139                }
140            }
141            .into()
142        }
143
144        Union(_) => {
145            todo!("Union not supported yet")
146        }
147
148        Struct(ds) => match ds.fields {
149            Fields::Unit => quote! {
150                impl ::cvlr::nondet::Nondet for #name {
151                    fn nondet() -> #name {
152                        #name
153                    }
154                }
155            }
156            .into(),
157
158            Named(named) => {
159                let init = of_named_fields(&name, &named);
160                quote! {
161                    impl ::cvlr::nondet::Nondet for #name {
162                        fn nondet() -> #name {
163                            #init
164                        }
165                    }
166                }
167                .into()
168            }
169
170            Unnamed(fields) => {
171                let init = of_unnamed_fields(&name, &fields);
172                quote! {
173                    impl ::cvlr::nondet::Nondet for #name {
174                        fn nondet() -> #name {
175                            #init
176                        }
177                    }
178                }
179                .into()
180            }
181        },
182    }
183}
184
185/// Derive macro for implementing the `CvlrLog` trait
186///
187/// This macro generates an implementation of `CvlrLog` for structs and enums,
188/// allowing them to be logged with CVLR's logging system.
189///
190/// Supports:
191/// - Structs with named fields (uses field names as tags)
192/// - Structs with unnamed fields (uses field indices "0", "1", "2", etc. as tags)
193/// - Unit structs (empty scope)
194/// - Enums with unit variants (logs variant name)
195/// - Enums with field variants (logs variant name first, then fields; uses scope for multiple fields)
196///
197/// # Example
198///
199/// ```ignore
200/// use cvlr_derive::CvlrLog;
201/// use cvlr::log::CvlrLog;
202///
203/// #[derive(CvlrLog)]
204/// struct Point {
205///     x: u64,
206///     y: u64,
207/// }
208///
209/// #[derive(CvlrLog)]
210/// struct Tuple(u64, i32);
211///
212/// #[derive(CvlrLog)]
213/// enum MyEnum {
214///     Variant1,
215///     Variant2(u64),
216///     Variant3 { x: u64, y: i32 },
217/// }
218///
219/// let p = Point { x: 1, y: 2 };
220/// p.log("point", &mut logger);
221///
222/// let t = Tuple(1, -2);
223/// t.log("tuple", &mut logger);
224///
225/// let e = MyEnum::Variant2(42);
226/// e.log("enum", &mut logger);
227/// ```
228#[proc_macro_derive(CvlrLog)]
229pub fn derive_cvlr_log(item: TokenStream) -> TokenStream {
230    let input = parse_macro_input!(item as DeriveInput);
231    let name = input.ident;
232
233    match input.data {
234        Enum(data_enum) => {
235            let variants = &data_enum.variants;
236            let match_arms: Vec<_> = variants.iter().map(|variant| {
237                let variant_name = &variant.ident;
238                let variant_name_str = variant_name.to_string();
239                match &variant.fields {
240                    Fields::Unit => {
241                        quote! {
242                            #name::#variant_name => {
243                                logger.log_str(tag, #variant_name_str);
244                            }
245                        }
246                    }
247                    Fields::Unnamed(unnamed) => {
248                        let field_bindings: Vec<_> = unnamed.unnamed.iter().enumerate().map(|(index, _f)| {
249                            syn::Ident::new(&format!("field{}", index), Span::call_site())
250                        }).collect();
251                        let field_logs: Vec<_> = unnamed.unnamed.iter().enumerate().map(|(index, _f)| {
252                            let field_binding = &field_bindings[index];
253                            let field_index_str = index.to_string();
254                            quote! {
255                                ::cvlr::log::cvlr_log_with(#field_index_str, &#field_binding, logger);
256                            }
257                        }).collect();
258                        quote! {
259                            #name::#variant_name(#(ref #field_bindings),*) => {
260                                logger.log_scope_start(tag);
261                                logger.log_str(tag, #variant_name_str);
262                                #( #field_logs )*
263                                logger.log_scope_end(tag);
264                            }
265                        }
266                    }
267                    Fields::Named(named) => {
268                        let field_logs: Vec<_> = named.named.iter().map(|f| {
269                            let field_name = f.ident.as_ref().unwrap();
270                            let field_name_str = field_name.to_string();
271                            quote! {
272                                ::cvlr::log::cvlr_log_with(#field_name_str, &#field_name, logger);
273                            }
274                        }).collect();
275                        let field_names: Vec<_> = named.named.iter().map(|f| {
276                            f.ident.as_ref().unwrap()
277                        }).collect();
278
279                        quote! {
280                            #name::#variant_name { #(ref #field_names),* } => {
281                                logger.log_scope_start(tag);
282                                logger.log_str(tag, #variant_name_str);
283                                #( #field_logs )*
284                                logger.log_scope_end(tag);
285                            }
286                        }
287                    }
288                }
289            }).collect();
290
291            quote! {
292                impl ::cvlr::log::CvlrLog for #name {
293                    #[inline(always)]
294                    fn log(&self, tag: &str, logger: &mut ::cvlr::log::CvlrLogger) {
295                        match self {
296                            #( #match_arms )*
297                        }
298                    }
299                }
300            }
301            .into()
302        }
303
304        Union(_) => quote! {
305            compile_error!("CvlrLog derive is only supported for structs");
306        }
307        .into(),
308
309        Struct(ds) => {
310            match ds.fields {
311                Fields::Unit => quote! {
312                    impl ::cvlr::log::CvlrLog for #name {
313                        #[inline(always)]
314                        fn log(&self, tag: &str, logger: &mut ::cvlr::log::CvlrLogger) {
315                            logger.log_scope_start(tag);
316                            logger.log_scope_end(tag);
317                        }
318                    }
319                }
320                .into(),
321
322                Fields::Unnamed(unnamed) => {
323                    let field_logs: Vec<_> = unnamed.unnamed.iter().enumerate().map(|(index, _f)| {
324                    let field_index = Index::from(index);
325                    let field_index_str = index.to_string();
326                    quote! {
327                        ::cvlr::log::cvlr_log_with(#field_index_str, &self.#field_index, logger);
328                    }
329                }).collect();
330
331                    quote! {
332                        impl ::cvlr::log::CvlrLog for #name {
333                            #[inline(always)]
334                            fn log(&self, tag: &str, logger: &mut ::cvlr::log::CvlrLogger) {
335                                logger.log_scope_start(tag);
336                                #( #field_logs )*
337                                logger.log_scope_end(tag);
338                            }
339                        }
340                    }
341                    .into()
342                }
343
344                Fields::Named(named) => {
345                    let field_logs: Vec<_> = named.named.iter().map(|f| {
346                    let field_name = f.ident.as_ref().unwrap();
347                    let field_name_str = field_name.to_string();
348                    quote! {
349                        ::cvlr::log::cvlr_log_with(#field_name_str, &self.#field_name, logger);
350                    }
351                }).collect();
352
353                    quote! {
354                        impl ::cvlr::log::CvlrLog for #name {
355                            #[inline(always)]
356                            fn log(&self, tag: &str, logger: &mut ::cvlr::log::CvlrLogger) {
357                                logger.log_scope_start(tag);
358                                #( #field_logs )*
359                                logger.log_scope_end(tag);
360                            }
361                        }
362                    }
363                    .into()
364                }
365            }
366        }
367    }
368}