Skip to main content

annotate_build/
config.rs

1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::rc::Rc;
4
5use crate::visitor;
6
7#[derive(Debug, PartialEq)]
8pub struct ConfigSpec {
9    pub functions: &'static [FunctionSpec],
10    pub modules: &'static [ModuleSpec],
11}
12
13#[derive(Debug, PartialEq)]
14pub struct FunctionSpec {
15    pub pragma: &'static str,
16}
17
18#[derive(Debug, PartialEq)]
19pub struct ModuleSpec {
20    pub pragma: &'static str,
21    pub derives: &'static [ModuleDeriveSpec],
22}
23
24#[derive(Debug, PartialEq)]
25pub struct ModuleDeriveSpec {
26    pub name: Option<&'static str>,
27    pub modules: &'static [ModuleDeriveSpec],
28    pub functions: &'static [&'static str],
29}
30
31#[derive(Default, Debug)]
32pub struct BuildConfig {
33    pub(crate) pragmas: Vec<String>,
34    pub(crate) derives: Vec<visitor::CustomDerive>,
35    pub(crate) module_derives: HashMap<String, Vec<visitor::CustomModuleDerive>>,
36}
37
38impl BuildConfig {
39    pub fn from_spec(spec: &ConfigSpec) -> Self {
40        spec.into()
41    }
42
43    pub(crate) fn merge(&mut self, other: Self) {
44        for pragma in other.pragmas {
45            push_unique_pragma(&mut self.pragmas, pragma.as_str());
46        }
47
48        self.derives.extend(other.derives);
49
50        for (pragma, derives) in other.module_derives {
51            self.module_derives.entry(pragma).or_default().extend(derives);
52        }
53    }
54
55    /// Register an additional attribute name that should be treated like `#[pragma(...)]`.
56    pub fn pragma(&mut self, pragma: impl Into<String>) -> &mut Self {
57        self.pragmas.push(pragma.into());
58        self
59    }
60
61    /// Register a custom derive name and its mustache template expansion.
62    pub fn derive(&mut self, name: impl Into<String>, template: impl AsRef<str>) -> &mut Self {
63        self.derives
64            .push(visitor::CustomDerive::new(name, template.as_ref()));
65        self
66    }
67
68    pub fn module_derive(
69        &mut self,
70        extension: impl Into<String>,
71        configure: impl FnOnce(&mut ModuleDeriveBuilder),
72    ) -> &mut Self {
73        let extension = extension.into();
74        let existing_derives = self
75            .module_derives
76            .get(&extension)
77            .cloned()
78            .unwrap_or_default();
79
80        let mut builder = ModuleDeriveBuilder::new(existing_derives);
81        configure(&mut builder);
82
83        self.module_derives
84            .insert(extension, builder.derives.borrow().clone());
85
86        self
87    }
88}
89
90impl From<&ConfigSpec> for BuildConfig {
91    fn from(spec: &ConfigSpec) -> Self {
92        let mut config = BuildConfig::default();
93
94        for function in spec.functions {
95            push_unique_pragma(&mut config.pragmas, function.pragma);
96        }
97
98        for module in spec.modules {
99            push_unique_pragma(&mut config.pragmas, module.pragma);
100            config.module_derives.insert(
101                module.pragma.to_string(),
102                module
103                    .derives
104                    .iter()
105                    .map(custom_module_derive_from_spec)
106                    .collect(),
107            );
108        }
109
110        config
111    }
112}
113
114#[derive(Default, Clone)]
115pub struct ModuleDeriveBuilder {
116    derives: Rc<RefCell<Vec<visitor::CustomModuleDerive>>>,
117    current_derive_path: Vec<usize>,
118}
119
120impl ModuleDeriveBuilder {
121    fn new(existing_derives: Vec<visitor::CustomModuleDerive>) -> Self {
122        Self {
123            derives: Rc::new(RefCell::new(existing_derives)),
124            current_derive_path: vec![],
125        }
126    }
127
128    pub fn module(&mut self, module: impl Into<String>) -> Self {
129        let mut clone = self.clone();
130
131        if clone.current_derive_path.is_empty() {
132            let index = clone.derives.borrow().len();
133            clone
134                .derives
135                .borrow_mut()
136                .push(visitor::CustomModuleDerive::new(module.into()));
137            clone.current_derive_path.push(index);
138        } else {
139            let mut index = 0;
140            clone.with_derive_at_path(|derive| {
141                index = derive.add_module(module.into());
142            });
143            clone.current_derive_path.push(index);
144        }
145
146        clone
147    }
148
149    pub fn functions(&mut self, functions: impl IntoIterator<Item = impl Into<String>>) -> Self {
150        let clone = self.clone();
151
152        clone.with_derive_at_path(|derive| {
153            for each in functions {
154                derive.add_function(each.into());
155            }
156        });
157
158        clone
159    }
160
161    fn with_derive_at_path(&self, f: impl FnOnce(&mut visitor::CustomModuleDerive)) {
162        if self.derives.borrow().is_empty() {
163            self.derives
164                .borrow_mut()
165                .push(visitor::CustomModuleDerive::default());
166        }
167
168        let mut borrow = self.derives.borrow_mut();
169        if self.current_derive_path.is_empty() {
170            let last_derive = borrow
171                .last_mut()
172                .unwrap()
173                .derive_at_mut(&self.current_derive_path);
174
175            f(last_derive)
176        } else {
177            let index = *self.current_derive_path.first().unwrap();
178            let first_derive = borrow.get_mut(index).unwrap();
179            f(first_derive.derive_at_mut(&self.current_derive_path[1..]));
180        }
181    }
182}
183
184fn push_unique_pragma(pragmas: &mut Vec<String>, pragma: &str) {
185    if pragmas.iter().all(|existing| existing != pragma) {
186        pragmas.push(pragma.to_string());
187    }
188}
189
190fn custom_module_derive_from_spec(spec: &ModuleDeriveSpec) -> visitor::CustomModuleDerive {
191    visitor::CustomModuleDerive::from_spec(spec)
192}
193
194#[macro_export]
195macro_rules! custom {
196    ($($tokens:tt)*) => {
197        $crate::__custom_config!(@parse [] [] $($tokens)*)
198    };
199}
200
201#[doc(hidden)]
202#[macro_export]
203macro_rules! __custom_config {
204    (@parse [ $($functions:expr,)* ] [ $($modules:expr,)* ]) => {
205        $crate::ConfigSpec {
206            functions: &[ $($functions,)* ],
207            modules: &[ $($modules,)* ],
208        }
209    };
210    (@parse [ $($functions:expr,)* ] [ $($modules:expr,)* ] fn pragma $pragma:literal, $($rest:tt)*) => {
211        $crate::__custom_config!(
212            @parse
213            [
214                $($functions,)*
215                $crate::FunctionSpec { pragma: $pragma },
216            ]
217            [ $($modules,)* ]
218            $($rest)*
219        )
220    };
221    (@parse [ $($functions:expr,)* ] [ $($modules:expr,)* ] fn pragma $pragma:literal) => {
222        $crate::__custom_config!(
223            @parse
224            [
225                $($functions,)*
226                $crate::FunctionSpec { pragma: $pragma },
227            ]
228            [ $($modules,)* ]
229        )
230    };
231    (@parse [ $($functions:expr,)* ] [ $($modules:expr,)* ] mod pragma $pragma:literal { $($body:tt)* }, $($rest:tt)*) => {
232        $crate::__custom_config!(
233            @parse
234            [ $($functions,)* ]
235            [
236                $($modules,)*
237                $crate::ModuleSpec {
238                    pragma: $pragma,
239                    derives: &$crate::__custom_module_derives!(@parse [] $($body)*),
240                },
241            ]
242            $($rest)*
243        )
244    };
245    (@parse [ $($functions:expr,)* ] [ $($modules:expr,)* ] mod pragma $pragma:literal { $($body:tt)* }) => {
246        $crate::__custom_config!(
247            @parse
248            [ $($functions,)* ]
249            [
250                $($modules,)*
251                $crate::ModuleSpec {
252                    pragma: $pragma,
253                    derives: &$crate::__custom_module_derives!(@parse [] $($body)*),
254                },
255            ]
256        )
257    };
258}
259
260#[doc(hidden)]
261#[macro_export]
262macro_rules! __custom_module_derives {
263    (@parse [ $($derives:expr,)* ]) => {
264        [ $($derives,)* ]
265    };
266    (@parse [ $($derives:expr,)* ] derive { mod $name:ident { $($body:tt)* } }, $($rest:tt)*) => {
267        $crate::__custom_module_derives!(
268            @parse
269            [
270                $($derives,)*
271                $crate::__custom_module_derive_spec!(@named_ident $name [] [] $($body)*),
272            ]
273            $($rest)*
274        )
275    };
276    (@parse [ $($derives:expr,)* ] derive { mod $name:ident { $($body:tt)* } }) => {
277        $crate::__custom_module_derives!(
278            @parse
279            [
280                $($derives,)*
281                $crate::__custom_module_derive_spec!(@named_ident $name [] [] $($body)*),
282            ]
283        )
284    };
285    (@parse [ $($derives:expr,)* ] derive { $($body:tt)* }, $($rest:tt)*) => {
286        $crate::__custom_module_derives!(
287            @parse
288            [
289                $($derives,)*
290                $crate::__custom_module_derive_spec!(@unnamed [] [] $($body)*),
291            ]
292            $($rest)*
293        )
294    };
295    (@parse [ $($derives:expr,)* ] derive { $($body:tt)* }) => {
296        $crate::__custom_module_derives!(
297            @parse
298            [
299                $($derives,)*
300                $crate::__custom_module_derive_spec!(@unnamed [] [] $($body)*),
301            ]
302        )
303    };
304    (@parse [ $($derives:expr,)* ] derive $name:literal { $($body:tt)* }, $($rest:tt)*) => {
305        $crate::__custom_module_derives!(
306            @parse
307            [
308                $($derives,)*
309                $crate::__custom_module_derive_spec!(@named $name [] [] $($body)*),
310            ]
311            $($rest)*
312        )
313    };
314    (@parse [ $($derives:expr,)* ] derive $name:literal { $($body:tt)* }) => {
315        $crate::__custom_module_derives!(
316            @parse
317            [
318                $($derives,)*
319                $crate::__custom_module_derive_spec!(@named $name [] [] $($body)*),
320            ]
321        )
322    };
323}
324
325#[doc(hidden)]
326#[macro_export]
327macro_rules! __custom_module_derive_spec {
328    (@unnamed [ $($functions:expr,)* ] [ $($modules:expr,)* ]) => {
329        $crate::ModuleDeriveSpec {
330            name: None,
331            functions: &[ $($functions,)* ],
332            modules: &[ $($modules,)* ],
333        }
334    };
335    (@named_ident $name:ident [ $($functions:expr,)* ] [ $($modules:expr,)* ]) => {
336        $crate::ModuleDeriveSpec {
337            name: Some(stringify!($name)),
338            functions: &[ $($functions,)* ],
339            modules: &[ $($modules,)* ],
340        }
341    };
342    (@named $name:literal [ $($functions:expr,)* ] [ $($modules:expr,)* ]) => {
343        $crate::ModuleDeriveSpec {
344            name: Some($name),
345            functions: &[ $($functions,)* ],
346            modules: &[ $($modules,)* ],
347        }
348    };
349    (@unnamed [ $($functions:expr,)* ] [ $($modules:expr,)* ] fn $function:ident, $($rest:tt)*) => {
350        $crate::__custom_module_derive_spec!(
351            @unnamed
352            [ $($functions,)* stringify!($function), ]
353            [ $($modules,)* ]
354            $($rest)*
355        )
356    };
357    (@unnamed [ $($functions:expr,)* ] [ $($modules:expr,)* ] fn $function:ident) => {
358        $crate::__custom_module_derive_spec!(
359            @unnamed
360            [ $($functions,)* stringify!($function), ]
361            [ $($modules,)* ]
362        )
363    };
364    (@named $name:literal [ $($functions:expr,)* ] [ $($modules:expr,)* ] fn $function:ident, $($rest:tt)*) => {
365        $crate::__custom_module_derive_spec!(
366            @named
367            $name
368            [ $($functions,)* stringify!($function), ]
369            [ $($modules,)* ]
370            $($rest)*
371        )
372    };
373    (@named $name:literal [ $($functions:expr,)* ] [ $($modules:expr,)* ] fn $function:ident) => {
374        $crate::__custom_module_derive_spec!(
375            @named
376            $name
377            [ $($functions,)* stringify!($function), ]
378            [ $($modules,)* ]
379        )
380    };
381    (@named_ident $name:ident [ $($functions:expr,)* ] [ $($modules:expr,)* ] fn $function:ident, $($rest:tt)*) => {
382        $crate::__custom_module_derive_spec!(
383            @named_ident
384            $name
385            [ $($functions,)* stringify!($function), ]
386            [ $($modules,)* ]
387            $($rest)*
388        )
389    };
390    (@named_ident $name:ident [ $($functions:expr,)* ] [ $($modules:expr,)* ] fn $function:ident) => {
391        $crate::__custom_module_derive_spec!(
392            @named_ident
393            $name
394            [ $($functions,)* stringify!($function), ]
395            [ $($modules,)* ]
396        )
397    };
398    (@unnamed [ $($functions:expr,)* ] [ $($modules:expr,)* ] mod $name:literal { $($body:tt)* }, $($rest:tt)*) => {
399        $crate::__custom_module_derive_spec!(
400            @unnamed
401            [ $($functions,)* ]
402            [
403                $($modules,)*
404                $crate::__custom_module_derive_spec!(@named $name [] [] $($body)*),
405            ]
406            $($rest)*
407        )
408    };
409    (@unnamed [ $($functions:expr,)* ] [ $($modules:expr,)* ] mod $name:literal { $($body:tt)* }) => {
410        $crate::__custom_module_derive_spec!(
411            @unnamed
412            [ $($functions,)* ]
413            [
414                $($modules,)*
415                $crate::__custom_module_derive_spec!(@named $name [] [] $($body)*),
416            ]
417        )
418    };
419    (@unnamed [ $($functions:expr,)* ] [ $($modules:expr,)* ] mod $name:ident { $($body:tt)* }, $($rest:tt)*) => {
420        $crate::__custom_module_derive_spec!(
421            @unnamed
422            [ $($functions,)* ]
423            [
424                $($modules,)*
425                $crate::__custom_module_derive_spec!(@named_ident $name [] [] $($body)*),
426            ]
427            $($rest)*
428        )
429    };
430    (@unnamed [ $($functions:expr,)* ] [ $($modules:expr,)* ] mod $name:ident { $($body:tt)* }) => {
431        $crate::__custom_module_derive_spec!(
432            @unnamed
433            [ $($functions,)* ]
434            [
435                $($modules,)*
436                $crate::__custom_module_derive_spec!(@named_ident $name [] [] $($body)*),
437            ]
438        )
439    };
440    (@named $name:literal [ $($functions:expr,)* ] [ $($modules:expr,)* ] mod $module_name:ident { $($body:tt)* }, $($rest:tt)*) => {
441        $crate::__custom_module_derive_spec!(
442            @named
443            $name
444            [ $($functions,)* ]
445            [
446                $($modules,)*
447                $crate::__custom_module_derive_spec!(@named_ident $module_name [] [] $($body)*),
448            ]
449            $($rest)*
450        )
451    };
452    (@named $name:literal [ $($functions:expr,)* ] [ $($modules:expr,)* ] mod $module_name:ident { $($body:tt)* }) => {
453        $crate::__custom_module_derive_spec!(
454            @named
455            $name
456            [ $($functions,)* ]
457            [
458                $($modules,)*
459                $crate::__custom_module_derive_spec!(@named_ident $module_name [] [] $($body)*),
460            ]
461        )
462    };
463    (@named_ident $name:ident [ $($functions:expr,)* ] [ $($modules:expr,)* ] mod $module_name:ident { $($body:tt)* }, $($rest:tt)*) => {
464        $crate::__custom_module_derive_spec!(
465            @named_ident
466            $name
467            [ $($functions,)* ]
468            [
469                $($modules,)*
470                $crate::__custom_module_derive_spec!(@named_ident $module_name [] [] $($body)*),
471            ]
472            $($rest)*
473        )
474    };
475    (@named_ident $name:ident [ $($functions:expr,)* ] [ $($modules:expr,)* ] mod $module_name:ident { $($body:tt)* }) => {
476        $crate::__custom_module_derive_spec!(
477            @named_ident
478            $name
479            [ $($functions,)* ]
480            [
481                $($modules,)*
482                $crate::__custom_module_derive_spec!(@named_ident $module_name [] [] $($body)*),
483            ]
484        )
485    };
486}
487
488#[cfg(test)]
489mod tests {
490    use super::{ConfigSpec, FunctionSpec, ModuleDeriveSpec, ModuleSpec};
491    use crate::BuildConfig;
492
493    const CONFIG_SPEC: ConfigSpec = ConfigSpec {
494        functions: &[FunctionSpec { pragma: "command" }],
495        modules: &[ModuleSpec {
496            pragma: "tooling",
497            derives: &[ModuleDeriveSpec {
498                name: None,
499                functions: &["bootstrap", "init"],
500                modules: &[ModuleDeriveSpec {
501                    name: Some("nested"),
502                    functions: &["run"],
503                    modules: &[],
504                }],
505            }],
506        }],
507    };
508
509    const CONFIG_SPEC_USING_MACROS: ConfigSpec = custom! {
510        fn pragma "command",
511        mod pragma "tooling" {
512            derive {
513                fn bootstrap,
514                fn init,
515                mod nested {
516                    fn run,
517                }
518            }
519        }
520    };
521
522    const PHLOW_SPEC_MACROS: ConfigSpec = custom! {
523        fn pragma "view",
524        mod pragma "extensions" {
525            derive {
526                mod __utilities {
527                    fn phlow_to_string,
528                    fn phlow_type_name,
529                    fn phlow_create_view,
530                    fn phlow_defining_methods,
531                }
532            }
533        }
534    };
535
536    const PHLOW_SPEC: ConfigSpec = ConfigSpec {
537        functions: &[FunctionSpec { pragma: "view" }],
538        modules: &[ModuleSpec {
539            pragma: "extensions",
540            derives: &[ModuleDeriveSpec {
541                name: Some("__utilities"),
542                functions: &[
543                    "phlow_to_string",
544                    "phlow_type_name",
545                    "phlow_create_view",
546                    "phlow_defining_methods",
547                ],
548                modules: &[],
549            }],
550        }],
551    };
552
553    #[test]
554    fn build_config_from_spec_collects_pragmas_and_module_derives() {
555        let config = BuildConfig::from_spec(&CONFIG_SPEC);
556
557        assert_eq!(config.pragmas, vec!["command", "tooling"]);
558        assert!(config.derives.is_empty());
559
560        let tooling_derives = config.module_derives.get("tooling").unwrap();
561        assert_eq!(tooling_derives.len(), 1);
562
563        let root_derive = &tooling_derives[0];
564        assert_eq!(root_derive.function_names(), ["bootstrap", "init"]);
565        assert_eq!(root_derive.modules().len(), 1);
566
567        let nested = &root_derive.modules()[0];
568        assert_eq!(nested.name(), Some("nested"));
569        assert_eq!(nested.function_names(), ["run"]);
570    }
571
572    #[test]
573    fn custom_macro_builds_the_same_config_spec() {
574        assert_eq!(CONFIG_SPEC, CONFIG_SPEC_USING_MACROS);
575    }
576
577    #[test]
578    fn phlow_macro_spec_matches_manual_spec() {
579        assert_eq!(PHLOW_SPEC, PHLOW_SPEC_MACROS);
580    }
581
582    #[test]
583    fn build_config_merge_combines_specs() {
584        const SECOND_SPEC: ConfigSpec = custom! {
585            fn pragma "inspect",
586            mod pragma "extensions" {
587                derive {
588                    mod diagnostics {
589                        fn collect_warnings,
590                    }
591                }
592            }
593        };
594
595        let mut config = BuildConfig::from_spec(&CONFIG_SPEC);
596        config.merge(BuildConfig::from_spec(&SECOND_SPEC));
597
598        assert_eq!(config.pragmas, vec!["command", "tooling", "inspect", "extensions"]);
599
600        let tooling_derives = config.module_derives.get("tooling").unwrap();
601        assert_eq!(tooling_derives.len(), 1);
602
603        let extension_derives = config.module_derives.get("extensions").unwrap();
604        assert_eq!(extension_derives.len(), 1);
605        assert_eq!(extension_derives[0].name(), Some("diagnostics"));
606        assert_eq!(extension_derives[0].function_names(), ["collect_warnings"]);
607    }
608}