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