cargo_platform/
cfg.rs

1use crate::error::{ParseError, ParseErrorKind::*};
2use std::fmt;
3use std::iter;
4use std::str::{self, FromStr};
5
6/// A cfg expression.
7#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)]
8pub enum CfgExpr {
9    Not(Box<CfgExpr>),
10    All(Vec<CfgExpr>),
11    Any(Vec<CfgExpr>),
12    Value(Cfg),
13}
14
15/// A cfg value.
16#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)]
17pub enum Cfg {
18    /// A named cfg value, like `unix`.
19    Name(String),
20    /// A key/value cfg pair, like `target_os = "linux"`.
21    KeyPair(String, String),
22}
23
24#[derive(PartialEq)]
25enum Token<'a> {
26    LeftParen,
27    RightParen,
28    Ident(&'a str),
29    Comma,
30    Equals,
31    String(&'a str),
32}
33
34#[derive(Clone)]
35struct Tokenizer<'a> {
36    s: iter::Peekable<str::CharIndices<'a>>,
37    orig: &'a str,
38}
39
40struct Parser<'a> {
41    t: Tokenizer<'a>,
42}
43
44impl FromStr for Cfg {
45    type Err = ParseError;
46
47    fn from_str(s: &str) -> Result<Cfg, Self::Err> {
48        let mut p = Parser::new(s);
49        let e = p.cfg()?;
50        if let Some(rest) = p.rest() {
51            return Err(ParseError::new(
52                p.t.orig,
53                UnterminatedExpression(rest.to_string()),
54            ));
55        }
56        Ok(e)
57    }
58}
59
60impl fmt::Display for Cfg {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        match *self {
63            Cfg::Name(ref s) => s.fmt(f),
64            Cfg::KeyPair(ref k, ref v) => write!(f, "{} = \"{}\"", k, v),
65        }
66    }
67}
68
69impl CfgExpr {
70    /// Utility function to check if the key, "cfg(..)" matches the `target_cfg`
71    pub fn matches_key(key: &str, target_cfg: &[Cfg]) -> bool {
72        if key.starts_with("cfg(") && key.ends_with(')') {
73            let cfg = &key[4..key.len() - 1];
74
75            CfgExpr::from_str(cfg)
76                .ok()
77                .map(|ce| ce.matches(target_cfg))
78                .unwrap_or(false)
79        } else {
80            false
81        }
82    }
83
84    pub fn matches(&self, cfg: &[Cfg]) -> bool {
85        match *self {
86            CfgExpr::Not(ref e) => !e.matches(cfg),
87            CfgExpr::All(ref e) => e.iter().all(|e| e.matches(cfg)),
88            CfgExpr::Any(ref e) => e.iter().any(|e| e.matches(cfg)),
89            CfgExpr::Value(ref e) => cfg.contains(e),
90        }
91    }
92}
93
94impl FromStr for CfgExpr {
95    type Err = ParseError;
96
97    fn from_str(s: &str) -> Result<CfgExpr, Self::Err> {
98        let mut p = Parser::new(s);
99        let e = p.expr()?;
100        if let Some(rest) = p.rest() {
101            return Err(ParseError::new(
102                p.t.orig,
103                UnterminatedExpression(rest.to_string()),
104            ));
105        }
106        Ok(e)
107    }
108}
109
110impl fmt::Display for CfgExpr {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        match *self {
113            CfgExpr::Not(ref e) => write!(f, "not({})", e),
114            CfgExpr::All(ref e) => write!(f, "all({})", CommaSep(e)),
115            CfgExpr::Any(ref e) => write!(f, "any({})", CommaSep(e)),
116            CfgExpr::Value(ref e) => write!(f, "{}", e),
117        }
118    }
119}
120
121struct CommaSep<'a, T>(&'a [T]);
122
123impl<'a, T: fmt::Display> fmt::Display for CommaSep<'a, T> {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        for (i, v) in self.0.iter().enumerate() {
126            if i > 0 {
127                write!(f, ", ")?;
128            }
129            write!(f, "{}", v)?;
130        }
131        Ok(())
132    }
133}
134
135impl<'a> Parser<'a> {
136    fn new(s: &'a str) -> Parser<'a> {
137        Parser {
138            t: Tokenizer {
139                s: s.char_indices().peekable(),
140                orig: s,
141            },
142        }
143    }
144
145    fn expr(&mut self) -> Result<CfgExpr, ParseError> {
146        match self.peek() {
147            Some(Ok(Token::Ident(op @ "all"))) | Some(Ok(Token::Ident(op @ "any"))) => {
148                self.t.next();
149                let mut e = Vec::new();
150                self.eat(&Token::LeftParen)?;
151                while !self.r#try(&Token::RightParen) {
152                    e.push(self.expr()?);
153                    if !self.r#try(&Token::Comma) {
154                        self.eat(&Token::RightParen)?;
155                        break;
156                    }
157                }
158                if op == "all" {
159                    Ok(CfgExpr::All(e))
160                } else {
161                    Ok(CfgExpr::Any(e))
162                }
163            }
164            Some(Ok(Token::Ident("not"))) => {
165                self.t.next();
166                self.eat(&Token::LeftParen)?;
167                let e = self.expr()?;
168                self.eat(&Token::RightParen)?;
169                Ok(CfgExpr::Not(Box::new(e)))
170            }
171            Some(Ok(..)) => self.cfg().map(CfgExpr::Value),
172            Some(Err(..)) => Err(self.t.next().unwrap().err().unwrap()),
173            None => Err(ParseError::new(
174                self.t.orig,
175                IncompleteExpr("start of a cfg expression"),
176            )),
177        }
178    }
179
180    fn cfg(&mut self) -> Result<Cfg, ParseError> {
181        match self.t.next() {
182            Some(Ok(Token::Ident(name))) => {
183                let e = if self.r#try(&Token::Equals) {
184                    let val = match self.t.next() {
185                        Some(Ok(Token::String(s))) => s,
186                        Some(Ok(t)) => {
187                            return Err(ParseError::new(
188                                self.t.orig,
189                                UnexpectedToken {
190                                    expected: "a string",
191                                    found: t.classify(),
192                                },
193                            ))
194                        }
195                        Some(Err(e)) => return Err(e),
196                        None => {
197                            return Err(ParseError::new(self.t.orig, IncompleteExpr("a string")))
198                        }
199                    };
200                    Cfg::KeyPair(name.to_string(), val.to_string())
201                } else {
202                    Cfg::Name(name.to_string())
203                };
204                Ok(e)
205            }
206            Some(Ok(t)) => Err(ParseError::new(
207                self.t.orig,
208                UnexpectedToken {
209                    expected: "identifier",
210                    found: t.classify(),
211                },
212            )),
213            Some(Err(e)) => Err(e),
214            None => Err(ParseError::new(self.t.orig, IncompleteExpr("identifier"))),
215        }
216    }
217
218    fn peek(&mut self) -> Option<Result<Token<'a>, ParseError>> {
219        self.t.clone().next()
220    }
221
222    fn r#try(&mut self, token: &Token<'a>) -> bool {
223        match self.peek() {
224            Some(Ok(ref t)) if token == t => {}
225            _ => return false,
226        }
227        self.t.next();
228        true
229    }
230
231    fn eat(&mut self, token: &Token<'a>) -> Result<(), ParseError> {
232        match self.t.next() {
233            Some(Ok(ref t)) if token == t => Ok(()),
234            Some(Ok(t)) => Err(ParseError::new(
235                self.t.orig,
236                UnexpectedToken {
237                    expected: token.classify(),
238                    found: t.classify(),
239                },
240            )),
241            Some(Err(e)) => Err(e),
242            None => Err(ParseError::new(
243                self.t.orig,
244                IncompleteExpr(token.classify()),
245            )),
246        }
247    }
248
249    /// Returns the rest of the input from the current location.
250    fn rest(&self) -> Option<&str> {
251        let mut s = self.t.s.clone();
252        loop {
253            match s.next() {
254                Some((_, ' ')) => {}
255                Some((start, _ch)) => return Some(&self.t.orig[start..]),
256                None => return None,
257            }
258        }
259    }
260}
261
262impl<'a> Iterator for Tokenizer<'a> {
263    type Item = Result<Token<'a>, ParseError>;
264
265    fn next(&mut self) -> Option<Result<Token<'a>, ParseError>> {
266        loop {
267            match self.s.next() {
268                Some((_, ' ')) => {}
269                Some((_, '(')) => return Some(Ok(Token::LeftParen)),
270                Some((_, ')')) => return Some(Ok(Token::RightParen)),
271                Some((_, ',')) => return Some(Ok(Token::Comma)),
272                Some((_, '=')) => return Some(Ok(Token::Equals)),
273                Some((start, '"')) => {
274                    while let Some((end, ch)) = self.s.next() {
275                        if ch == '"' {
276                            return Some(Ok(Token::String(&self.orig[start + 1..end])));
277                        }
278                    }
279                    return Some(Err(ParseError::new(self.orig, UnterminatedString)));
280                }
281                Some((start, ch)) if is_ident_start(ch) => {
282                    while let Some(&(end, ch)) = self.s.peek() {
283                        if !is_ident_rest(ch) {
284                            return Some(Ok(Token::Ident(&self.orig[start..end])));
285                        } else {
286                            self.s.next();
287                        }
288                    }
289                    return Some(Ok(Token::Ident(&self.orig[start..])));
290                }
291                Some((_, ch)) => {
292                    return Some(Err(ParseError::new(self.orig, UnexpectedChar(ch))));
293                }
294                None => return None,
295            }
296        }
297    }
298}
299
300fn is_ident_start(ch: char) -> bool {
301    ch == '_' || ch.is_ascii_alphabetic()
302}
303
304fn is_ident_rest(ch: char) -> bool {
305    is_ident_start(ch) || ch.is_ascii_digit()
306}
307
308impl<'a> Token<'a> {
309    fn classify(&self) -> &'static str {
310        match *self {
311            Token::LeftParen => "`(`",
312            Token::RightParen => "`)`",
313            Token::Ident(..) => "an identifier",
314            Token::Comma => "`,`",
315            Token::Equals => "`=`",
316            Token::String(..) => "a string",
317        }
318    }
319}