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