candela/cfg.rs
1//! Compile-time configuration: the names `@cfg(...)` tests.
2//!
3//! An item or statement written after `@cfg(predicate)` is compiled only when
4//! the predicate holds, and is dropped before name resolution otherwise, so the
5//! code it holds never has to resolve. candela names no configuration of its
6//! own. The program embedding it decides which flags are on, the way a host
7//! compiling for a browser turns on `web`:
8//!
9//! ```no_run
10//! use candela::Cfg;
11//!
12//! let mut cfg = Cfg::new();
13//! cfg.enable("web");
14//! cfg.enable_value("target", "wasm32");
15//! let engine = candela::Engine::new().with_cfg(cfg);
16//! ```
17//!
18//! A compile that does not go through an [`crate::Engine`] runs inside
19//! [`Cfg::scope`] instead. A flag nobody enabled is false.
20
21use rustc_hash::FxHashSet;
22use smol_strc::SmolStr;
23use std::cell::RefCell;
24
25/// The flags and `key = "value"` pairs a compile treats as true.
26#[derive(Clone, Debug, Default, PartialEq, Eq)]
27pub struct Cfg {
28 flags: FxHashSet<SmolStr>,
29 values: FxHashSet<(SmolStr, SmolStr)>,
30}
31
32impl Cfg {
33 /// A configuration with nothing enabled: every `@cfg(flag)` is false.
34 #[must_use]
35 pub fn new() -> Self {
36 Self::default()
37 }
38
39 /// Turns `flag` on, so `@cfg(flag)` holds.
40 pub fn enable(&mut self, flag: &str) {
41 self.flags.insert(SmolStr::new(flag));
42 }
43
44 /// Turns the pair on, so `@cfg(key = "value")` holds. A key may carry
45 /// several values at once; each one is its own pair.
46 pub fn enable_value(&mut self, key: &str, value: &str) {
47 self.values.insert((SmolStr::new(key), SmolStr::new(value)));
48 }
49
50 /// Whether `@cfg(flag)` holds.
51 #[must_use]
52 pub fn is_enabled(&self, flag: &str) -> bool {
53 self.flags.contains(flag)
54 }
55
56 /// Whether `@cfg(key = "value")` holds.
57 #[must_use]
58 pub fn has_value(&self, key: &str, value: &str) -> bool {
59 self.values
60 .contains(&(SmolStr::new(key), SmolStr::new(value)))
61 }
62
63 /// Runs `f` with this configuration active, and returns what it returns.
64 ///
65 /// Anything compiled inside `f` reads its `@cfg` attributes against this
66 /// configuration, including the files it imports. The previous one is
67 /// restored afterwards, including when `f` unwinds.
68 pub fn scope<R>(&self, f: impl FnOnce() -> R) -> R {
69 let _active = Active::push(self.clone());
70 f()
71 }
72}
73
74/// Each name enables one flag.
75impl<S: AsRef<str>> FromIterator<S> for Cfg {
76 fn from_iter<I: IntoIterator<Item = S>>(iter: I) -> Self {
77 let mut cfg = Self::new();
78 for flag in iter {
79 cfg.enable(flag.as_ref());
80 }
81 cfg
82 }
83}
84
85thread_local! {
86 /// The configurations installed by the [`Cfg::scope`] calls currently on
87 /// the stack; the last one is the active one.
88 static ACTIVE: RefCell<Vec<Cfg>> = const { RefCell::new(Vec::new()) };
89}
90
91/// Pops the configuration it pushed when it is dropped, so a compile that
92/// unwinds leaves the stack as it found it.
93struct Active;
94
95impl Active {
96 fn push(cfg: Cfg) -> Self {
97 ACTIVE.with(|stack| stack.borrow_mut().push(cfg));
98 Self
99 }
100}
101
102impl Drop for Active {
103 fn drop(&mut self) {
104 ACTIVE.with(|stack| {
105 stack.borrow_mut().pop();
106 });
107 }
108}
109
110/// The configuration a compile starting now reads: the innermost
111/// [`Cfg::scope`], or nothing enabled outside of one.
112pub(crate) fn active() -> Cfg {
113 ACTIVE.with(|stack| stack.borrow().last().cloned().unwrap_or_default())
114}