1use std::fmt;
6
7use intern::Symbol;
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub enum CfgAtom {
12 Flag(Symbol),
14 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(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")]
69 pub fn parse<S: Copy>(tt: &tt::TopSubtree<S>) -> CfgExpr {
70 next_cfg_expr(&mut tt.iter()).unwrap_or(CfgExpr::Invalid)
71 }
72
73 #[cfg(feature = "tt")]
74 pub fn parse_from_iter<S: Copy>(tt: &mut tt::iter::TtIter<'_, S>) -> CfgExpr {
75 next_cfg_expr(tt).unwrap_or(CfgExpr::Invalid)
76 }
77
78 #[cfg(feature = "syntax")]
79 pub fn parse_from_ast(
80 ast: &mut std::iter::Peekable<syntax::ast::TokenTreeChildren>,
81 ) -> CfgExpr {
82 next_cfg_expr_from_ast(ast).unwrap_or(CfgExpr::Invalid)
83 }
84
85 pub fn fold(&self, query: &dyn Fn(&CfgAtom) -> bool) -> Option<bool> {
87 match self {
88 CfgExpr::Invalid => None,
89 CfgExpr::Atom(atom) => Some(query(atom)),
90 CfgExpr::All(preds) => {
91 preds.iter().try_fold(true, |s, pred| Some(s && pred.fold(query)?))
92 }
93 CfgExpr::Any(preds) => {
94 preds.iter().try_fold(false, |s, pred| Some(s || pred.fold(query)?))
95 }
96 CfgExpr::Not(pred) => pred.fold(query).map(|s| !s),
97 }
98 }
99}
100
101#[cfg(feature = "syntax")]
102fn next_cfg_expr_from_ast(
103 it: &mut std::iter::Peekable<syntax::ast::TokenTreeChildren>,
104) -> Option<CfgExpr> {
105 use intern::sym;
106 use syntax::{NodeOrToken, SyntaxKind, T, ast};
107
108 let name = match it.next() {
109 None => return None,
110 Some(NodeOrToken::Token(ident)) if ident.kind().is_any_identifier() => {
111 Symbol::intern(ident.text())
112 }
113 Some(_) => return Some(CfgExpr::Invalid),
114 };
115
116 let ret = match it.peek() {
117 Some(NodeOrToken::Token(eq)) if eq.kind() == T![=] => {
118 it.next();
119 if let Some(NodeOrToken::Token(literal)) = it.peek()
120 && matches!(literal.kind(), SyntaxKind::STRING)
121 {
122 let literal = tt::token_to_literal(literal.text(), ()).symbol;
123 it.next();
124 CfgAtom::KeyValue { key: name, value: literal.clone() }.into()
125 } else {
126 return Some(CfgExpr::Invalid);
127 }
128 }
129 Some(NodeOrToken::Node(subtree)) => {
130 let mut subtree_iter = ast::TokenTreeChildren::new(subtree).peekable();
131 it.next();
132 let mut subs = std::iter::from_fn(|| next_cfg_expr_from_ast(&mut subtree_iter));
133 match name {
134 s if s == sym::all => CfgExpr::All(subs.collect()),
135 s if s == sym::any => CfgExpr::Any(subs.collect()),
136 s if s == sym::not => {
137 CfgExpr::Not(Box::new(subs.next().unwrap_or(CfgExpr::Invalid)))
138 }
139 _ => CfgExpr::Invalid,
140 }
141 }
142 _ => CfgAtom::Flag(name).into(),
143 };
144
145 while it.next().is_some_and(|it| it.as_token().is_none_or(|it| it.kind() != T![,])) {}
147
148 Some(ret)
149}
150
151#[cfg(feature = "tt")]
152fn next_cfg_expr<S: Copy>(it: &mut tt::iter::TtIter<'_, S>) -> Option<CfgExpr> {
153 use intern::sym;
154 use tt::iter::TtElement;
155
156 let name = match it.next() {
157 None => return None,
158 Some(TtElement::Leaf(tt::Leaf::Ident(ident))) => ident.sym.clone(),
159 Some(_) => return Some(CfgExpr::Invalid),
160 };
161
162 let ret = match it.peek() {
163 Some(TtElement::Leaf(tt::Leaf::Punct(punct)))
164 if punct.char == '='
166 && (punct.spacing == tt::Spacing::Alone
167 || it.remaining().flat_tokens().get(1).is_none_or(|peek2| {
168 !matches!(peek2, tt::TokenTree::Leaf(tt::Leaf::Punct(_)))
169 })) =>
170 {
171 match it.remaining().flat_tokens().get(1) {
172 Some(tt::TokenTree::Leaf(tt::Leaf::Literal(literal))) => {
173 it.next();
174 it.next();
175 CfgAtom::KeyValue { key: name, value: literal.symbol.clone() }.into()
176 }
177 _ => return Some(CfgExpr::Invalid),
178 }
179 }
180 Some(TtElement::Subtree(_, mut sub_it)) => {
181 it.next();
182 let mut subs = std::iter::from_fn(|| next_cfg_expr(&mut sub_it));
183 match name {
184 s if s == sym::all => CfgExpr::All(subs.collect()),
185 s if s == sym::any => CfgExpr::Any(subs.collect()),
186 s if s == sym::not => {
187 CfgExpr::Not(Box::new(subs.next().unwrap_or(CfgExpr::Invalid)))
188 }
189 _ => CfgExpr::Invalid,
190 }
191 }
192 _ => CfgAtom::Flag(name).into(),
193 };
194
195 if let Some(TtElement::Leaf(tt::Leaf::Punct(punct))) = it.peek()
197 && punct.char == ','
198 {
199 it.next();
200 }
201 Some(ret)
202}
203
204#[cfg(test)]
205impl arbitrary::Arbitrary<'_> for CfgAtom {
206 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
207 if u.arbitrary()? {
208 Ok(CfgAtom::Flag(Symbol::intern(<_>::arbitrary(u)?)))
209 } else {
210 Ok(CfgAtom::KeyValue {
211 key: Symbol::intern(<_>::arbitrary(u)?),
212 value: Symbol::intern(<_>::arbitrary(u)?),
213 })
214 }
215 }
216}