| use crate::{ |
| error::{ParseError, Reason}, |
| expr::{ |
| lexer::{Lexer, Token}, |
| ExprNode, Expression, Func, InnerPredicate, |
| }, |
| }; |
| use smallvec::SmallVec; |
| |
| impl Expression { |
| /// Given a cfg() expression (the cfg( and ) are optional), attempts to |
| /// parse it into a form where it can be evaluated |
| /// |
| /// ``` |
| /// assert!(cfg_expr::Expression::parse(r#"cfg(all(unix, target_arch = "x86_64"))"#).is_ok()); |
| /// ``` |
| pub fn parse(original: &str) -> Result<Self, ParseError> { |
| let lexer = Lexer::new(original); |
| |
| // The lexer automatically trims any cfg( ), so reacquire |
| // the string before we start walking tokens |
| let original = lexer.inner; |
| |
| #[derive(Debug)] |
| struct FuncAndSpan { |
| func: Func, |
| parens_index: usize, |
| span: std::ops::Range<usize>, |
| predicates: SmallVec<[InnerPredicate; 5]>, |
| nest_level: u8, |
| } |
| |
| let mut func_stack = SmallVec::<[FuncAndSpan; 5]>::new(); |
| let mut expr_queue = SmallVec::<[ExprNode; 5]>::new(); |
| |
| // Keep track of the last token to simplify validation of the token stream |
| let mut last_token: Option<Token<'_>> = None; |
| |
| let parse_predicate = |key: (&str, std::ops::Range<usize>), |
| val: Option<(&str, std::ops::Range<usize>)>| |
| -> Result<InnerPredicate, ParseError> { |
| // Warning: It is possible for arbitrarily-set configuration |
| // options to have the same value as compiler-set configuration |
| // options. For example, it is possible to do rustc --cfg "unix" program.rs |
| // while compiling to a Windows target, and have both unix and windows |
| // configuration options set at the same time. It is unwise to actually |
| // do this. |
| // |
| // rustc is very permissive in this regard, but I'd rather be really |
| // strict, as it's much easier to loosen restrictions over time than add |
| // new ones |
| macro_rules! err_if_val { |
| () => { |
| if let Some((_, vspan)) = val { |
| return Err(ParseError { |
| original: original.to_owned(), |
| span: vspan, |
| reason: Reason::Unexpected(&[]), |
| }); |
| } |
| }; |
| } |
| |
| let span = key.1; |
| let key = key.0; |
| |
| use super::{InnerTarget, Which}; |
| |
| Ok(match key { |
| // These are special cases in the cfg language that are |
| // semantically the same as `target_family = "<family>"`, |
| // so we just make them not special |
| // NOTE: other target families like "wasm" are NOT allowed |
| // as naked predicates; they must be specified through |
| // `target_family` |
| "unix" | "windows" => { |
| err_if_val!(); |
| |
| InnerPredicate::Target(InnerTarget { |
| which: Which::Family, |
| span: Some(span), |
| }) |
| } |
| "test" => { |
| err_if_val!(); |
| InnerPredicate::Test |
| } |
| "debug_assertions" => { |
| err_if_val!(); |
| InnerPredicate::DebugAssertions |
| } |
| "proc_macro" => { |
| err_if_val!(); |
| InnerPredicate::ProcMacro |
| } |
| "feature" => { |
| // rustc allows bare feature without a value, but the only way |
| // such a predicate would ever evaluate to true would be if they |
| // explicitly set --cfg feature, which would be terrible, so we |
| // just error instead |
| match val { |
| Some((_, span)) => InnerPredicate::Feature(span), |
| None => { |
| return Err(ParseError { |
| original: original.to_owned(), |
| span, |
| reason: Reason::Unexpected(&["= \"<feature_name>\""]), |
| }); |
| } |
| } |
| } |
| "panic" => match val { |
| Some((_, vspan)) => InnerPredicate::Target(InnerTarget { |
| which: Which::Panic, |
| span: Some(vspan), |
| }), |
| None => { |
| return Err(ParseError { |
| original: original.to_owned(), |
| span, |
| reason: Reason::Unexpected(&["= \"<panic_strategy>\""]), |
| }); |
| } |
| }, |
| target_key if key.starts_with("target_") => { |
| let (val, vspan) = match val { |
| None => { |
| return Err(ParseError { |
| original: original.to_owned(), |
| span, |
| reason: Reason::Unexpected(&["= \"<target_cfg_value>\""]), |
| }); |
| } |
| Some((val, vspan)) => (val, vspan), |
| }; |
| |
| macro_rules! tp { |
| ($which:ident) => { |
| InnerTarget { |
| which: Which::$which, |
| span: Some(vspan), |
| } |
| }; |
| } |
| |
| let tp = match &target_key[7..] { |
| "abi" => tp!(Abi), |
| "arch" => tp!(Arch), |
| "feature" => { |
| if val.is_empty() { |
| return Err(ParseError { |
| original: original.to_owned(), |
| span: vspan, |
| reason: Reason::Unexpected(&["<feature>"]), |
| }); |
| } |
| |
| return Ok(InnerPredicate::TargetFeature(vspan)); |
| } |
| "os" => tp!(Os), |
| "family" => tp!(Family), |
| "env" => tp!(Env), |
| "endian" => InnerTarget { |
| which: Which::Endian(val.parse().map_err(|_err| ParseError { |
| original: original.to_owned(), |
| span: vspan, |
| reason: Reason::InvalidInteger, |
| })?), |
| span: None, |
| }, |
| "has_atomic" => InnerTarget { |
| which: Which::HasAtomic(val.parse().map_err(|_err| ParseError { |
| original: original.to_owned(), |
| span: vspan, |
| reason: Reason::InvalidHasAtomic, |
| })?), |
| span: None, |
| }, |
| "pointer_width" => InnerTarget { |
| which: Which::PointerWidth(val.parse().map_err(|_err| ParseError { |
| original: original.to_owned(), |
| span: vspan, |
| reason: Reason::InvalidInteger, |
| })?), |
| span: None, |
| }, |
| "vendor" => tp!(Vendor), |
| _ => { |
| return Err(ParseError { |
| original: original.to_owned(), |
| span, |
| reason: Reason::Unexpected(&[ |
| "target_arch", |
| "target_feature", |
| "target_os", |
| "target_family", |
| "target_env", |
| "target_endian", |
| "target_has_atomic", |
| "target_pointer_width", |
| "target_vendor", |
| ]), |
| }) |
| } |
| }; |
| |
| InnerPredicate::Target(tp) |
| } |
| _other => InnerPredicate::Other { |
| identifier: span, |
| value: val.map(|(_, span)| span), |
| }, |
| }) |
| }; |
| |
| macro_rules! token_err { |
| ($span:expr) => {{ |
| let expected: &[&str] = match last_token { |
| None => &["<key>", "all", "any", "not"], |
| Some(Token::All | Token::Any | Token::Not) => &["("], |
| Some(Token::CloseParen) => &[")", ","], |
| Some(Token::Comma) => &[")", "<key>"], |
| Some(Token::Equals) => &["\""], |
| Some(Token::Key(_)) => &["=", ",", ")"], |
| Some(Token::Value(_)) => &[",", ")"], |
| Some(Token::OpenParen) => &["<key>", ")", "all", "any", "not"], |
| }; |
| |
| return Err(ParseError { |
| original: original.to_owned(), |
| span: $span, |
| reason: Reason::Unexpected(&expected), |
| }); |
| }}; |
| } |
| |
| let mut pred_key: Option<(&str, _)> = None; |
| let mut pred_val: Option<(&str, _)> = None; |
| |
| let mut root_predicate_count = 0; |
| |
| // Basic implementation of the https://en.wikipedia.org/wiki/Shunting-yard_algorithm |
| 'outer: for lt in lexer { |
| let lt = lt?; |
| match <.token { |
| Token::Key(k) => { |
| if matches!(last_token, None | Some(Token::OpenParen | Token::Comma)) { |
| pred_key = Some((k, lt.span.clone())); |
| } else { |
| token_err!(lt.span) |
| } |
| } |
| Token::Value(v) => { |
| if matches!(last_token, Some(Token::Equals)) { |
| // We only record the span for keys and values |
| // so that the expression doesn't need a lifetime |
| // but in the value case we need to strip off |
| // the quotes so that the proper raw string is |
| // provided to callers when evaluating the expression |
| pred_val = Some((v, lt.span.start + 1..lt.span.end - 1)); |
| } else { |
| token_err!(lt.span) |
| } |
| } |
| Token::Equals => { |
| if !matches!(last_token, Some(Token::Key(_))) { |
| token_err!(lt.span) |
| } |
| } |
| Token::All | Token::Any | Token::Not => { |
| if matches!(last_token, None | Some(Token::OpenParen | Token::Comma)) { |
| let new_fn = match lt.token { |
| // the 0 is a dummy value -- it will be substituted for the real |
| // number of predicates in the `CloseParen` branch below. |
| Token::All => Func::All(0), |
| Token::Any => Func::Any(0), |
| Token::Not => Func::Not, |
| _ => unreachable!(), |
| }; |
| |
| if let Some(fs) = func_stack.last_mut() { |
| fs.nest_level += 1; |
| } |
| |
| func_stack.push(FuncAndSpan { |
| func: new_fn, |
| span: lt.span, |
| parens_index: 0, |
| predicates: SmallVec::new(), |
| nest_level: 0, |
| }); |
| } else { |
| token_err!(lt.span) |
| } |
| } |
| Token::OpenParen => { |
| if matches!(last_token, Some(Token::All | Token::Any | Token::Not)) { |
| if let Some(ref mut fs) = func_stack.last_mut() { |
| fs.parens_index = lt.span.start; |
| } |
| } else { |
| token_err!(lt.span) |
| } |
| } |
| Token::CloseParen => { |
| if matches!( |
| last_token, |
| None | Some(Token::All | Token::Any | Token::Not | Token::Equals) |
| ) { |
| token_err!(lt.span) |
| } else { |
| if let Some(top) = func_stack.pop() { |
| let key = pred_key.take(); |
| let val = pred_val.take(); |
| |
| // In this context, the boolean to int conversion is confusing. |
| #[allow(clippy::bool_to_int_with_if)] |
| let num_predicates = top.predicates.len() |
| + if key.is_some() { 1 } else { 0 } |
| + top.nest_level as usize; |
| |
| let func = match top.func { |
| Func::All(_) => Func::All(num_predicates), |
| Func::Any(_) => Func::Any(num_predicates), |
| Func::Not => { |
| // not() doesn't take a predicate list, but only a single predicate, |
| // so ensure we have exactly 1 |
| if num_predicates != 1 { |
| return Err(ParseError { |
| original: original.to_owned(), |
| span: top.span.start..lt.span.end, |
| reason: Reason::InvalidNot(num_predicates), |
| }); |
| } |
| |
| Func::Not |
| } |
| }; |
| |
| for pred in top.predicates { |
| expr_queue.push(ExprNode::Predicate(pred)); |
| } |
| |
| if let Some(key) = key { |
| let inner_pred = parse_predicate(key, val)?; |
| expr_queue.push(ExprNode::Predicate(inner_pred)); |
| } |
| |
| expr_queue.push(ExprNode::Fn(func)); |
| |
| // This is the only place we go back to the top of the outer loop, |
| // so make sure we correctly record this token |
| last_token = Some(Token::CloseParen); |
| continue 'outer; |
| } |
| |
| // We didn't have an opening parentheses if we get here |
| return Err(ParseError { |
| original: original.to_owned(), |
| span: lt.span, |
| reason: Reason::UnopenedParens, |
| }); |
| } |
| } |
| Token::Comma => { |
| if matches!( |
| last_token, |
| None | Some( |
| Token::OpenParen | Token::All | Token::Any | Token::Not | Token::Equals |
| ) |
| ) { |
| token_err!(lt.span) |
| } else { |
| let key = pred_key.take(); |
| let val = pred_val.take(); |
| |
| let inner_pred = key.map(|key| parse_predicate(key, val)).transpose()?; |
| |
| match (inner_pred, func_stack.last_mut()) { |
| (Some(pred), Some(func)) => { |
| func.predicates.push(pred); |
| } |
| (Some(pred), None) => { |
| root_predicate_count += 1; |
| |
| expr_queue.push(ExprNode::Predicate(pred)); |
| } |
| _ => {} |
| } |
| } |
| } |
| } |
| |
| last_token = Some(lt.token); |
| } |
| |
| if let Some(Token::Equals) = last_token { |
| return Err(ParseError { |
| original: original.to_owned(), |
| span: original.len()..original.len(), |
| reason: Reason::Unexpected(&["\"<value>\""]), |
| }); |
| } |
| |
| // If we still have functions on the stack, it means we have an unclosed parens |
| if let Some(top) = func_stack.pop() { |
| if top.parens_index != 0 { |
| Err(ParseError { |
| original: original.to_owned(), |
| span: top.parens_index..original.len(), |
| reason: Reason::UnclosedParens, |
| }) |
| } else { |
| Err(ParseError { |
| original: original.to_owned(), |
| span: top.span, |
| reason: Reason::Unexpected(&["("]), |
| }) |
| } |
| } else { |
| let key = pred_key.take(); |
| let val = pred_val.take(); |
| |
| if let Some(key) = key { |
| root_predicate_count += 1; |
| expr_queue.push(ExprNode::Predicate(parse_predicate(key, val)?)); |
| } |
| |
| if expr_queue.is_empty() { |
| Err(ParseError { |
| original: original.to_owned(), |
| span: 0..original.len(), |
| reason: Reason::Empty, |
| }) |
| } else if root_predicate_count > 1 { |
| Err(ParseError { |
| original: original.to_owned(), |
| span: 0..original.len(), |
| reason: Reason::MultipleRootPredicates, |
| }) |
| } else { |
| Ok(Expression { |
| original: original.to_owned(), |
| expr: expr_queue, |
| }) |
| } |
| } |
| } |
| } |