cargo_platform/
cfg.rs

1use crate::error::{ParseError, ParseErrorKind::*};
2use std::fmt;
3use std::hash::{Hash, Hasher};
4use std::iter;
5use std::str::{self, FromStr};
6
7/// A cfg expression.
8#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)]
9pub enum CfgExpr {
10    Not(Box<CfgExpr>),
11    All(Vec<CfgExpr>),
12    Any(Vec<CfgExpr>),
13    Value(Cfg),
14    True,
15    False,
16}
17
18/// A cfg value.
19#[derive(Eq, PartialEq, Hash, Ord, PartialOrd, Clone, Debug)]
20pub enum Cfg {
21    /// A named cfg value, like `unix`.
22    Name(Ident),
23    /// A key/value cfg pair, like `target_os = "linux"`.
24    KeyPair(Ident, String),
25}
26
27/// A identifier
28#[derive(Eq, Ord, PartialOrd, Clone, Debug)]
29pub struct Ident {
30    /// The identifier
31    pub name: String,
32    /// Is this a raw ident: `r#async`
33    ///
34    /// It's mainly used for display and doesn't take
35    /// part in the hash or equality (`foo` == `r#foo`).
36    pub raw: bool,
37}
38
39#[derive(PartialEq)]
40enum Token<'a> {
41    LeftParen,
42    RightParen,
43    Ident(bool, &'a str),
44    Comma,
45    Equals,
46    String(&'a str),
47}
48
49/// The list of keywords.
50///
51/// We should consider all the keywords, but some are conditional on
52/// the edition so for now we just consider true/false.
53///
54/// <https://doc.rust-lang.org/reference/keywords.html>
55pub(crate) const KEYWORDS: &[&str; 2] = &["true", "false"];
56
57#[derive(Clone)]
58struct Tokenizer<'a> {
59    s: iter::Peekable<str::CharIndices<'a>>,
60    orig: &'a str,
61}
62
63struct Parser<'a> {
64    t: Tokenizer<'a>,
65}
66
67impl Ident {
68    pub fn as_str(&self) -> &str {
69        &self.name
70    }
71}
72
73impl Hash for Ident {
74    fn hash<H: Hasher>(&self, state: &mut H) {
75        self.name.hash(state);
76    }
77}
78
79impl PartialEq<str> for Ident {
80    fn eq(&self, other: &str) -> bool {
81        self.name == other
82    }
83}
84
85impl PartialEq<&str> for Ident {
86    fn eq(&self, other: &&str) -> bool {
87        self.name == *other
88    }
89}
90
91impl PartialEq<Ident> for Ident {
92    fn eq(&self, other: &Ident) -> bool {
93        self.name == other.name
94    }
95}
96
97impl fmt::Display for Ident {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        if self.raw {
100            f.write_str("r#")?;
101        }
102        f.write_str(&*self.name)
103    }
104}
105
106impl FromStr for Cfg {
107    type Err = ParseError;
108
109    fn from_str(s: &str) -> Result<Cfg, Self::Err> {
110        let mut p = Parser::new(s);
111        let e = p.cfg()?;
112        if let Some(rest) = p.rest() {
113            return Err(ParseError::new(
114                p.t.orig,
115                UnterminatedExpression(rest.to_string()),
116            ));
117        }
118        Ok(e)
119    }
120}
121
122impl fmt::Display for Cfg {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        match *self {
125            Cfg::Name(ref s) => s.fmt(f),
126            Cfg::KeyPair(ref k, ref v) => write!(f, "{} = \"{}\"", k, v),
127        }
128    }
129}
130
131impl CfgExpr {
132    /// Utility function to check if the key, "cfg(..)" matches the `target_cfg`
133    pub fn matches_key(key: &str, target_cfg: &[Cfg]) -> bool {
134        if key.starts_with("cfg(") && key.ends_with(')') {
135            let cfg = &key[4..key.len() - 1];
136
137            CfgExpr::from_str(cfg)
138                .ok()
139                .map(|ce| ce.matches(target_cfg))
140                .unwrap_or(false)
141        } else {
142            false
143        }
144    }
145
146    pub fn matches(&self, cfg: &[Cfg]) -> bool {
147        match *self {
148            CfgExpr::Not(ref e) => !e.matches(cfg),
149            CfgExpr::All(ref e) => e.iter().all(|e| e.matches(cfg)),
150            CfgExpr::Any(ref e) => e.iter().any(|e| e.matches(cfg)),
151            CfgExpr::Value(ref e) => cfg.contains(e),
152            CfgExpr::True => true,
153            CfgExpr::False => false,
154        }
155    }
156}
157
158impl FromStr for CfgExpr {
159    type Err = ParseError;
160
161    fn from_str(s: &str) -> Result<CfgExpr, Self::Err> {
162        let mut p = Parser::new(s);
163        let e = p.expr()?;
164        if let Some(rest) = p.rest() {
165            return Err(ParseError::new(
166                p.t.orig,
167                UnterminatedExpression(rest.to_string()),
168            ));
169        }
170        Ok(e)
171    }
172}
173
174impl fmt::Display for CfgExpr {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        match *self {
177            CfgExpr::Not(ref e) => write!(f, "not({})", e),
178            CfgExpr::All(ref e) => write!(f, "all({})", CommaSep(e)),
179            CfgExpr::Any(ref e) => write!(f, "any({})", CommaSep(e)),
180            CfgExpr::Value(ref e) => write!(f, "{}", e),
181            CfgExpr::True => write!(f, "true"),
182            CfgExpr::False => write!(f, "false"),
183        }
184    }
185}
186
187struct CommaSep<'a, T>(&'a [T]);
188
189impl<'a, T: fmt::Display> fmt::Display for CommaSep<'a, T> {
190    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191        for (i, v) in self.0.iter().enumerate() {
192            if i > 0 {
193                write!(f, ", ")?;
194            }
195            write!(f, "{}", v)?;
196        }
197        Ok(())
198    }
199}
200
201impl<'a> Parser<'a> {
202    fn new(s: &'a str) -> Parser<'a> {
203        Parser {
204            t: Tokenizer {
205                s: s.char_indices().peekable(),
206                orig: s,
207            },
208        }
209    }
210
211    fn expr(&mut self) -> Result<CfgExpr, ParseError> {
212        match self.peek() {
213            Some(Ok(Token::Ident(false, op @ "all")))
214            | Some(Ok(Token::Ident(false, op @ "any"))) => {
215                self.t.next();
216                let mut e = Vec::new();
217                self.eat(&Token::LeftParen)?;
218                while !self.r#try(&Token::RightParen) {
219                    e.push(self.expr()?);
220                    if !self.r#try(&Token::Comma) {
221                        self.eat(&Token::RightParen)?;
222                        break;
223                    }
224                }
225                if op == "all" {
226                    Ok(CfgExpr::All(e))
227                } else {
228                    Ok(CfgExpr::Any(e))
229                }
230            }
231            Some(Ok(Token::Ident(false, "not"))) => {
232                self.t.next();
233                self.eat(&Token::LeftParen)?;
234                let e = self.expr()?;
235                self.eat(&Token::RightParen)?;
236                Ok(CfgExpr::Not(Box::new(e)))
237            }
238            Some(Ok(..)) => self.cfg().map(|v| match v {
239                Cfg::Name(n) if n == "true" => CfgExpr::True,
240                Cfg::Name(n) if n == "false" => CfgExpr::False,
241                v => CfgExpr::Value(v),
242            }),
243            Some(Err(..)) => Err(self.t.next().unwrap().err().unwrap()),
244            None => Err(ParseError::new(
245                self.t.orig,
246                IncompleteExpr("start of a cfg expression"),
247            )),
248        }
249    }
250
251    fn cfg(&mut self) -> Result<Cfg, ParseError> {
252        match self.t.next() {
253            Some(Ok(Token::Ident(raw, name))) => {
254                let e = if self.r#try(&Token::Equals) {
255                    let val = match self.t.next() {
256                        Some(Ok(Token::String(s))) => s,
257                        Some(Ok(t)) => {
258                            return Err(ParseError::new(
259                                self.t.orig,
260                                UnexpectedToken {
261                                    expected: "a string",
262                                    found: t.classify(),
263                                },
264                            ))
265                        }
266                        Some(Err(e)) => return Err(e),
267                        None => {
268                            return Err(ParseError::new(self.t.orig, IncompleteExpr("a string")))
269                        }
270                    };
271                    Cfg::KeyPair(
272                        Ident {
273                            name: name.to_string(),
274                            raw,
275                        },
276                        val.to_string(),
277                    )
278                } else {
279                    Cfg::Name(Ident {
280                        name: name.to_string(),
281                        raw,
282                    })
283                };
284                Ok(e)
285            }
286            Some(Ok(t)) => Err(ParseError::new(
287                self.t.orig,
288                UnexpectedToken {
289                    expected: "identifier",
290                    found: t.classify(),
291                },
292            )),
293            Some(Err(e)) => Err(e),
294            None => Err(ParseError::new(self.t.orig, IncompleteExpr("identifier"))),
295        }
296    }
297
298    fn peek(&mut self) -> Option<Result<Token<'a>, ParseError>> {
299        self.t.clone().next()
300    }
301
302    fn r#try(&mut self, token: &Token<'a>) -> bool {
303        match self.peek() {
304            Some(Ok(ref t)) if token == t => {}
305            _ => return false,
306        }
307        self.t.next();
308        true
309    }
310
311    fn eat(&mut self, token: &Token<'a>) -> Result<(), ParseError> {
312        match self.t.next() {
313            Some(Ok(ref t)) if token == t => Ok(()),
314            Some(Ok(t)) => Err(ParseError::new(
315                self.t.orig,
316                UnexpectedToken {
317                    expected: token.classify(),
318                    found: t.classify(),
319                },
320            )),
321            Some(Err(e)) => Err(e),
322            None => Err(ParseError::new(
323                self.t.orig,
324                IncompleteExpr(token.classify()),
325            )),
326        }
327    }
328
329    /// Returns the rest of the input from the current location.
330    fn rest(&self) -> Option<&str> {
331        let mut s = self.t.s.clone();
332        loop {
333            match s.next() {
334                Some((_, ' ')) => {}
335                Some((start, _ch)) => return Some(&self.t.orig[start..]),
336                None => return None,
337            }
338        }
339    }
340}
341
342impl<'a> Iterator for Tokenizer<'a> {
343    type Item = Result<Token<'a>, ParseError>;
344
345    fn next(&mut self) -> Option<Result<Token<'a>, ParseError>> {
346        loop {
347            match self.s.next() {
348                Some((_, ' ')) => {}
349                Some((_, '(')) => return Some(Ok(Token::LeftParen)),
350                Some((_, ')')) => return Some(Ok(Token::RightParen)),
351                Some((_, ',')) => return Some(Ok(Token::Comma)),
352                Some((_, '=')) => return Some(Ok(Token::Equals)),
353                Some((start, '"')) => {
354                    while let Some((end, ch)) = self.s.next() {
355                        if ch == '"' {
356                            return Some(Ok(Token::String(&self.orig[start + 1..end])));
357                        }
358                    }
359                    return Some(Err(ParseError::new(self.orig, UnterminatedString)));
360                }
361                Some((start, ch)) if is_ident_start(ch) => {
362                    let (start, raw) = if ch == 'r' {
363                        if let Some(&(_pos, '#')) = self.s.peek() {
364                            // starts with `r#` is a raw ident
365                            self.s.next();
366                            if let Some((start, ch)) = self.s.next() {
367                                if is_ident_start(ch) {
368                                    (start, true)
369                                } else {
370                                    // not a starting ident character
371                                    return Some(Err(ParseError::new(
372                                        self.orig,
373                                        UnexpectedChar(ch),
374                                    )));
375                                }
376                            } else {
377                                // not followed by a ident, error out
378                                return Some(Err(ParseError::new(
379                                    self.orig,
380                                    IncompleteExpr("identifier"),
381                                )));
382                            }
383                        } else {
384                            // starts with `r` but not does continue with `#`
385                            // cannot be a raw ident
386                            (start, false)
387                        }
388                    } else {
389                        // do not start with `r`, cannot be a raw ident
390                        (start, false)
391                    };
392                    while let Some(&(end, ch)) = self.s.peek() {
393                        if !is_ident_rest(ch) {
394                            return Some(Ok(Token::Ident(raw, &self.orig[start..end])));
395                        } else {
396                            self.s.next();
397                        }
398                    }
399                    return Some(Ok(Token::Ident(raw, &self.orig[start..])));
400                }
401                Some((_, ch)) => {
402                    return Some(Err(ParseError::new(self.orig, UnexpectedChar(ch))));
403                }
404                None => return None,
405            }
406        }
407    }
408}
409
410fn is_ident_start(ch: char) -> bool {
411    ch == '_' || ch.is_ascii_alphabetic()
412}
413
414fn is_ident_rest(ch: char) -> bool {
415    is_ident_start(ch) || ch.is_ascii_digit()
416}
417
418impl<'a> Token<'a> {
419    fn classify(&self) -> &'static str {
420        match *self {
421            Token::LeftParen => "`(`",
422            Token::RightParen => "`)`",
423            Token::Ident(..) => "an identifier",
424            Token::Comma => "`,`",
425            Token::Equals => "`=`",
426            Token::String(..) => "a string",
427        }
428    }
429}