diff options
Diffstat (limited to 'crates/cfg/src/cfg_expr.rs')
-rw-r--r-- | crates/cfg/src/cfg_expr.rs | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/crates/cfg/src/cfg_expr.rs b/crates/cfg/src/cfg_expr.rs new file mode 100644 index 000000000..336fe25bc --- /dev/null +++ b/crates/cfg/src/cfg_expr.rs | |||
@@ -0,0 +1,133 @@ | |||
1 | //! The condition expression used in `#[cfg(..)]` attributes. | ||
2 | //! | ||
3 | //! See: https://doc.rust-lang.org/reference/conditional-compilation.html#conditional-compilation | ||
4 | |||
5 | use std::slice::Iter as SliceIter; | ||
6 | |||
7 | use tt::SmolStr; | ||
8 | |||
9 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
10 | pub enum CfgExpr { | ||
11 | Invalid, | ||
12 | Atom(SmolStr), | ||
13 | KeyValue { key: SmolStr, value: SmolStr }, | ||
14 | All(Vec<CfgExpr>), | ||
15 | Any(Vec<CfgExpr>), | ||
16 | Not(Box<CfgExpr>), | ||
17 | } | ||
18 | |||
19 | impl CfgExpr { | ||
20 | pub fn parse(tt: &tt::Subtree) -> CfgExpr { | ||
21 | next_cfg_expr(&mut tt.token_trees.iter()).unwrap_or(CfgExpr::Invalid) | ||
22 | } | ||
23 | /// Fold the cfg by querying all basic `Atom` and `KeyValue` predicates. | ||
24 | pub fn fold(&self, query: &dyn Fn(&SmolStr, Option<&SmolStr>) -> bool) -> Option<bool> { | ||
25 | match self { | ||
26 | CfgExpr::Invalid => None, | ||
27 | CfgExpr::Atom(name) => Some(query(name, None)), | ||
28 | CfgExpr::KeyValue { key, value } => Some(query(key, Some(value))), | ||
29 | CfgExpr::All(preds) => { | ||
30 | preds.iter().try_fold(true, |s, pred| Some(s && pred.fold(query)?)) | ||
31 | } | ||
32 | CfgExpr::Any(preds) => { | ||
33 | preds.iter().try_fold(false, |s, pred| Some(s || pred.fold(query)?)) | ||
34 | } | ||
35 | CfgExpr::Not(pred) => pred.fold(query).map(|s| !s), | ||
36 | } | ||
37 | } | ||
38 | } | ||
39 | |||
40 | fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> { | ||
41 | let name = match it.next() { | ||
42 | None => return None, | ||
43 | Some(tt::TokenTree::Leaf(tt::Leaf::Ident(ident))) => ident.text.clone(), | ||
44 | Some(_) => return Some(CfgExpr::Invalid), | ||
45 | }; | ||
46 | |||
47 | // Peek | ||
48 | let ret = match it.as_slice().first() { | ||
49 | Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) if punct.char == '=' => { | ||
50 | match it.as_slice().get(1) { | ||
51 | Some(tt::TokenTree::Leaf(tt::Leaf::Literal(literal))) => { | ||
52 | it.next(); | ||
53 | it.next(); | ||
54 | // FIXME: escape? raw string? | ||
55 | let value = | ||
56 | SmolStr::new(literal.text.trim_start_matches('"').trim_end_matches('"')); | ||
57 | CfgExpr::KeyValue { key: name, value } | ||
58 | } | ||
59 | _ => return Some(CfgExpr::Invalid), | ||
60 | } | ||
61 | } | ||
62 | Some(tt::TokenTree::Subtree(subtree)) => { | ||
63 | it.next(); | ||
64 | let mut sub_it = subtree.token_trees.iter(); | ||
65 | let mut subs = std::iter::from_fn(|| next_cfg_expr(&mut sub_it)).collect(); | ||
66 | match name.as_str() { | ||
67 | "all" => CfgExpr::All(subs), | ||
68 | "any" => CfgExpr::Any(subs), | ||
69 | "not" => CfgExpr::Not(Box::new(subs.pop().unwrap_or(CfgExpr::Invalid))), | ||
70 | _ => CfgExpr::Invalid, | ||
71 | } | ||
72 | } | ||
73 | _ => CfgExpr::Atom(name), | ||
74 | }; | ||
75 | |||
76 | // Eat comma separator | ||
77 | if let Some(tt::TokenTree::Leaf(tt::Leaf::Punct(punct))) = it.as_slice().first() { | ||
78 | if punct.char == ',' { | ||
79 | it.next(); | ||
80 | } | ||
81 | } | ||
82 | Some(ret) | ||
83 | } | ||
84 | |||
85 | #[cfg(test)] | ||
86 | mod tests { | ||
87 | use super::*; | ||
88 | |||
89 | use mbe::ast_to_token_tree; | ||
90 | use syntax::ast::{self, AstNode}; | ||
91 | |||
92 | fn assert_parse_result(input: &str, expected: CfgExpr) { | ||
93 | let (tt, _) = { | ||
94 | let source_file = ast::SourceFile::parse(input).ok().unwrap(); | ||
95 | let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); | ||
96 | ast_to_token_tree(&tt).unwrap() | ||
97 | }; | ||
98 | let cfg = CfgExpr::parse(&tt); | ||
99 | assert_eq!(cfg, expected); | ||
100 | } | ||
101 | |||
102 | #[test] | ||
103 | fn test_cfg_expr_parser() { | ||
104 | assert_parse_result("#![cfg(foo)]", CfgExpr::Atom("foo".into())); | ||
105 | assert_parse_result("#![cfg(foo,)]", CfgExpr::Atom("foo".into())); | ||
106 | assert_parse_result( | ||
107 | "#![cfg(not(foo))]", | ||
108 | CfgExpr::Not(Box::new(CfgExpr::Atom("foo".into()))), | ||
109 | ); | ||
110 | assert_parse_result("#![cfg(foo(bar))]", CfgExpr::Invalid); | ||
111 | |||
112 | // Only take the first | ||
113 | assert_parse_result(r#"#![cfg(foo, bar = "baz")]"#, CfgExpr::Atom("foo".into())); | ||
114 | |||
115 | assert_parse_result( | ||
116 | r#"#![cfg(all(foo, bar = "baz"))]"#, | ||
117 | CfgExpr::All(vec![ | ||
118 | CfgExpr::Atom("foo".into()), | ||
119 | CfgExpr::KeyValue { key: "bar".into(), value: "baz".into() }, | ||
120 | ]), | ||
121 | ); | ||
122 | |||
123 | assert_parse_result( | ||
124 | r#"#![cfg(any(not(), all(), , bar = "baz",))]"#, | ||
125 | CfgExpr::Any(vec![ | ||
126 | CfgExpr::Not(Box::new(CfgExpr::Invalid)), | ||
127 | CfgExpr::All(vec![]), | ||
128 | CfgExpr::Invalid, | ||
129 | CfgExpr::KeyValue { key: "bar".into(), value: "baz".into() }, | ||
130 | ]), | ||
131 | ); | ||
132 | } | ||
133 | } | ||