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