use crate::error::{ParseError, ParseErrorKind::*}; use std::fmt; use std::iter; use std::str::{self, FromStr}; /// A cfg expression. #[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)] pub enum CfgExpr { Not(Box), All(Vec), Any(Vec), Value(Cfg), } /// A cfg value. #[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)] pub enum Cfg { /// A named cfg value, like `unix`. Name(String), /// A key/value cfg pair, like `target_os = "linux"`. KeyPair(String, String), } #[derive(PartialEq)] enum Token<'a> { LeftParen, RightParen, Ident(&'a str), Comma, Equals, String(&'a str), } #[derive(Clone)] struct Tokenizer<'a> { s: iter::Peekable>, orig: &'a str, } struct Parser<'a> { t: Tokenizer<'a>, } impl FromStr for Cfg { type Err = ParseError; fn from_str(s: &str) -> Result { let mut p = Parser::new(s); let e = p.cfg()?; if let Some(rest) = p.rest() { return Err(ParseError::new( p.t.orig, UnterminatedExpression(rest.to_string()), )); } Ok(e) } } impl fmt::Display for Cfg { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Cfg::Name(ref s) => s.fmt(f), Cfg::KeyPair(ref k, ref v) => write!(f, "{} = \"{}\"", k, v), } } } impl CfgExpr { /// Utility function to check if the key, "cfg(..)" matches the `target_cfg` pub fn matches_key(key: &str, target_cfg: &[Cfg]) -> bool { if key.starts_with("cfg(") && key.ends_with(')') { let cfg = &key[4..key.len() - 1]; CfgExpr::from_str(cfg) .ok() .map(|ce| ce.matches(target_cfg)) .unwrap_or(false) } else { false } } pub fn matches(&self, cfg: &[Cfg]) -> bool { match *self { CfgExpr::Not(ref e) => !e.matches(cfg), CfgExpr::All(ref e) => e.iter().all(|e| e.matches(cfg)), CfgExpr::Any(ref e) => e.iter().any(|e| e.matches(cfg)), CfgExpr::Value(ref e) => cfg.contains(e), } } } impl FromStr for CfgExpr { type Err = ParseError; fn from_str(s: &str) -> Result { let mut p = Parser::new(s); let e = p.expr()?; if let Some(rest) = p.rest() { return Err(ParseError::new( p.t.orig, UnterminatedExpression(rest.to_string()), )); } Ok(e) } } impl fmt::Display for CfgExpr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { CfgExpr::Not(ref e) => write!(f, "not({})", e), CfgExpr::All(ref e) => write!(f, "all({})", CommaSep(e)), CfgExpr::Any(ref e) => write!(f, "any({})", CommaSep(e)), CfgExpr::Value(ref e) => write!(f, "{}", e), } } } struct CommaSep<'a, T>(&'a [T]); impl<'a, T: fmt::Display> fmt::Display for CommaSep<'a, T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for (i, v) in self.0.iter().enumerate() { if i > 0 { write!(f, ", ")?; } write!(f, "{}", v)?; } Ok(()) } } impl<'a> Parser<'a> { fn new(s: &'a str) -> Parser<'a> { Parser { t: Tokenizer { s: s.char_indices().peekable(), orig: s, }, } } fn expr(&mut self) -> Result { match self.peek() { Some(Ok(Token::Ident(op @ "all"))) | Some(Ok(Token::Ident(op @ "any"))) => { self.t.next(); let mut e = Vec::new(); self.eat(&Token::LeftParen)?; while !self.r#try(&Token::RightParen) { e.push(self.expr()?); if !self.r#try(&Token::Comma) { self.eat(&Token::RightParen)?; break; } } if op == "all" { Ok(CfgExpr::All(e)) } else { Ok(CfgExpr::Any(e)) } } Some(Ok(Token::Ident("not"))) => { self.t.next(); self.eat(&Token::LeftParen)?; let e = self.expr()?; self.eat(&Token::RightParen)?; Ok(CfgExpr::Not(Box::new(e))) } Some(Ok(..)) => self.cfg().map(CfgExpr::Value), Some(Err(..)) => Err(self.t.next().unwrap().err().unwrap()), None => Err(ParseError::new( self.t.orig, IncompleteExpr("start of a cfg expression"), )), } } fn cfg(&mut self) -> Result { match self.t.next() { Some(Ok(Token::Ident(name))) => { let e = if self.r#try(&Token::Equals) { let val = match self.t.next() { Some(Ok(Token::String(s))) => s, Some(Ok(t)) => { return Err(ParseError::new( self.t.orig, UnexpectedToken { expected: "a string", found: t.classify(), }, )) } Some(Err(e)) => return Err(e), None => { return Err(ParseError::new(self.t.orig, IncompleteExpr("a string"))) } }; Cfg::KeyPair(name.to_string(), val.to_string()) } else { Cfg::Name(name.to_string()) }; Ok(e) } Some(Ok(t)) => Err(ParseError::new( self.t.orig, UnexpectedToken { expected: "identifier", found: t.classify(), }, )), Some(Err(e)) => Err(e), None => Err(ParseError::new(self.t.orig, IncompleteExpr("identifier"))), } } fn peek(&mut self) -> Option, ParseError>> { self.t.clone().next() } fn r#try(&mut self, token: &Token<'a>) -> bool { match self.peek() { Some(Ok(ref t)) if token == t => {} _ => return false, } self.t.next(); true } fn eat(&mut self, token: &Token<'a>) -> Result<(), ParseError> { match self.t.next() { Some(Ok(ref t)) if token == t => Ok(()), Some(Ok(t)) => Err(ParseError::new( self.t.orig, UnexpectedToken { expected: token.classify(), found: t.classify(), }, )), Some(Err(e)) => Err(e), None => Err(ParseError::new( self.t.orig, IncompleteExpr(token.classify()), )), } } /// Returns the rest of the input from the current location. fn rest(&self) -> Option<&str> { let mut s = self.t.s.clone(); loop { match s.next() { Some((_, ' ')) => {} Some((start, _ch)) => return Some(&self.t.orig[start..]), None => return None, } } } } impl<'a> Iterator for Tokenizer<'a> { type Item = Result, ParseError>; fn next(&mut self) -> Option, ParseError>> { loop { match self.s.next() { Some((_, ' ')) => {} Some((_, '(')) => return Some(Ok(Token::LeftParen)), Some((_, ')')) => return Some(Ok(Token::RightParen)), Some((_, ',')) => return Some(Ok(Token::Comma)), Some((_, '=')) => return Some(Ok(Token::Equals)), Some((start, '"')) => { while let Some((end, ch)) = self.s.next() { if ch == '"' { return Some(Ok(Token::String(&self.orig[start + 1..end]))); } } return Some(Err(ParseError::new(self.orig, UnterminatedString))); } Some((start, ch)) if is_ident_start(ch) => { while let Some(&(end, ch)) = self.s.peek() { if !is_ident_rest(ch) { return Some(Ok(Token::Ident(&self.orig[start..end]))); } else { self.s.next(); } } return Some(Ok(Token::Ident(&self.orig[start..]))); } Some((_, ch)) => { return Some(Err(ParseError::new(self.orig, UnexpectedChar(ch)))); } None => return None, } } } } fn is_ident_start(ch: char) -> bool { ch == '_' || ch.is_ascii_alphabetic() } fn is_ident_rest(ch: char) -> bool { is_ident_start(ch) || ch.is_ascii_digit() } impl<'a> Token<'a> { fn classify(&self) -> &'static str { match *self { Token::LeftParen => "`(`", Token::RightParen => "`)`", Token::Ident(..) => "an identifier", Token::Comma => "`,`", Token::Equals => "`=`", Token::String(..) => "a string", } } }