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