ra_ap_cfg/
cfg_expr.rs

1//! The condition expression used in `#[cfg(..)]` attributes.
2//!
3//! See: <https://doc.rust-lang.org/reference/conditional-compilation.html#conditional-compilation>
4
5use std::fmt;
6
7use intern::Symbol;
8
9/// A simple configuration value passed in from the outside.
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub enum CfgAtom {
12    /// eg. `#[cfg(test)]`
13    Flag(Symbol),
14    /// eg. `#[cfg(target_os = "linux")]`
15    ///
16    /// Note that a key can have multiple values that are all considered "active" at the same time.
17    /// For example, `#[cfg(target_feature = "sse")]` and `#[cfg(target_feature = "sse2")]`.
18    KeyValue { key: Symbol, value: Symbol },
19}
20
21impl PartialOrd for CfgAtom {
22    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
23        Some(self.cmp(other))
24    }
25}
26
27impl Ord for CfgAtom {
28    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
29        match (self, other) {
30            (CfgAtom::Flag(a), CfgAtom::Flag(b)) => a.as_str().cmp(b.as_str()),
31            (CfgAtom::Flag(_), CfgAtom::KeyValue { .. }) => std::cmp::Ordering::Less,
32            (CfgAtom::KeyValue { .. }, CfgAtom::Flag(_)) => std::cmp::Ordering::Greater,
33            (CfgAtom::KeyValue { key, value }, CfgAtom::KeyValue { key: key2, value: value2 }) => {
34                key.as_str().cmp(key2.as_str()).then(value.as_str().cmp(value2.as_str()))
35            }
36        }
37    }
38}
39
40impl fmt::Display for CfgAtom {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        match self {
43            CfgAtom::Flag(name) => name.fmt(f),
44            CfgAtom::KeyValue { key, value } => write!(f, "{key} = {value:?}"),
45        }
46    }
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Hash)]
50#[cfg_attr(test, derive(derive_arbitrary::Arbitrary))]
51pub enum CfgExpr {
52    Invalid,
53    Atom(CfgAtom),
54    All(Box<[CfgExpr]>),
55    Any(Box<[CfgExpr]>),
56    Not(Box<CfgExpr>),
57}
58
59impl From<CfgAtom> for CfgExpr {
60    fn from(atom: CfgAtom) -> Self {
61        CfgExpr::Atom(atom)
62    }
63}
64
65impl CfgExpr {
66    #[cfg(feature = "tt")]
67    pub fn parse<S: Copy>(tt: &tt::TopSubtree<S>) -> CfgExpr {
68        next_cfg_expr(&mut tt.iter()).unwrap_or(CfgExpr::Invalid)
69    }
70
71    /// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates.
72    pub fn fold(&self, query: &dyn Fn(&CfgAtom) -> bool) -> Option<bool> {
73        match self {
74            CfgExpr::Invalid => None,
75            CfgExpr::Atom(atom) => Some(query(atom)),
76            CfgExpr::All(preds) => {
77                preds.iter().try_fold(true, |s, pred| Some(s && pred.fold(query)?))
78            }
79            CfgExpr::Any(preds) => {
80                preds.iter().try_fold(false, |s, pred| Some(s || pred.fold(query)?))
81            }
82            CfgExpr::Not(pred) => pred.fold(query).map(|s| !s),
83        }
84    }
85}
86
87#[cfg(feature = "tt")]
88fn next_cfg_expr<S: Copy>(it: &mut tt::iter::TtIter<'_, S>) -> Option<CfgExpr> {
89    use intern::sym;
90    use tt::iter::TtElement;
91
92    let name = match it.next() {
93        None => return None,
94        Some(TtElement::Leaf(tt::Leaf::Ident(ident))) => ident.sym.clone(),
95        Some(_) => return Some(CfgExpr::Invalid),
96    };
97
98    let ret = match it.peek() {
99        Some(TtElement::Leaf(tt::Leaf::Punct(punct))) if punct.char == '=' => {
100            match it.remaining().flat_tokens().get(1) {
101                Some(tt::TokenTree::Leaf(tt::Leaf::Literal(literal))) => {
102                    it.next();
103                    it.next();
104                    CfgAtom::KeyValue { key: name, value: literal.symbol.clone() }.into()
105                }
106                _ => return Some(CfgExpr::Invalid),
107            }
108        }
109        Some(TtElement::Subtree(_, mut sub_it)) => {
110            it.next();
111            let mut subs = std::iter::from_fn(|| next_cfg_expr(&mut sub_it));
112            match name {
113                s if s == sym::all => CfgExpr::All(subs.collect()),
114                s if s == sym::any => CfgExpr::Any(subs.collect()),
115                s if s == sym::not => {
116                    CfgExpr::Not(Box::new(subs.next().unwrap_or(CfgExpr::Invalid)))
117                }
118                _ => CfgExpr::Invalid,
119            }
120        }
121        _ => CfgAtom::Flag(name).into(),
122    };
123
124    // Eat comma separator
125    if let Some(TtElement::Leaf(tt::Leaf::Punct(punct))) = it.peek() {
126        if punct.char == ',' {
127            it.next();
128        }
129    }
130    Some(ret)
131}
132
133#[cfg(test)]
134impl arbitrary::Arbitrary<'_> for CfgAtom {
135    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
136        if u.arbitrary()? {
137            Ok(CfgAtom::Flag(Symbol::intern(<_>::arbitrary(u)?)))
138        } else {
139            Ok(CfgAtom::KeyValue {
140                key: Symbol::intern(<_>::arbitrary(u)?),
141                value: Symbol::intern(<_>::arbitrary(u)?),
142            })
143        }
144    }
145}