chandeliers_san/codegen/
options.rs

1//! Generate code depending on options passed through declaration attributes.
2
3use proc_macro2::TokenStream;
4use quote::{quote, quote_spanned, ToTokens};
5
6use crate::ast;
7use crate::ast::options::usage::Codegen as This;
8use crate::ast::options::{Allow, Const, ExtNode, Node, TraceFile, TraceFormat};
9use crate::sp::Sp;
10
11use super::{AsIdent, RawIdent, SanitizedIdent};
12
13impl ToTokens for Allow {
14    fn to_tokens(&self, toks: &mut TokenStream) {
15        toks.extend(match self {
16            Self::Rustc(id) => quote!( #[allow(#id)] ),
17            Self::Clippy(id) => quote!( #[allow(clippy::#id)] ),
18        });
19    }
20}
21
22/// Define the behavior of an option.
23///
24/// We expect most options to be specific enough that their functionality
25/// can be implemented homogeneously between all declarations that it can
26/// apply to, to we define this macro as a way to provide the implementation
27/// of an option for all declarations at once.
28///
29/// Typical usage will look like this:
30/// ```skip
31/// generic_option! {
32///     #[doc = "Explanation of the option"]
33///     // This defines the name of the trait that implements this option,
34///     // and the implementors of the trait in question among `Node`, `ExtNode`,
35///     // `Const`, and `ExtConst`.
36///     trait Stuff for { Node, Const }
37///     impl {
38///         // All option groups should have a `stuff` field of type
39///         // `UseOpt<bool, context[Codegen]>`
40///         // See [`chandeliers_san::ast::options::UseOpt`]
41///         // for more information.
42///         from stuff return bool;
43///         // Finally the implementation.
44///         fn stuff(&self) -> TokenStream {
45///             unimplemented!()
46///         }
47///     }
48/// }
49/// ```
50macro_rules! generic_option {
51    (
52      $( #[$($doc:tt)*] )*
53      trait $trait:ident for { $($implementor:ty),* }
54      impl {
55          from $field:ident return $fetch:ty ;
56          $($func:tt)*
57      }
58    ) => {
59        $( #[$($doc)*] )*
60        pub trait $trait {
61            #[doc = "How to get the value for this option"]
62            fn fetch(&self) -> $fetch;
63
64            #[doc = "How to construct a TokenStream that implements the feature from this option"]
65            $($func)*
66        }
67
68        $(
69            impl $trait for $implementor {
70                fn fetch(&self) -> $fetch {
71                    self.$field.fetch::<This>()
72                }
73            }
74        )*
75    };
76}
77
78generic_option! {
79    #[doc = "`#[trace]`: print debug information."]
80    trait Traces for { Node, ExtNode }
81    impl {
82        from trace return &Option<(TraceFile, (TraceFormat, TraceFormat))>;
83        fn traces(
84            &self,
85            prefix: &str,
86            name: &Sp<ast::decl::NodeName>,
87            inputs: Sp<&ast::Tuple<Sp<ast::decl::TyVar>>>,
88            locals: Sp<&ast::Tuple<Sp<ast::decl::TyVar>>>,
89            outputs: Sp<&ast::Tuple<Sp<ast::decl::TyVar>>>,
90        ) -> (TokenStream, TokenStream) {
91            if let Some((file, (ifmt, ofmt))) = self.fetch() {
92                let name = format!("{name}");
93                let input_var_fmt = inputs.fmt_strings();
94                let output_var_fmt = outputs.fmt_strings();
95                let inputs_self_assigned = inputs.self_assigned();
96                let locals_self_assigned = locals.self_assigned();
97                let outputs_self_assigned = outputs.self_assigned();
98                let input_fmt = match ifmt {
99                    TraceFormat::Default => format!("{{_ext}}{{_this}} <- {input_var_fmt}\n"),
100                    TraceFormat::Empty => String::new(),
101                    TraceFormat::Str(s) => s.clone(),
102                };
103                let output_fmt = match ofmt {
104                    TraceFormat::Default => format!("{{_ext}}{{_this}} -> {output_var_fmt}\n"),
105                    TraceFormat::Empty => String::new(),
106                    TraceFormat::Str(s) => s.clone(),
107                };
108                let printer = match file {
109                    TraceFile::StdOut => quote!(print),
110                    TraceFile::StdErr => quote!(eprint),
111                };
112                (
113                    quote! {
114                        #[allow(unused_variables)]
115                        {
116                            let _ext = #prefix;
117                            let _this = #name;
118                            let _clk = self.__clock;
119                            #inputs_self_assigned
120                            #printer!(#input_fmt);
121                        }
122                    },
123                    quote! {
124                        #[allow(unused_variables)]
125                        {
126                            let _ext = #prefix;
127                            let _this = #name;
128                            let _clk = self.__clock;
129                            #inputs_self_assigned
130                            #locals_self_assigned
131                            #outputs_self_assigned
132                            #printer!(#output_fmt);
133                        }
134                    },
135                )
136            } else {
137                (quote!(), quote!())
138            }
139        }
140    }
141}
142
143generic_option! {
144    #[doc = "`#[main(42)]`: build a `main` function that executes this node a set number of times."]
145    trait FnMain for { Node, ExtNode }
146    impl {
147        from main return &Option<usize>;
148        fn fn_main(&self, name: &Sp<ast::decl::NodeName>, rustc_allow: &Vec<Allow>) -> TokenStream {
149            if let Some(nb_iter) = self.fetch() {
150                let ext_name = name.as_ident(SanitizedIdent, None);
151
152                let doc = format!(
153                    "Main function automatically generated from {name} (runs for {nb_iter} steps)"
154                );
155                quote_spanned! {name.span.unwrap()=>
156                    #[doc = #doc]
157                    #[allow(unused_imports)] // Step, Embed, Trusted imported just in case.
158                    #( #rustc_allow )*
159                    pub fn main() {
160                        use ::chandeliers_sem::traits::{Step, Embed, Trusted};
161                        let mut sys = #ext_name::default();
162                        if #nb_iter == 0 {
163                            loop {
164                                sys.step(().embed()).trusted();
165                            }
166                        } else {
167                            for _ in 1..=#nb_iter {
168                                sys.step(().embed()).trusted();
169                            }
170                        }
171                    }
172                }
173            } else {
174                quote!()
175            }
176        }
177    }
178}
179
180generic_option! {
181    #[doc = "`#[test(42)]`: build a test function that executes this node a set number of times."]
182    trait FnTest for { Node }
183    impl {
184        from test return &Option<usize>;
185        fn fn_test(&self, name: &Sp<ast::decl::NodeName>, rustc_allow: &Vec<Allow>) -> TokenStream {
186            if let Some(nb_iter) = self.fetch() {
187                let priv_name = name.as_ident(SanitizedIdent, None);
188                let pub_name = name.as_ident(RawIdent, None);
189
190                let doc = format!(
191                    "Test function automatically generated from {name} (runs for {nb_iter} steps)"
192                );
193                quote_spanned! {name.span.unwrap()=>
194                    #[doc = #doc]
195                    #[allow(unused_imports)] // Step, Embed, Trusted imported just in case.
196                    #( #rustc_allow )*
197                    #[test]
198                    pub fn #pub_name() {
199                        use ::chandeliers_sem::traits::{Step, Embed, Trusted};
200                        let mut sys = #priv_name::default();
201                        if #nb_iter == 0 {
202                            loop {
203                                sys.step(().embed()).trusted();
204                            }
205                        } else {
206                            for _ in 1..=#nb_iter {
207                                sys.step(().embed()).trusted();
208                            }
209                        }
210                    }
211                }
212            } else {
213                quote!()
214            }
215        }
216    }
217}
218
219generic_option! {
220    #[doc = "`#[pub]`: make this declaration public. Implies `#[export]`."]
221    trait PubQualifier for { Const, Node }
222    impl {
223        from public return &bool;
224        fn pub_qualifier(&self, implementing_trait: bool) -> TokenStream {
225            if *self.fetch() && !implementing_trait{
226                quote!(pub)
227            } else {
228                quote!()
229            }
230        }
231    }
232}
233
234generic_option! {
235    #[doc = "`#[doc(\"Message\")]`: insert documentation in the generated code."]
236    trait Docs for { Const, Node }
237    impl {
238        from doc return &Vec<Sp<String>>;
239        fn docs(&self) -> TokenStream {
240            let docs = self.fetch();
241            if docs.is_empty() {
242                quote! {
243                    #[doc = "(No user documentation provided)"]
244                }
245            } else {
246                quote! {
247                    #( #[doc = #docs] )*
248                }
249            }
250        }
251    }
252}
253
254generic_option! {
255    #[doc = "`#[generic[T, U, V]]`: declare type variables. Returns (1) the variable declarations, (2) the phantom data to use, and (3) the trait bounds."]
256    trait GenericParams for { Node, ExtNode }
257    impl {
258        from generics return &Vec<Sp<String>>;
259        fn generic_params(&self) -> (TokenStream, TokenStream, TokenStream) {
260            let generics = self.fetch().iter().map(|t| syn::Ident::new_raw(&t.t, t.span.unwrap())).collect::<Vec<_>>();
261            if generics.is_empty() {
262                (quote!(), quote!( () ), quote!())
263            } else {
264                (quote! {
265                    < #( #generics ),* >
266                }, quote! {
267                    ::std::marker::PhantomData<( #( #generics ),* )>
268                }, quote! {
269                    where #(
270                        #generics: ::chandeliers_sem::traits::Scalar,
271                    )*
272                })
273            }
274        }
275    }
276}