1 | pub mod lexer; |
2 | mod parser; |
3 | |
4 | use smallvec::SmallVec; |
5 | use std::ops::Range; |
6 | |
7 | /// A predicate function, used to combine 1 or more predicates |
8 | /// into a single value |
9 | #[derive (Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] |
10 | pub enum Func { |
11 | /// `not()` with a configuration predicate. It is true if its predicate |
12 | /// is false and false if its predicate is true. |
13 | Not, |
14 | /// `all()` with a comma separated list of configuration predicates. It |
15 | /// is false if at least one predicate is false. If there are no predicates, |
16 | /// it is true. |
17 | /// |
18 | /// The associated `usize` is the number of predicates inside the `all()`. |
19 | All(usize), |
20 | /// `any()` with a comma separated list of configuration predicates. It |
21 | /// is true if at least one predicate is true. If there are no predicates, |
22 | /// it is false. |
23 | /// |
24 | /// The associated `usize` is the number of predicates inside the `any()`. |
25 | Any(usize), |
26 | } |
27 | |
28 | use crate::targets as targ; |
29 | |
30 | /// All predicates that pertains to a target, except for `target_feature` |
31 | #[derive (Clone, PartialEq, Eq, Debug)] |
32 | pub enum TargetPredicate { |
33 | /// [target_abi](https://github.com/rust-lang/rust/issues/80970) |
34 | Abi(targ::Abi), |
35 | /// [target_arch](https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch) |
36 | Arch(targ::Arch), |
37 | /// [target_endian](https://doc.rust-lang.org/reference/conditional-compilation.html#target_endian) |
38 | Endian(targ::Endian), |
39 | /// [target_env](https://doc.rust-lang.org/reference/conditional-compilation.html#target_env) |
40 | Env(targ::Env), |
41 | /// [target_family](https://doc.rust-lang.org/reference/conditional-compilation.html#target_family) |
42 | /// This also applies to the bare [`unix` and `windows`](https://doc.rust-lang.org/reference/conditional-compilation.html#unix-and-windows) |
43 | /// predicates. |
44 | Family(targ::Family), |
45 | /// [target_has_atomic](https://doc.rust-lang.org/reference/conditional-compilation.html#target_has_atomic). |
46 | HasAtomic(targ::HasAtomic), |
47 | /// [target_os](https://doc.rust-lang.org/reference/conditional-compilation.html#target_os) |
48 | Os(targ::Os), |
49 | /// [panic](https://doc.rust-lang.org/reference/conditional-compilation.html#panic) |
50 | Panic(targ::Panic), |
51 | /// [target_pointer_width](https://doc.rust-lang.org/reference/conditional-compilation.html#target_pointer_width) |
52 | PointerWidth(u8), |
53 | /// [target_vendor](https://doc.rust-lang.org/reference/conditional-compilation.html#target_vendor) |
54 | Vendor(targ::Vendor), |
55 | } |
56 | |
57 | pub trait TargetMatcher { |
58 | fn matches(&self, tp: &TargetPredicate) -> bool; |
59 | } |
60 | |
61 | impl TargetMatcher for targ::TargetInfo { |
62 | fn matches(&self, tp: &TargetPredicate) -> bool { |
63 | use TargetPredicate::{ |
64 | Abi, Arch, Endian, Env, Family, HasAtomic, Os, Panic, PointerWidth, Vendor, |
65 | }; |
66 | |
67 | match tp { |
68 | // The ABI is allowed to be an empty string |
69 | Abi(abi) => match &self.abi { |
70 | Some(a) => abi == a, |
71 | None => abi.0.is_empty(), |
72 | }, |
73 | Arch(a) => a == &self.arch, |
74 | Endian(end) => *end == self.endian, |
75 | // The environment is allowed to be an empty string |
76 | Env(env) => match &self.env { |
77 | Some(e) => env == e, |
78 | None => env.0.is_empty(), |
79 | }, |
80 | Family(fam) => self.families.contains(fam), |
81 | HasAtomic(has_atomic) => self.has_atomics.contains(*has_atomic), |
82 | Os(os) => match &self.os { |
83 | Some(self_os) => os == self_os, |
84 | // os = "none" means it should be matched against None. Note that this is different |
85 | // from "env" above. |
86 | None => os.as_str() == "none" , |
87 | }, |
88 | PointerWidth(w) => *w == self.pointer_width, |
89 | Vendor(ven) => match &self.vendor { |
90 | Some(v) => ven == v, |
91 | None => ven == &targ::Vendor::unknown, |
92 | }, |
93 | Panic(panic) => &self.panic == panic, |
94 | } |
95 | } |
96 | } |
97 | |
98 | #[cfg (feature = "targets" )] |
99 | impl TargetMatcher for target_lexicon::Triple { |
100 | #[allow (clippy::cognitive_complexity)] |
101 | #[allow (clippy::match_same_arms)] |
102 | fn matches(&self, tp: &TargetPredicate) -> bool { |
103 | use target_lexicon::*; |
104 | use TargetPredicate::{ |
105 | Abi, Arch, Endian, Env, Family, HasAtomic, Os, Panic, PointerWidth, Vendor, |
106 | }; |
107 | |
108 | match tp { |
109 | Abi(_) => { |
110 | // `target_abi` is unstable. Assume false for this. |
111 | false |
112 | } |
113 | Arch(arch) => { |
114 | if arch == &targ::Arch::x86 { |
115 | matches!(self.architecture, Architecture::X86_32(_)) |
116 | } else if arch == &targ::Arch::wasm32 { |
117 | self.architecture == Architecture::Wasm32 |
118 | || self.architecture == Architecture::Asmjs |
119 | } else if arch == &targ::Arch::arm { |
120 | matches!(self.architecture, Architecture::Arm(_)) |
121 | } else if arch == &targ::Arch::bpf { |
122 | self.architecture == Architecture::Bpfeb |
123 | || self.architecture == Architecture::Bpfel |
124 | } else if arch == &targ::Arch::x86_64 { |
125 | self.architecture == Architecture::X86_64 |
126 | || self.architecture == Architecture::X86_64h |
127 | } else if arch == &targ::Arch::mips32r6 { |
128 | matches!( |
129 | self.architecture, |
130 | Architecture::Mips32( |
131 | Mips32Architecture::Mipsisa32r6 | Mips32Architecture::Mipsisa32r6el |
132 | ) |
133 | ) |
134 | } else if arch == &targ::Arch::mips64r6 { |
135 | matches!( |
136 | self.architecture, |
137 | Architecture::Mips64( |
138 | Mips64Architecture::Mipsisa64r6 | Mips64Architecture::Mipsisa64r6el |
139 | ) |
140 | ) |
141 | } else { |
142 | match arch.0.parse::<Architecture>() { |
143 | Ok(a) => match (self.architecture, a) { |
144 | (Architecture::Aarch64(_), Architecture::Aarch64(_)) |
145 | | (Architecture::Mips32(_), Architecture::Mips32(_)) |
146 | | (Architecture::Mips64(_), Architecture::Mips64(_)) |
147 | | (Architecture::Powerpc64le, Architecture::Powerpc64) |
148 | | (Architecture::Riscv32(_), Architecture::Riscv32(_)) |
149 | | (Architecture::Riscv64(_), Architecture::Riscv64(_)) |
150 | | (Architecture::Sparcv9, Architecture::Sparc64) => true, |
151 | (a, b) => a == b, |
152 | }, |
153 | Err(_) => false, |
154 | } |
155 | } |
156 | } |
157 | Endian(end) => match self.architecture.endianness() { |
158 | Ok(endian) => matches!( |
159 | (end, endian), |
160 | (crate::targets::Endian::little, Endianness::Little) |
161 | | (crate::targets::Endian::big, Endianness::Big) |
162 | ), |
163 | |
164 | Err(_) => false, |
165 | }, |
166 | Env(env) => { |
167 | // The environment is implied by some operating systems |
168 | match self.operating_system { |
169 | OperatingSystem::Redox => env == &targ::Env::relibc, |
170 | OperatingSystem::VxWorks => env == &targ::Env::gnu, |
171 | OperatingSystem::Freebsd => match self.architecture { |
172 | Architecture::Arm(ArmArchitecture::Armv6 | ArmArchitecture::Armv7) => { |
173 | env == &targ::Env::gnueabihf |
174 | } |
175 | _ => env.0.is_empty(), |
176 | }, |
177 | OperatingSystem::Netbsd => match self.architecture { |
178 | Architecture::Arm(ArmArchitecture::Armv6 | ArmArchitecture::Armv7) => { |
179 | env == &targ::Env::eabihf |
180 | } |
181 | _ => env.0.is_empty(), |
182 | }, |
183 | OperatingSystem::None_ |
184 | | OperatingSystem::Cloudabi |
185 | | OperatingSystem::Hermit |
186 | | OperatingSystem::Ios => match self.environment { |
187 | Environment::LinuxKernel => env == &targ::Env::gnu, |
188 | _ => env.0.is_empty(), |
189 | }, |
190 | _ => { |
191 | if env.0.is_empty() { |
192 | matches!( |
193 | self.environment, |
194 | Environment::Unknown |
195 | | Environment::Android |
196 | | Environment::Softfloat |
197 | | Environment::Androideabi |
198 | | Environment::Eabi |
199 | | Environment::Eabihf |
200 | | Environment::Sim |
201 | ) |
202 | } else { |
203 | match env.0.parse::<Environment>() { |
204 | Ok(e) => { |
205 | // Rustc shortens multiple "gnu*" environments to just "gnu" |
206 | if env == &targ::Env::gnu { |
207 | match self.environment { |
208 | Environment::Gnu |
209 | | Environment::Gnuabi64 |
210 | | Environment::Gnueabi |
211 | | Environment::Gnuspe |
212 | | Environment::Gnux32 |
213 | | Environment::GnuIlp32 |
214 | | Environment::Gnueabihf |
215 | | Environment::GnuLlvm => true, |
216 | // Rust 1.49.0 changed all android targets to have the |
217 | // gnu environment |
218 | Environment::Android | Environment::Androideabi |
219 | if self.operating_system |
220 | == OperatingSystem::Linux => |
221 | { |
222 | true |
223 | } |
224 | Environment::Kernel => { |
225 | self.operating_system == OperatingSystem::Linux |
226 | } |
227 | _ => false, |
228 | } |
229 | } else if env == &targ::Env::musl { |
230 | matches!( |
231 | self.environment, |
232 | Environment::Musl |
233 | | Environment::Musleabi |
234 | | Environment::Musleabihf |
235 | | Environment::Muslabi64 |
236 | ) |
237 | } else if env == &targ::Env::uclibc { |
238 | matches!( |
239 | self.environment, |
240 | Environment::Uclibc |
241 | | Environment::Uclibceabi |
242 | | Environment::Uclibceabihf |
243 | ) |
244 | } else if env == &targ::Env::newlib { |
245 | matches!( |
246 | self.operating_system, |
247 | OperatingSystem::Horizon | OperatingSystem::Espidf |
248 | ) |
249 | } else { |
250 | self.environment == e |
251 | } |
252 | } |
253 | Err(_) => false, |
254 | } |
255 | } |
256 | } |
257 | } |
258 | } |
259 | Family(fam) => { |
260 | use OperatingSystem::{ |
261 | Aix, AmdHsa, Bitrig, Cloudabi, Cuda, Darwin, Dragonfly, Emscripten, Espidf, |
262 | Freebsd, Fuchsia, Haiku, Hermit, Horizon, Illumos, Ios, L4re, Linux, MacOSX, |
263 | Nebulet, Netbsd, None_, Openbsd, Redox, Solaris, Tvos, Uefi, Unknown, VxWorks, |
264 | Wasi, Watchos, Windows, |
265 | }; |
266 | match self.operating_system { |
267 | AmdHsa | Bitrig | Cloudabi | Cuda | Hermit | Nebulet | None_ | Uefi => false, |
268 | Aix |
269 | | Darwin |
270 | | Dragonfly |
271 | | Espidf |
272 | | Freebsd |
273 | | Fuchsia |
274 | | Haiku |
275 | | Illumos |
276 | | Ios |
277 | | L4re |
278 | | MacOSX { .. } |
279 | | Horizon |
280 | | Netbsd |
281 | | Openbsd |
282 | | Redox |
283 | | Solaris |
284 | | Tvos |
285 | | VxWorks |
286 | | Watchos => fam == &crate::targets::Family::unix, |
287 | Emscripten => { |
288 | match self.architecture { |
289 | // asmjs, wasm32 and wasm64 are part of both the wasm and unix families |
290 | Architecture::Asmjs | Architecture::Wasm32 => { |
291 | fam == &crate::targets::Family::wasm |
292 | || fam == &crate::targets::Family::unix |
293 | } |
294 | _ => false, |
295 | } |
296 | } |
297 | Unknown => { |
298 | // asmjs, wasm32 and wasm64 are part of the wasm family. |
299 | match self.architecture { |
300 | Architecture::Asmjs | Architecture::Wasm32 | Architecture::Wasm64 => { |
301 | fam == &crate::targets::Family::wasm |
302 | } |
303 | _ => false, |
304 | } |
305 | } |
306 | Linux => { |
307 | // The 'kernel' environment is treated specially as not-unix |
308 | if self.environment != Environment::Kernel { |
309 | fam == &crate::targets::Family::unix |
310 | } else { |
311 | false |
312 | } |
313 | } |
314 | Wasi => fam == &crate::targets::Family::wasm, |
315 | Windows => fam == &crate::targets::Family::windows, |
316 | // I really dislike non-exhaustive :( |
317 | _ => false, |
318 | } |
319 | } |
320 | HasAtomic(_) => { |
321 | // atomic support depends on both the architecture and the OS. Assume false for |
322 | // this. |
323 | false |
324 | } |
325 | Os(os) => match os.0.parse::<OperatingSystem>() { |
326 | Ok(o) => match self.environment { |
327 | Environment::HermitKernel => os == &targ::Os::hermit, |
328 | _ => self.operating_system == o, |
329 | }, |
330 | Err(_) => { |
331 | // Handle special case for darwin/macos, where the triple is |
332 | // "darwin", but rustc identifies the OS as "macos" |
333 | if os == &targ::Os::macos && self.operating_system == OperatingSystem::Darwin { |
334 | true |
335 | } else { |
336 | // For android, the os is still linux, but the environment is android |
337 | os == &targ::Os::android |
338 | && self.operating_system == OperatingSystem::Linux |
339 | && (self.environment == Environment::Android |
340 | || self.environment == Environment::Androideabi) |
341 | } |
342 | } |
343 | }, |
344 | Panic(_) => { |
345 | // panic support depends on the OS. Assume false for this. |
346 | false |
347 | } |
348 | Vendor(ven) => match ven.0.parse::<target_lexicon::Vendor>() { |
349 | Ok(v) => { |
350 | if self.vendor == v { |
351 | true |
352 | } else if let target_lexicon::Vendor::Custom(custom) = &self.vendor { |
353 | custom.as_str() == "esp" && v == target_lexicon::Vendor::Espressif |
354 | } else { |
355 | false |
356 | } |
357 | } |
358 | Err(_) => false, |
359 | }, |
360 | PointerWidth(pw) => { |
361 | // The gnux32 environment is a special case, where it has an |
362 | // x86_64 architecture, but a 32-bit pointer width |
363 | if !matches!( |
364 | self.environment, |
365 | Environment::Gnux32 | Environment::GnuIlp32 |
366 | ) { |
367 | *pw == match self.pointer_width() { |
368 | Ok(pw) => pw.bits(), |
369 | Err(_) => return false, |
370 | } |
371 | } else { |
372 | *pw == 32 |
373 | } |
374 | } |
375 | } |
376 | } |
377 | } |
378 | |
379 | impl TargetPredicate { |
380 | /// Returns true of the predicate matches the specified target |
381 | /// |
382 | /// Note that when matching against a [`target_lexicon::Triple`], the |
383 | /// `has_target_atomic` and `panic` predicates will _always_ return `false`. |
384 | /// |
385 | /// ``` |
386 | /// use cfg_expr::{targets::*, expr::TargetPredicate as tp}; |
387 | /// let win = get_builtin_target_by_triple("x86_64-pc-windows-msvc" ).unwrap(); |
388 | /// |
389 | /// assert!( |
390 | /// tp::Arch(Arch::x86_64).matches(win) && |
391 | /// tp::Endian(Endian::little).matches(win) && |
392 | /// tp::Env(Env::msvc).matches(win) && |
393 | /// tp::Family(Family::windows).matches(win) && |
394 | /// tp::Os(Os::windows).matches(win) && |
395 | /// tp::PointerWidth(64).matches(win) && |
396 | /// tp::Vendor(Vendor::pc).matches(win) |
397 | /// ); |
398 | /// ``` |
399 | pub fn matches<T>(&self, target: &T) -> bool |
400 | where |
401 | T: TargetMatcher, |
402 | { |
403 | target.matches(self) |
404 | } |
405 | } |
406 | |
407 | #[derive (Clone, Debug)] |
408 | pub(crate) enum Which { |
409 | Abi, |
410 | Arch, |
411 | Endian(targ::Endian), |
412 | Env, |
413 | Family, |
414 | Os, |
415 | HasAtomic(targ::HasAtomic), |
416 | Panic, |
417 | PointerWidth(u8), |
418 | Vendor, |
419 | } |
420 | |
421 | #[derive (Clone, Debug)] |
422 | pub(crate) struct InnerTarget { |
423 | which: Which, |
424 | span: Option<Range<usize>>, |
425 | } |
426 | |
427 | /// A single predicate in a `cfg()` expression |
428 | #[derive (Debug, PartialEq, Eq)] |
429 | pub enum Predicate<'a> { |
430 | /// A target predicate, with the `target_` prefix |
431 | Target(TargetPredicate), |
432 | /// Whether rustc's test harness is [enabled](https://doc.rust-lang.org/reference/conditional-compilation.html#test) |
433 | Test, |
434 | /// [Enabled](https://doc.rust-lang.org/reference/conditional-compilation.html#debug_assertions) |
435 | /// when compiling without optimizations. |
436 | DebugAssertions, |
437 | /// [Enabled](https://doc.rust-lang.org/reference/conditional-compilation.html#proc_macro) for |
438 | /// crates of the proc_macro type. |
439 | ProcMacro, |
440 | /// A [`feature = "<name>"`](https://doc.rust-lang.org/nightly/cargo/reference/features.html) |
441 | Feature(&'a str), |
442 | /// [target_feature](https://doc.rust-lang.org/reference/conditional-compilation.html#target_feature) |
443 | TargetFeature(&'a str), |
444 | /// A generic bare predicate key that doesn't match one of the known options, eg `cfg(bare)` |
445 | Flag(&'a str), |
446 | /// A generic key = "value" predicate that doesn't match one of the known options, eg `cfg(foo = "bar")` |
447 | KeyValue { key: &'a str, val: &'a str }, |
448 | } |
449 | |
450 | #[derive (Clone, Debug)] |
451 | pub(crate) enum InnerPredicate { |
452 | Target(InnerTarget), |
453 | Test, |
454 | DebugAssertions, |
455 | ProcMacro, |
456 | Feature(Range<usize>), |
457 | TargetFeature(Range<usize>), |
458 | Other { |
459 | identifier: Range<usize>, |
460 | value: Option<Range<usize>>, |
461 | }, |
462 | } |
463 | |
464 | impl InnerPredicate { |
465 | fn to_pred<'a>(&self, s: &'a str) -> Predicate<'a> { |
466 | use InnerPredicate as IP; |
467 | use Predicate::{ |
468 | DebugAssertions, Feature, Flag, KeyValue, ProcMacro, Target, TargetFeature, Test, |
469 | }; |
470 | |
471 | match self { |
472 | IP::Target(it) => match &it.which { |
473 | Which::Abi => Target(TargetPredicate::Abi(targ::Abi::new( |
474 | s[it.span.clone().unwrap()].to_owned(), |
475 | ))), |
476 | Which::Arch => Target(TargetPredicate::Arch(targ::Arch::new( |
477 | s[it.span.clone().unwrap()].to_owned(), |
478 | ))), |
479 | Which::Os => Target(TargetPredicate::Os(targ::Os::new( |
480 | s[it.span.clone().unwrap()].to_owned(), |
481 | ))), |
482 | Which::Vendor => Target(TargetPredicate::Vendor(targ::Vendor::new( |
483 | s[it.span.clone().unwrap()].to_owned(), |
484 | ))), |
485 | Which::Env => Target(TargetPredicate::Env(targ::Env::new( |
486 | s[it.span.clone().unwrap()].to_owned(), |
487 | ))), |
488 | Which::Family => Target(TargetPredicate::Family(targ::Family::new( |
489 | s[it.span.clone().unwrap()].to_owned(), |
490 | ))), |
491 | Which::Endian(end) => Target(TargetPredicate::Endian(*end)), |
492 | Which::HasAtomic(has_atomic) => Target(TargetPredicate::HasAtomic(*has_atomic)), |
493 | Which::Panic => Target(TargetPredicate::Panic(targ::Panic::new( |
494 | s[it.span.clone().unwrap()].to_owned(), |
495 | ))), |
496 | Which::PointerWidth(pw) => Target(TargetPredicate::PointerWidth(*pw)), |
497 | }, |
498 | IP::Test => Test, |
499 | IP::DebugAssertions => DebugAssertions, |
500 | IP::ProcMacro => ProcMacro, |
501 | IP::Feature(rng) => Feature(&s[rng.clone()]), |
502 | IP::TargetFeature(rng) => TargetFeature(&s[rng.clone()]), |
503 | IP::Other { identifier, value } => match value { |
504 | Some(vs) => KeyValue { |
505 | key: &s[identifier.clone()], |
506 | val: &s[vs.clone()], |
507 | }, |
508 | None => Flag(&s[identifier.clone()]), |
509 | }, |
510 | } |
511 | } |
512 | } |
513 | |
514 | #[derive (Clone, Debug)] |
515 | pub(crate) enum ExprNode { |
516 | Fn(Func), |
517 | Predicate(InnerPredicate), |
518 | } |
519 | |
520 | /// A parsed `cfg()` expression that can evaluated |
521 | #[derive (Clone, Debug)] |
522 | pub struct Expression { |
523 | pub(crate) expr: SmallVec<[ExprNode; 5]>, |
524 | // We keep the original string around for providing the arbitrary |
525 | // strings that can make up an expression |
526 | pub(crate) original: String, |
527 | } |
528 | |
529 | impl Expression { |
530 | /// An iterator over each predicate in the expression |
531 | pub fn predicates(&self) -> impl Iterator<Item = Predicate<'_>> { |
532 | self.expr.iter().filter_map(move |item| match item { |
533 | ExprNode::Predicate(pred) => { |
534 | let pred = pred.clone().to_pred(&self.original); |
535 | Some(pred) |
536 | } |
537 | ExprNode::Fn(_) => None, |
538 | }) |
539 | } |
540 | |
541 | /// Evaluates the expression, using the provided closure to determine the value of |
542 | /// each predicate, which are then combined into a final result depending on the |
543 | /// functions `not()`, `all()`, or `any()` in the expression. |
544 | /// |
545 | /// `eval_predicate` typically returns `bool`, but may return any type that implements |
546 | /// the `Logic` trait. |
547 | /// |
548 | /// ## Examples |
549 | /// |
550 | /// ``` |
551 | /// use cfg_expr::{targets::*, Expression, Predicate}; |
552 | /// |
553 | /// let linux_musl = get_builtin_target_by_triple("x86_64-unknown-linux-musl" ).unwrap(); |
554 | /// |
555 | /// let expr = Expression::parse(r#"all(not(windows), target_env = "musl", any(target_arch = "x86", target_arch = "x86_64"))"# ).unwrap(); |
556 | /// |
557 | /// assert!(expr.eval(|pred| { |
558 | /// match pred { |
559 | /// Predicate::Target(tp) => tp.matches(linux_musl), |
560 | /// _ => false, |
561 | /// } |
562 | /// })); |
563 | /// ``` |
564 | /// |
565 | /// Returning `Option<bool>`, where `None` indicates the result is unknown: |
566 | /// |
567 | /// ``` |
568 | /// use cfg_expr::{targets::*, Expression, Predicate}; |
569 | /// |
570 | /// let expr = Expression::parse(r#"any(target_feature = "sse2", target_env = "musl")"# ).unwrap(); |
571 | /// |
572 | /// let linux_gnu = get_builtin_target_by_triple("x86_64-unknown-linux-gnu" ).unwrap(); |
573 | /// let linux_musl = get_builtin_target_by_triple("x86_64-unknown-linux-musl" ).unwrap(); |
574 | /// |
575 | /// fn eval(expr: &Expression, target: &TargetInfo) -> Option<bool> { |
576 | /// expr.eval(|pred| { |
577 | /// match pred { |
578 | /// Predicate::Target(tp) => Some(tp.matches(target)), |
579 | /// Predicate::TargetFeature(_) => None, |
580 | /// _ => panic!("unexpected predicate" ), |
581 | /// } |
582 | /// }) |
583 | /// } |
584 | /// |
585 | /// // Whether the target feature is present is unknown, so the whole expression evaluates to |
586 | /// // None (unknown). |
587 | /// assert_eq!(eval(&expr, linux_gnu), None); |
588 | /// |
589 | /// // Whether the target feature is present is irrelevant for musl, since the any() always |
590 | /// // evaluates to true. |
591 | /// assert_eq!(eval(&expr, linux_musl), Some(true)); |
592 | /// ``` |
593 | pub fn eval<EP, T>(&self, mut eval_predicate: EP) -> T |
594 | where |
595 | EP: FnMut(&Predicate<'_>) -> T, |
596 | T: Logic + std::fmt::Debug, |
597 | { |
598 | let mut result_stack = SmallVec::<[T; 8]>::new(); |
599 | |
600 | // We store the expression as postfix, so just evaluate each license |
601 | // requirement in the order it comes, and then combining the previous |
602 | // results according to each operator as it comes |
603 | for node in self.expr.iter() { |
604 | match node { |
605 | ExprNode::Predicate(pred) => { |
606 | let pred = pred.to_pred(&self.original); |
607 | |
608 | result_stack.push(eval_predicate(&pred)); |
609 | } |
610 | ExprNode::Fn(Func::All(count)) => { |
611 | // all() with a comma separated list of configuration predicates. |
612 | let mut result = T::top(); |
613 | |
614 | for _ in 0..*count { |
615 | let r = result_stack.pop().unwrap(); |
616 | result = result.and(r); |
617 | } |
618 | |
619 | result_stack.push(result); |
620 | } |
621 | ExprNode::Fn(Func::Any(count)) => { |
622 | // any() with a comma separated list of configuration predicates. |
623 | let mut result = T::bottom(); |
624 | |
625 | for _ in 0..*count { |
626 | let r = result_stack.pop().unwrap(); |
627 | result = result.or(r); |
628 | } |
629 | |
630 | result_stack.push(result); |
631 | } |
632 | ExprNode::Fn(Func::Not) => { |
633 | // not() with a configuration predicate. |
634 | // It is true if its predicate is false |
635 | // and false if its predicate is true. |
636 | let r = result_stack.pop().unwrap(); |
637 | result_stack.push(r.not()); |
638 | } |
639 | } |
640 | } |
641 | |
642 | result_stack.pop().unwrap() |
643 | } |
644 | |
645 | /// The original string which has been parsed to produce this [`Expression`]. |
646 | /// |
647 | /// ``` |
648 | /// use cfg_expr::Expression; |
649 | /// |
650 | /// assert_eq!( |
651 | /// Expression::parse("any()" ).unwrap().original(), |
652 | /// "any()" |
653 | /// ); |
654 | /// ``` |
655 | #[inline ] |
656 | pub fn original(&self) -> &str { |
657 | &self.original |
658 | } |
659 | } |
660 | |
661 | /// [`PartialEq`] will do a **syntactical** comparison, so will just check if both |
662 | /// expressions have been parsed from the same string, **not** if they are semantically |
663 | /// equivalent. |
664 | /// |
665 | /// ``` |
666 | /// use cfg_expr::Expression; |
667 | /// |
668 | /// assert_eq!( |
669 | /// Expression::parse("any()" ).unwrap(), |
670 | /// Expression::parse("any()" ).unwrap() |
671 | /// ); |
672 | /// assert_ne!( |
673 | /// Expression::parse("any()" ).unwrap(), |
674 | /// Expression::parse("unix" ).unwrap() |
675 | /// ); |
676 | /// ``` |
677 | impl PartialEq for Expression { |
678 | fn eq(&self, other: &Self) -> bool { |
679 | self.original.eq(&other.original) |
680 | } |
681 | } |
682 | |
683 | /// A propositional logic used to evaluate `Expression` instances. |
684 | /// |
685 | /// An `Expression` consists of some predicates and the `any`, `all` and `not` operators. An |
686 | /// implementation of `Logic` defines how the `any`, `all` and `not` operators should be evaluated. |
687 | pub trait Logic { |
688 | /// The result of an `all` operation with no operands, akin to Boolean `true`. |
689 | fn top() -> Self; |
690 | |
691 | /// The result of an `any` operation with no operands, akin to Boolean `false`. |
692 | fn bottom() -> Self; |
693 | |
694 | /// `AND`, which corresponds to the `all` operator. |
695 | fn and(self, other: Self) -> Self; |
696 | |
697 | /// `OR`, which corresponds to the `any` operator. |
698 | fn or(self, other: Self) -> Self; |
699 | |
700 | /// `NOT`, which corresponds to the `not` operator. |
701 | fn not(self) -> Self; |
702 | } |
703 | |
704 | /// A boolean logic. |
705 | impl Logic for bool { |
706 | #[inline ] |
707 | fn top() -> Self { |
708 | true |
709 | } |
710 | |
711 | #[inline ] |
712 | fn bottom() -> Self { |
713 | false |
714 | } |
715 | |
716 | #[inline ] |
717 | fn and(self, other: Self) -> Self { |
718 | self && other |
719 | } |
720 | |
721 | #[inline ] |
722 | fn or(self, other: Self) -> Self { |
723 | self || other |
724 | } |
725 | |
726 | #[inline ] |
727 | fn not(self) -> Self { |
728 | !self |
729 | } |
730 | } |
731 | |
732 | /// A three-valued logic -- `None` stands for the value being unknown. |
733 | /// |
734 | /// The truth tables for this logic are described on |
735 | /// [Wikipedia](https://en.wikipedia.org/wiki/Three-valued_logic#Kleene_and_Priest_logics). |
736 | impl Logic for Option<bool> { |
737 | #[inline ] |
738 | fn top() -> Self { |
739 | Some(true) |
740 | } |
741 | |
742 | #[inline ] |
743 | fn bottom() -> Self { |
744 | Some(false) |
745 | } |
746 | |
747 | #[inline ] |
748 | fn and(self, other: Self) -> Self { |
749 | match (self, other) { |
750 | // If either is false, the expression is false. |
751 | (Some(false), _) | (_, Some(false)) => Some(false), |
752 | // If both are true, the expression is true. |
753 | (Some(true), Some(true)) => Some(true), |
754 | // One or both are unknown -- the result is unknown. |
755 | _ => None, |
756 | } |
757 | } |
758 | |
759 | #[inline ] |
760 | fn or(self, other: Self) -> Self { |
761 | match (self, other) { |
762 | // If either is true, the expression is true. |
763 | (Some(true), _) | (_, Some(true)) => Some(true), |
764 | // If both are false, the expression is false. |
765 | (Some(false), Some(false)) => Some(false), |
766 | // One or both are unknown -- the result is unknown. |
767 | _ => None, |
768 | } |
769 | } |
770 | |
771 | #[inline ] |
772 | fn not(self) -> Self { |
773 | self.map(|v| !v) |
774 | } |
775 | } |
776 | |