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