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::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
379impl 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)]
408pub(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)]
422pub(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)]
429pub 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)]
451pub(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
464impl 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)]
515pub(crate) enum ExprNode {
516 Fn(Func),
517 Predicate(InnerPredicate),
518}
519
520/// A parsed `cfg()` expression that can evaluated
521#[derive(Clone, Debug)]
522pub 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
529impl 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/// ```
677impl 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.
687pub 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.
705impl 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).
736impl 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