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