Skip to main content

zngur_parser/
cfg.rs

1use std::collections::{HashMap, HashSet};
2
3use crate::{
4    ParseContext, Span, Spanned, Token, ZngParser,
5    conditional::{MatchPattern, MatchPatternParse, Matchable, MatchableParse},
6    spanned,
7};
8use chumsky::prelude::*;
9
10/// A configuration provider, Must be Clone.
11pub trait RustCfgProvider: CloneableCfg {
12    /// Gets values associated with a config key if it's present.
13    fn get_cfg(&self, key: &str) -> Option<Vec<String>>;
14    /// Gets a list of feature names that are enabled
15    fn get_features(&self) -> Vec<String>;
16    /// Gets all config key:value pairs
17    ///
18    /// Returns a pair for every value of a key, if a key has no values emits a
19    /// (key, None) pair
20    fn get_cfg_pairs(&self) -> Vec<(String, Option<String>)>;
21}
22
23pub trait CloneableCfg {
24    fn clone_box(&self) -> Box<dyn RustCfgProvider>;
25}
26
27impl<T> CloneableCfg for T
28where
29    T: 'static + RustCfgProvider + Clone,
30{
31    fn clone_box(&self) -> Box<dyn RustCfgProvider> {
32        Box::new(self.clone())
33    }
34}
35
36#[derive(Copy, Clone)]
37pub struct NullCfg;
38
39impl RustCfgProvider for NullCfg {
40    fn get_cfg(&self, _key: &str) -> Option<Vec<String>> {
41        None
42    }
43    fn get_features(&self) -> Vec<String> {
44        Vec::new()
45    }
46    fn get_cfg_pairs(&self) -> Vec<(String, Option<String>)> {
47        Vec::new()
48    }
49}
50
51#[derive(Clone)]
52pub struct InMemoryRustCfgProvider {
53    cfg: HashMap<String, Vec<String>>,
54}
55
56const CARGO_FEATURE_PREFIX: &str = "CARGO_FEATURE_";
57const CARGO_CFG_PREFIX: &str = "CARGO_CFG_";
58
59impl InMemoryRustCfgProvider {
60    pub fn new() -> Self {
61        InMemoryRustCfgProvider {
62            cfg: HashMap::new(),
63        }
64    }
65
66    pub fn with_values<'a, CfgPairs, CfgKey, CfgValues>(mut self, cfg_values: CfgPairs) -> Self
67    where
68        CfgPairs: IntoIterator<Item = (CfgKey, CfgValues)>,
69        CfgKey: AsRef<str> + 'a,
70        CfgValues: Clone + IntoIterator + 'a,
71        <CfgValues as IntoIterator>::Item: AsRef<str>,
72    {
73        for (key, values) in cfg_values {
74            let entry = self.cfg.entry(key.as_ref().to_string()).or_default();
75            let values = values.clone().into_iter().map(|v| v.as_ref().to_string());
76            entry.reserve(values.size_hint().0);
77            for value in values {
78                if !entry.contains(&value) {
79                    entry.push(value);
80                }
81            }
82        }
83        self
84    }
85
86    pub fn load_from_cargo_env(mut self) -> Self {
87        // set to unify features that can appear in two locations
88        let mut features = HashSet::new();
89        for (k, v) in std::env::vars_os() {
90            // no panic if not unicode
91            let (Some(k), Some(v)) = (k.to_str(), v.to_str()) else {
92                continue;
93            };
94            if let Some(feature) = k.strip_prefix(CARGO_FEATURE_PREFIX) {
95                features.insert(feature.to_lowercase());
96            } else if let Some(key) = k.strip_prefix(CARGO_CFG_PREFIX) {
97                let key = key.to_lowercase();
98                let values: Vec<String> = v.split(",").map(str::to_owned).collect();
99                if key == "feature" {
100                    features.extend(values);
101                } else {
102                    let entry = self.cfg.entry(key.to_string()).or_default();
103                    entry.reserve(values.len());
104                    for value in values {
105                        if !entry.contains(&value) {
106                            entry.push(value);
107                        }
108                    }
109                }
110            }
111        }
112        if !features.is_empty() {
113            let features_entry = self.cfg.entry("feature".to_string()).or_default();
114            features_entry.reserve(features.len());
115            features_entry.extend(features);
116        }
117        self
118    }
119}
120
121impl Default for InMemoryRustCfgProvider {
122    fn default() -> Self {
123        Self::new()
124    }
125}
126
127impl RustCfgProvider for InMemoryRustCfgProvider {
128    fn get_cfg(&self, key: &str) -> Option<Vec<String>> {
129        self.cfg.get(key).map(|values| values.to_vec())
130    }
131    fn get_features(&self) -> Vec<String> {
132        self.cfg.get("feature").cloned().unwrap_or_default()
133    }
134    fn get_cfg_pairs(&self) -> Vec<(String, Option<String>)> {
135        self.cfg
136            .iter()
137            .flat_map(|(key, values)| {
138                if values.is_empty() {
139                    vec![(key.clone(), None)]
140                } else {
141                    values
142                        .iter()
143                        .map(|value| (key.clone(), Some(value.clone())))
144                        .collect()
145                }
146            })
147            .collect()
148    }
149}
150
151#[derive(Debug, Copy, Clone, PartialEq, Eq)]
152pub(crate) enum CfgScrutinee<'src> {
153    Key(&'src str),
154    KeyWithItem(&'src str, &'src str),
155    Feature(&'src str),
156    AllFeatures,
157}
158
159#[derive(Debug, Clone, PartialEq, Eq)]
160pub(crate) enum ProcessedCfgScrutinee {
161    Empty,
162    Some,
163    Values(Vec<String>),
164}
165#[derive(Debug, Clone, PartialEq, Eq)]
166pub(crate) enum ProcessedCfgConditional {
167    Single(ProcessedCfgScrutinee),
168    Tuple(Vec<ProcessedCfgScrutinee>),
169}
170
171/// Match on config keys and features
172#[derive(Debug, Clone, PartialEq, Eq)]
173pub(crate) enum CfgConditional<'src> {
174    Single(CfgScrutinee<'src>),
175    Tuple(Vec<CfgScrutinee<'src>>),
176}
177
178#[derive(Debug, Clone, PartialEq, Eq)]
179pub(crate) enum CfgPatternItem<'src> {
180    Empty, // a `_` pattern
181    Some,  // the config has "some" value for the key
182    None,  // the config has "no" value for the key
183    Str(&'src str),
184    Number(usize),
185}
186
187#[derive(Debug, Clone, PartialEq, Eq)]
188pub(crate) enum CfgPattern<'src> {
189    Single(CfgPatternItem<'src>, Span),
190    And(Vec<CfgPattern<'src>>, Span),
191    Or(Vec<CfgPattern<'src>>, Span),
192    Not(Box<CfgPattern<'src>>, Span),
193    Grouped(Box<CfgPattern<'src>>, Span),
194    Tuple(Vec<CfgPattern<'src>>, Span),
195}
196
197impl<'src> MatchPattern for CfgPattern<'src> {
198    fn default_some(span: Span) -> Self {
199        CfgPattern::Single(CfgPatternItem::Some, span)
200    }
201}
202impl<'src> MatchPatternParse<'src> for CfgPattern<'src> {
203    fn parser() -> impl ZngParser<'src, Self> {
204        let single = recursive(|pat| {
205            let literals = select! {
206                Token::Str(c) => CfgPatternItem::Str(c),
207                Token::Number(n) => CfgPatternItem::Number(n),
208            };
209            let atom = choice((
210                spanned(literals),
211                spanned(just(Token::Underscore).to(CfgPatternItem::Empty)),
212                spanned(just(Token::Ident("Some")).to(CfgPatternItem::Some)),
213                spanned(just(Token::Ident("None")).to(CfgPatternItem::None)),
214            ))
215            .map(|item| CfgPattern::Single(item.inner, item.span))
216            .or(spanned(
217                pat.delimited_by(just(Token::ParenOpen), just(Token::ParenClose)),
218            )
219            .map(|item| CfgPattern::Grouped(Box::new(item.inner), item.span)));
220
221            let not_pat = just(Token::Bang)
222                .repeated()
223                .foldr_with(atom, |_op, rhs, e| CfgPattern::Not(Box::new(rhs), e.span()));
224
225            let and_pat = not_pat.clone().foldl_with(
226                just(Token::And).ignore_then(not_pat).repeated(),
227                |lhs, rhs, e| match lhs {
228                    CfgPattern::And(mut items, _span) => {
229                        items.push(rhs);
230                        CfgPattern::And(items, e.span())
231                    }
232                    _ => CfgPattern::And(vec![lhs, rhs], e.span()),
233                },
234            );
235
236            // or pat
237            and_pat.clone().foldl_with(
238                just(Token::Pipe).ignore_then(and_pat).repeated(),
239                |lhs, rhs, e| match lhs {
240                    CfgPattern::Or(mut items, _span) => {
241                        items.push(rhs);
242                        CfgPattern::Or(items, e.span())
243                    }
244                    _ => CfgPattern::Or(vec![lhs, rhs], e.span()),
245                },
246            )
247        });
248
249        spanned(
250            single
251                .clone()
252                .separated_by(just(Token::Comma))
253                .at_least(1)
254                .collect::<Vec<_>>()
255                .delimited_by(just(Token::ParenOpen), just(Token::ParenClose)),
256        )
257        .map(|item| CfgPattern::Tuple(item.inner, item.span))
258        .or(single)
259    }
260}
261
262impl<'src> Matchable for CfgConditional<'src> {
263    type Pattern = CfgPattern<'src>;
264
265    fn eval(&self, pattern: &Self::Pattern, ctx: &mut ParseContext) -> bool {
266        let cfg = ctx.get_config_provider();
267
268        let process = |key: &CfgScrutinee<'src>| -> ProcessedCfgScrutinee {
269            match key {
270                CfgScrutinee::Key(key) => cfg
271                    .get_cfg(key)
272                    .map(|values| {
273                        if values.is_empty() {
274                            ProcessedCfgScrutinee::Some
275                        } else {
276                            ProcessedCfgScrutinee::Values(values)
277                        }
278                    })
279                    .unwrap_or(ProcessedCfgScrutinee::Empty),
280                CfgScrutinee::KeyWithItem(key, item) => cfg
281                    .get_cfg(key)
282                    .and_then(|values| {
283                        values
284                            .iter()
285                            .any(|value| value == item)
286                            .then_some(ProcessedCfgScrutinee::Some)
287                    })
288                    .unwrap_or(ProcessedCfgScrutinee::Empty),
289                CfgScrutinee::AllFeatures => ProcessedCfgScrutinee::Values(cfg.get_features()),
290                CfgScrutinee::Feature(feature) => {
291                    if cfg.get_features().iter().any(|value| value == feature) {
292                        ProcessedCfgScrutinee::Some
293                    } else {
294                        ProcessedCfgScrutinee::Empty
295                    }
296                }
297            }
298        };
299
300        let scrutinee = match self {
301            Self::Single(key) => ProcessedCfgConditional::Single(process(key)),
302            Self::Tuple(keys) => {
303                ProcessedCfgConditional::Tuple(keys.iter().map(process).collect::<Vec<_>>())
304            }
305        };
306
307        pattern.matches(&scrutinee, ctx)
308    }
309}
310
311impl<'src> CfgScrutinee<'src> {
312    fn parser() -> impl ZngParser<'src, Self> {
313        select! {Token::Ident(c) => c, Token::Str(s) => s}
314            .separated_by(just(Token::Dot))
315            .at_least(1)
316            .at_most(2)
317            .collect::<Vec<_>>()
318            .map(|item| {
319                match &item[..] {
320                    [key] if key == &"feature" => CfgScrutinee::AllFeatures,
321                    [key, item] if key == &"feature" => CfgScrutinee::Feature(item),
322                    [key] => CfgScrutinee::Key(key),
323                    [key, item] => CfgScrutinee::KeyWithItem(key, item),
324                    // the above at_least(1) and at_most(2) calls
325                    // prevent this branch
326                    _ => unreachable!(),
327                }
328            })
329    }
330}
331
332impl<'src> MatchableParse<'src> for CfgConditional<'src> {
333    fn parser() -> impl ZngParser<'src, Self> {
334        let directive = just([Token::Ident("cfg"), Token::Bang]).ignore_then(
335            CfgScrutinee::parser().delimited_by(just(Token::ParenOpen), just(Token::ParenClose)),
336        );
337
338        choice((
339            directive.clone().map(CfgConditional::Single),
340            directive
341                .separated_by(just(Token::Comma))
342                .allow_trailing()
343                .at_least(1)
344                .collect::<Vec<_>>()
345                .map(CfgConditional::Tuple)
346                .delimited_by(just(Token::ParenOpen), just(Token::ParenClose)),
347        ))
348    }
349
350    fn combined() -> Option<
351        crate::BoxedZngParser<
352            'src,
353            (
354                crate::Spanned<Self>,
355                crate::Spanned<<Self as Matchable>::Pattern>,
356            ),
357        >,
358    > {
359        let directive = just([Token::Ident("cfg"), Token::Bang])
360            .ignore_then(
361                spanned(CfgScrutinee::parser())
362                    .then(
363                        just(Token::Eq)
364                            .ignore_then(spanned(CfgPattern::parser()))
365                            .or_not(),
366                    )
367                    .delimited_by(just(Token::ParenOpen), just(Token::ParenClose)),
368            )
369            .map_with(|(scrutinee, pat), e| {
370                (
371                    scrutinee,
372                    pat.unwrap_or_else(|| Spanned {
373                        inner: CfgPattern::default_some(e.span()),
374                        span: e.span(),
375                    }),
376                )
377            });
378        Some(
379            directive
380                .clone()
381                .map(|(scrutinee, pat)| {
382                    (
383                        Spanned {
384                            inner: CfgConditional::Single(scrutinee.inner),
385                            span: scrutinee.span,
386                        },
387                        pat,
388                    )
389                })
390                .boxed(),
391        )
392    }
393}
394
395impl CfgPattern<'_> {
396    fn matches(&self, scrutinee: &ProcessedCfgConditional, ctx: &mut ParseContext) -> bool {
397        use ProcessedCfgConditional as PCC;
398        match (self, scrutinee) {
399            (Self::Tuple(pats, _), PCC::Single(_)) if pats.len() == 1 => {
400                let pat = pats.iter().last().unwrap();
401                // tuple is actually single
402                pat.matches(scrutinee, ctx)
403            }
404            (Self::Single(pat, _), PCC::Tuple(scrutinees)) if scrutinees.len() == 1 => {
405                let scrutinee = scrutinees.iter().last().unwrap();
406                // tuple is actually single
407                pat.matches(scrutinee)
408            }
409            (Self::Single(CfgPatternItem::Empty, _), PCC::Tuple(_)) => {
410                // empty pattern matches anything
411                true
412            }
413            (Self::Tuple(_, span), PCC::Single(_)) => {
414                ctx.add_error_str(
415                    "Can not match tuple pattern against a single cfg value.",
416                    *span,
417                );
418                false
419            }
420            (
421                Self::Single(_, span)
422                | Self::Not(_, span)
423                | Self::And(_, span)
424                | Self::Or(_, span)
425                | Self::Grouped(_, span),
426                PCC::Tuple(_),
427            ) => {
428                ctx.add_error_str(
429                    "Can not match single pattern against multiple cfg values.",
430                    *span,
431                );
432                false
433            }
434            (Self::Tuple(pats, span), PCC::Tuple(scrutinees)) => {
435                if scrutinees.len() != pats.len() {
436                    ctx.add_error_str(
437                        "Number of patterns and number of scrutinees do not match.",
438                        *span,
439                    );
440                    false
441                } else {
442                    pats.iter()
443                        .zip(scrutinees.iter())
444                        .all(|(pat, scrutinee)| pat.matches(&PCC::Single(scrutinee.clone()), ctx))
445                }
446            }
447            (Self::Single(pat, _), PCC::Single(scrutinee)) => pat.matches(scrutinee),
448            (Self::Grouped(pat, _), PCC::Single(_)) => pat.matches(scrutinee, ctx),
449            (Self::Not(pat, _), PCC::Single(_)) => !pat.matches(scrutinee, ctx),
450            (Self::And(pats, _), PCC::Single(_)) => {
451                pats.iter().all(|pat| pat.matches(scrutinee, ctx))
452            }
453            (Self::Or(pats, _), PCC::Single(_)) => {
454                pats.iter().any(|pat| pat.matches(scrutinee, ctx))
455            }
456        }
457    }
458}
459
460impl CfgPatternItem<'_> {
461    fn matches(&self, scrutinee: &ProcessedCfgScrutinee) -> bool {
462        use ProcessedCfgScrutinee as PCS;
463        match self {
464            Self::Empty => true,
465            Self::Some => !matches!(scrutinee, PCS::Empty),
466            Self::None => matches!(scrutinee, PCS::Empty),
467            Self::Str(v) => match &scrutinee {
468                PCS::Empty | PCS::Some => false,
469                PCS::Values(values) => values.iter().any(|value| value == v),
470            },
471            Self::Number(n) => match &scrutinee {
472                PCS::Empty | PCS::Some => false,
473                PCS::Values(values) => values
474                    .iter()
475                    .any(|value| value.parse::<usize>().map(|v| v == *n).unwrap_or(false)),
476            },
477        }
478    }
479}