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