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
10pub trait RustCfgProvider: CloneableCfg {
12 fn get_cfg(&self, key: &str) -> Option<Vec<String>>;
14 fn get_features(&self) -> Vec<String>;
16 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 let mut features = HashSet::new();
89 for (k, v) in std::env::vars_os() {
90 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#[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, Some, None, 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 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 _ => 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 pat.matches(scrutinee, ctx)
403 }
404 (Self::Single(pat, _), PCC::Tuple(scrutinees)) if scrutinees.len() == 1 => {
405 let scrutinee = scrutinees.iter().last().unwrap();
406 pat.matches(scrutinee)
408 }
409 (Self::Single(CfgPatternItem::Empty, _), PCC::Tuple(_)) => {
410 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}