diff options
Diffstat (limited to 'third_party/rust/cargo-platform/src')
-rw-r--r-- | third_party/rust/cargo-platform/src/cfg.rs | 319 | ||||
-rw-r--r-- | third_party/rust/cargo-platform/src/error.rs | 67 | ||||
-rw-r--r-- | third_party/rust/cargo-platform/src/lib.rs | 146 |
3 files changed, 532 insertions, 0 deletions
diff --git a/third_party/rust/cargo-platform/src/cfg.rs b/third_party/rust/cargo-platform/src/cfg.rs new file mode 100644 index 0000000000..c3ddb69bc8 --- /dev/null +++ b/third_party/rust/cargo-platform/src/cfg.rs @@ -0,0 +1,319 @@ +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<CfgExpr>), + All(Vec<CfgExpr>), + Any(Vec<CfgExpr>), + 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<str::CharIndices<'a>>, + orig: &'a str, +} + +struct Parser<'a> { + t: Tokenizer<'a>, +} + +impl FromStr for Cfg { + type Err = ParseError; + + fn from_str(s: &str) -> Result<Cfg, Self::Err> { + 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<CfgExpr, Self::Err> { + 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<CfgExpr, ParseError> { + 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<Cfg, ParseError> { + 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<Result<Token<'a>, 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<Token<'a>, ParseError>; + + fn next(&mut self) -> Option<Result<Token<'a>, 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", + } + } +} diff --git a/third_party/rust/cargo-platform/src/error.rs b/third_party/rust/cargo-platform/src/error.rs new file mode 100644 index 0000000000..bf4b35f271 --- /dev/null +++ b/third_party/rust/cargo-platform/src/error.rs @@ -0,0 +1,67 @@ +use std::fmt; + +#[derive(Debug)] +pub struct ParseError { + kind: ParseErrorKind, + orig: String, +} + +#[non_exhaustive] +#[derive(Debug)] +pub enum ParseErrorKind { + UnterminatedString, + UnexpectedChar(char), + UnexpectedToken { + expected: &'static str, + found: &'static str, + }, + IncompleteExpr(&'static str), + UnterminatedExpression(String), + InvalidTarget(String), +} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "failed to parse `{}` as a cfg expression: {}", + self.orig, self.kind + ) + } +} + +impl fmt::Display for ParseErrorKind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use ParseErrorKind::*; + match self { + UnterminatedString => write!(f, "unterminated string in cfg"), + UnexpectedChar(ch) => write!( + f, + "unexpected character `{}` in cfg, expected parens, a comma, \ + an identifier, or a string", + ch + ), + UnexpectedToken { expected, found } => { + write!(f, "expected {}, found {}", expected, found) + } + IncompleteExpr(expected) => { + write!(f, "expected {}, but cfg expression ended", expected) + } + UnterminatedExpression(s) => { + write!(f, "unexpected content `{}` found after cfg expression", s) + } + InvalidTarget(s) => write!(f, "invalid target specifier: {}", s), + } + } +} + +impl std::error::Error for ParseError {} + +impl ParseError { + pub fn new(orig: &str, kind: ParseErrorKind) -> ParseError { + ParseError { + kind, + orig: orig.to_string(), + } + } +} diff --git a/third_party/rust/cargo-platform/src/lib.rs b/third_party/rust/cargo-platform/src/lib.rs new file mode 100644 index 0000000000..0a3dcf1af1 --- /dev/null +++ b/third_party/rust/cargo-platform/src/lib.rs @@ -0,0 +1,146 @@ +//! Platform definition used by Cargo. +//! +//! This defines a [`Platform`] type which is used in Cargo to specify a target platform. +//! There are two kinds, a named target like `x86_64-apple-darwin`, and a "cfg expression" +//! like `cfg(any(target_os = "macos", target_os = "ios"))`. +//! +//! See `examples/matches.rs` for an example of how to match against a `Platform`. +//! +//! [`Platform`]: enum.Platform.html + +use std::fmt; +use std::str::FromStr; + +mod cfg; +mod error; + +pub use cfg::{Cfg, CfgExpr}; +pub use error::{ParseError, ParseErrorKind}; + +/// Platform definition. +#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)] +pub enum Platform { + /// A named platform, like `x86_64-apple-darwin`. + Name(String), + /// A cfg expression, like `cfg(windows)`. + Cfg(CfgExpr), +} + +impl Platform { + /// Returns whether the Platform matches the given target and cfg. + /// + /// The named target and cfg values should be obtained from `rustc`. + pub fn matches(&self, name: &str, cfg: &[Cfg]) -> bool { + match *self { + Platform::Name(ref p) => p == name, + Platform::Cfg(ref p) => p.matches(cfg), + } + } + + fn validate_named_platform(name: &str) -> Result<(), ParseError> { + if let Some(ch) = name + .chars() + .find(|&c| !(c.is_alphanumeric() || c == '_' || c == '-' || c == '.')) + { + if name.chars().any(|c| c == '(') { + return Err(ParseError::new( + name, + ParseErrorKind::InvalidTarget( + "unexpected `(` character, cfg expressions must start with `cfg(`" + .to_string(), + ), + )); + } + return Err(ParseError::new( + name, + ParseErrorKind::InvalidTarget(format!( + "unexpected character {} in target name", + ch + )), + )); + } + Ok(()) + } + + pub fn check_cfg_attributes(&self, warnings: &mut Vec<String>) { + fn check_cfg_expr(expr: &CfgExpr, warnings: &mut Vec<String>) { + match *expr { + CfgExpr::Not(ref e) => check_cfg_expr(e, warnings), + CfgExpr::All(ref e) | CfgExpr::Any(ref e) => { + for e in e { + check_cfg_expr(e, warnings); + } + } + CfgExpr::Value(ref e) => match e { + Cfg::Name(name) => match name.as_str() { + "test" | "debug_assertions" | "proc_macro" => + warnings.push(format!( + "Found `{}` in `target.'cfg(...)'.dependencies`. \ + This value is not supported for selecting dependencies \ + and will not work as expected. \ + To learn more visit \ + https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#platform-specific-dependencies", + name + )), + _ => (), + }, + Cfg::KeyPair(name, _) => if name.as_str() == "feature" { + warnings.push(String::from( + "Found `feature = ...` in `target.'cfg(...)'.dependencies`. \ + This key is not supported for selecting dependencies \ + and will not work as expected. \ + Use the [features] section instead: \ + https://doc.rust-lang.org/cargo/reference/features.html" + )) + }, + } + } + } + + if let Platform::Cfg(cfg) = self { + check_cfg_expr(cfg, warnings); + } + } +} + +impl serde::Serialize for Platform { + fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + self.to_string().serialize(s) + } +} + +impl<'de> serde::Deserialize<'de> for Platform { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + FromStr::from_str(&s).map_err(serde::de::Error::custom) + } +} + +impl FromStr for Platform { + type Err = ParseError; + + fn from_str(s: &str) -> Result<Platform, ParseError> { + if s.starts_with("cfg(") && s.ends_with(')') { + let s = &s[4..s.len() - 1]; + s.parse().map(Platform::Cfg) + } else { + Platform::validate_named_platform(s)?; + Ok(Platform::Name(s.to_string())) + } + } +} + +impl fmt::Display for Platform { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Platform::Name(ref n) => n.fmt(f), + Platform::Cfg(ref e) => write!(f, "cfg({})", e), + } + } +} |