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 pub fn pragma(&mut self, pragma: impl Into<String>) -> &mut Self {
60 self.pragmas.push(pragma.into());
61 self
62 }
63
64 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}