cfg_expr/expr/
parser.rs

1use crate::{
2    error::{ParseError, Reason},
3    expr::{
4        lexer::{Lexer, Token},
5        ExprNode, Expression, Func, InnerPredicate,
6    },
7};
8use smallvec::SmallVec;
9
10impl Expression {
11    /// Given a `cfg()` expression (the cfg( and ) are optional), attempts to
12    /// parse it into a form where it can be evaluated
13    ///
14    /// ```
15    /// assert!(cfg_expr::Expression::parse(r#"cfg(all(unix, target_arch = "x86_64"))"#).is_ok());
16    /// ```
17    pub fn parse(original: &str) -> Result<Self, ParseError> {
18        let lexer = Lexer::new(original);
19
20        // The lexer automatically trims any cfg( ), so reacquire
21        // the string before we start walking tokens
22        let original = lexer.inner;
23
24        #[derive(Debug)]
25        struct FuncAndSpan {
26            func: Func,
27            parens_index: usize,
28            span: std::ops::Range<usize>,
29            predicates: SmallVec<[InnerPredicate; 5]>,
30            nest_level: u8,
31        }
32
33        let mut func_stack = SmallVec::<[FuncAndSpan; 5]>::new();
34        let mut expr_queue = SmallVec::<[ExprNode; 5]>::new();
35
36        // Keep track of the last token to simplify validation of the token stream
37        let mut last_token: Option<Token<'_>> = None;
38
39        let parse_predicate = |key: (&str, std::ops::Range<usize>),
40                               val: Option<(&str, std::ops::Range<usize>)>|
41         -> Result<InnerPredicate, ParseError> {
42            // Warning: It is possible for arbitrarily-set configuration
43            // options to have the same value as compiler-set configuration
44            // options. For example, it is possible to do rustc --cfg "unix" program.rs
45            // while compiling to a Windows target, and have both unix and windows
46            // configuration options set at the same time. It is unwise to actually
47            // do this.
48            //
49            // rustc is very permissive in this regard, but I'd rather be really
50            // strict, as it's much easier to loosen restrictions over time than add
51            // new ones
52            macro_rules! err_if_val {
53                () => {
54                    if let Some((_, vspan)) = val {
55                        return Err(ParseError {
56                            original: original.to_owned(),
57                            span: vspan,
58                            reason: Reason::Unexpected(&[]),
59                        });
60                    }
61                };
62            }
63
64            let span = key.1;
65            let key = key.0;
66
67            use super::{InnerTarget, Which};
68
69            Ok(match key {
70                // These are special cases in the cfg language that are
71                // semantically the same as `target_family = "<family>"`,
72                // so we just make them not special
73                // NOTE: other target families like "wasm" are NOT allowed
74                // as naked predicates; they must be specified through
75                // `target_family`
76                "unix" | "windows" => {
77                    err_if_val!();
78
79                    InnerPredicate::Target(InnerTarget {
80                        which: Which::Family,
81                        span: Some(span),
82                    })
83                }
84                "test" => {
85                    err_if_val!();
86                    InnerPredicate::Test
87                }
88                "debug_assertions" => {
89                    err_if_val!();
90                    InnerPredicate::DebugAssertions
91                }
92                "proc_macro" => {
93                    err_if_val!();
94                    InnerPredicate::ProcMacro
95                }
96                "feature" => {
97                    // rustc allows bare feature without a value, but the only way
98                    // such a predicate would ever evaluate to true would be if they
99                    // explicitly set --cfg feature, which would be terrible, so we
100                    // just error instead
101                    match val {
102                        Some((_, span)) => InnerPredicate::Feature(span),
103                        None => {
104                            return Err(ParseError {
105                                original: original.to_owned(),
106                                span,
107                                reason: Reason::Unexpected(&["= \"<feature_name>\""]),
108                            });
109                        }
110                    }
111                }
112                "panic" => match val {
113                    Some((_, vspan)) => InnerPredicate::Target(InnerTarget {
114                        which: Which::Panic,
115                        span: Some(vspan),
116                    }),
117                    None => {
118                        return Err(ParseError {
119                            original: original.to_owned(),
120                            span,
121                            reason: Reason::Unexpected(&["= \"<panic_strategy>\""]),
122                        });
123                    }
124                },
125                target_key if key.starts_with("target_") => {
126                    let (val, vspan) = match val {
127                        None => {
128                            return Err(ParseError {
129                                original: original.to_owned(),
130                                span,
131                                reason: Reason::Unexpected(&["= \"<target_cfg_value>\""]),
132                            });
133                        }
134                        Some((val, vspan)) => (val, vspan),
135                    };
136
137                    macro_rules! tp {
138                        ($which:ident) => {
139                            InnerTarget {
140                                which: Which::$which,
141                                span: Some(vspan),
142                            }
143                        };
144                    }
145
146                    let tp = match &target_key[7..] {
147                        "abi" => tp!(Abi),
148                        "arch" => tp!(Arch),
149                        "feature" => {
150                            if val.is_empty() {
151                                return Err(ParseError {
152                                    original: original.to_owned(),
153                                    span: vspan,
154                                    reason: Reason::Unexpected(&["<feature>"]),
155                                });
156                            }
157
158                            return Ok(InnerPredicate::TargetFeature(vspan));
159                        }
160                        "os" => tp!(Os),
161                        "family" => tp!(Family),
162                        "env" => tp!(Env),
163                        "endian" => InnerTarget {
164                            which: Which::Endian(val.parse().map_err(|_err| ParseError {
165                                original: original.to_owned(),
166                                span: vspan,
167                                reason: Reason::InvalidInteger,
168                            })?),
169                            span: None,
170                        },
171                        "has_atomic" => InnerTarget {
172                            which: Which::HasAtomic(val.parse().map_err(|_err| ParseError {
173                                original: original.to_owned(),
174                                span: vspan,
175                                reason: Reason::InvalidHasAtomic,
176                            })?),
177                            span: None,
178                        },
179                        "pointer_width" => InnerTarget {
180                            which: Which::PointerWidth(val.parse().map_err(|_err| ParseError {
181                                original: original.to_owned(),
182                                span: vspan,
183                                reason: Reason::InvalidInteger,
184                            })?),
185                            span: None,
186                        },
187                        "vendor" => tp!(Vendor),
188                        _ => {
189                            return Err(ParseError {
190                                original: original.to_owned(),
191                                span,
192                                reason: Reason::Unexpected(&[
193                                    "target_arch",
194                                    "target_feature",
195                                    "target_os",
196                                    "target_family",
197                                    "target_env",
198                                    "target_endian",
199                                    "target_has_atomic",
200                                    "target_pointer_width",
201                                    "target_vendor",
202                                ]),
203                            })
204                        }
205                    };
206
207                    InnerPredicate::Target(tp)
208                }
209                _other => InnerPredicate::Other {
210                    identifier: span,
211                    value: val.map(|(_, span)| span),
212                },
213            })
214        };
215
216        macro_rules! token_err {
217            ($span:expr) => {{
218                let expected: &[&str] = match last_token {
219                    None => &["<key>", "all", "any", "not"],
220                    Some(Token::All | Token::Any | Token::Not) => &["("],
221                    Some(Token::CloseParen) => &[")", ","],
222                    Some(Token::Comma) => &[")", "<key>"],
223                    Some(Token::Equals) => &["\""],
224                    Some(Token::Key(_)) => &["=", ",", ")"],
225                    Some(Token::Value(_)) => &[",", ")"],
226                    Some(Token::OpenParen) => &["<key>", ")", "all", "any", "not"],
227                };
228
229                return Err(ParseError {
230                    original: original.to_owned(),
231                    span: $span,
232                    reason: Reason::Unexpected(&expected),
233                });
234            }};
235        }
236
237        let mut pred_key: Option<(&str, _)> = None;
238        let mut pred_val: Option<(&str, _)> = None;
239
240        let mut root_predicate_count = 0;
241
242        // Basic implementation of the https://en.wikipedia.org/wiki/Shunting-yard_algorithm
243        'outer: for lt in lexer {
244            let lt = lt?;
245            match &lt.token {
246                Token::Key(k) => {
247                    if matches!(last_token, None | Some(Token::OpenParen | Token::Comma)) {
248                        pred_key = Some((k, lt.span.clone()));
249                    } else {
250                        token_err!(lt.span)
251                    }
252                }
253                Token::Value(v) => {
254                    if matches!(last_token, Some(Token::Equals)) {
255                        // We only record the span for keys and values
256                        // so that the expression doesn't need a lifetime
257                        // but in the value case we need to strip off
258                        // the quotes so that the proper raw string is
259                        // provided to callers when evaluating the expression
260                        pred_val = Some((v, lt.span.start + 1..lt.span.end - 1));
261                    } else {
262                        token_err!(lt.span)
263                    }
264                }
265                Token::Equals => {
266                    if !matches!(last_token, Some(Token::Key(_))) {
267                        token_err!(lt.span)
268                    }
269                }
270                Token::All | Token::Any | Token::Not => {
271                    if matches!(last_token, None | Some(Token::OpenParen | Token::Comma)) {
272                        let new_fn = match lt.token {
273                            // the 0 is a dummy value -- it will be substituted for the real
274                            // number of predicates in the `CloseParen` branch below.
275                            Token::All => Func::All(0),
276                            Token::Any => Func::Any(0),
277                            Token::Not => Func::Not,
278                            _ => unreachable!(),
279                        };
280
281                        if let Some(fs) = func_stack.last_mut() {
282                            fs.nest_level += 1;
283                        }
284
285                        func_stack.push(FuncAndSpan {
286                            func: new_fn,
287                            span: lt.span,
288                            parens_index: 0,
289                            predicates: SmallVec::new(),
290                            nest_level: 0,
291                        });
292                    } else {
293                        token_err!(lt.span)
294                    }
295                }
296                Token::OpenParen => {
297                    if matches!(last_token, Some(Token::All | Token::Any | Token::Not)) {
298                        if let Some(ref mut fs) = func_stack.last_mut() {
299                            fs.parens_index = lt.span.start;
300                        }
301                    } else {
302                        token_err!(lt.span)
303                    }
304                }
305                Token::CloseParen => {
306                    if matches!(
307                        last_token,
308                        None | Some(Token::All | Token::Any | Token::Not | Token::Equals)
309                    ) {
310                        token_err!(lt.span)
311                    } else {
312                        if let Some(top) = func_stack.pop() {
313                            let key = pred_key.take();
314                            let val = pred_val.take();
315
316                            // In this context, the boolean to int conversion is confusing.
317                            #[allow(clippy::bool_to_int_with_if)]
318                            let num_predicates = top.predicates.len()
319                                + if key.is_some() { 1 } else { 0 }
320                                + top.nest_level as usize;
321
322                            let func = match top.func {
323                                Func::All(_) => Func::All(num_predicates),
324                                Func::Any(_) => Func::Any(num_predicates),
325                                Func::Not => {
326                                    // not() doesn't take a predicate list, but only a single predicate,
327                                    // so ensure we have exactly 1
328                                    if num_predicates != 1 {
329                                        return Err(ParseError {
330                                            original: original.to_owned(),
331                                            span: top.span.start..lt.span.end,
332                                            reason: Reason::InvalidNot(num_predicates),
333                                        });
334                                    }
335
336                                    Func::Not
337                                }
338                            };
339
340                            for pred in top.predicates {
341                                expr_queue.push(ExprNode::Predicate(pred));
342                            }
343
344                            if let Some(key) = key {
345                                let inner_pred = parse_predicate(key, val)?;
346                                expr_queue.push(ExprNode::Predicate(inner_pred));
347                            }
348
349                            expr_queue.push(ExprNode::Fn(func));
350
351                            // This is the only place we go back to the top of the outer loop,
352                            // so make sure we correctly record this token
353                            last_token = Some(Token::CloseParen);
354                            continue 'outer;
355                        }
356
357                        // We didn't have an opening parentheses if we get here
358                        return Err(ParseError {
359                            original: original.to_owned(),
360                            span: lt.span,
361                            reason: Reason::UnopenedParens,
362                        });
363                    }
364                }
365                Token::Comma => {
366                    if matches!(
367                        last_token,
368                        None | Some(
369                            Token::OpenParen | Token::All | Token::Any | Token::Not | Token::Equals
370                        )
371                    ) {
372                        token_err!(lt.span)
373                    } else {
374                        let key = pred_key.take();
375                        let val = pred_val.take();
376
377                        let inner_pred = key.map(|key| parse_predicate(key, val)).transpose()?;
378
379                        match (inner_pred, func_stack.last_mut()) {
380                            (Some(pred), Some(func)) => {
381                                func.predicates.push(pred);
382                            }
383                            (Some(pred), None) => {
384                                root_predicate_count += 1;
385
386                                expr_queue.push(ExprNode::Predicate(pred));
387                            }
388                            _ => {}
389                        }
390                    }
391                }
392            }
393
394            last_token = Some(lt.token);
395        }
396
397        if let Some(Token::Equals) = last_token {
398            return Err(ParseError {
399                original: original.to_owned(),
400                span: original.len()..original.len(),
401                reason: Reason::Unexpected(&["\"<value>\""]),
402            });
403        }
404
405        // If we still have functions on the stack, it means we have an unclosed parens
406        if let Some(top) = func_stack.pop() {
407            if top.parens_index != 0 {
408                Err(ParseError {
409                    original: original.to_owned(),
410                    span: top.parens_index..original.len(),
411                    reason: Reason::UnclosedParens,
412                })
413            } else {
414                Err(ParseError {
415                    original: original.to_owned(),
416                    span: top.span,
417                    reason: Reason::Unexpected(&["("]),
418                })
419            }
420        } else {
421            let key = pred_key.take();
422            let val = pred_val.take();
423
424            if let Some(key) = key {
425                root_predicate_count += 1;
426                expr_queue.push(ExprNode::Predicate(parse_predicate(key, val)?));
427            }
428
429            if expr_queue.is_empty() {
430                Err(ParseError {
431                    original: original.to_owned(),
432                    span: 0..original.len(),
433                    reason: Reason::Empty,
434                })
435            } else if root_predicate_count > 1 {
436                Err(ParseError {
437                    original: original.to_owned(),
438                    span: 0..original.len(),
439                    reason: Reason::MultipleRootPredicates,
440                })
441            } else {
442                Ok(Expression {
443                    original: original.to_owned(),
444                    expr: expr_queue,
445                })
446            }
447        }
448    }
449}