pub mod lexer;
mod parser;
use smallvec::SmallVec;
use std::ops::Range;
/// A predicate function, used to combine 1 or more predicates
/// into a single value
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
pub enum Func {
/// `not()` with a configuration predicate. It is true if its predicate
/// is false and false if its predicate is true.
Not,
/// `all()` with a comma separated list of configuration predicates. It
/// is false if at least one predicate is false. If there are no predicates,
/// it is true.
///
/// The associated `usize` is the number of predicates inside the `all()`.
All(usize),
/// `any()` with a comma separated list of configuration predicates. It
/// is true if at least one predicate is true. If there are no predicates,
/// it is false.
///
/// The associated `usize` is the number of predicates inside the `any()`.
Any(usize),
}
use crate::targets as targ;
/// All predicates that pertains to a target, except for `target_feature`
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum TargetPredicate {
/// [target_abi](https://github.com/rust-lang/rust/issues/80970)
Abi(targ::Abi),
/// [target_arch](https://doc.rust-lang.org/reference/conditional-compilation.html#target_arch)
Arch(targ::Arch),
/// [target_endian](https://doc.rust-lang.org/reference/conditional-compilation.html#target_endian)
Endian(targ::Endian),
/// [target_env](https://doc.rust-lang.org/reference/conditional-compilation.html#target_env)
Env(targ::Env),
/// [target_family](https://doc.rust-lang.org/reference/conditional-compilation.html#target_family)
/// This also applies to the bare [`unix` and `windows`](https://doc.rust-lang.org/reference/conditional-compilation.html#unix-and-windows)
/// predicates.
Family(targ::Family),
/// [target_has_atomic](https://doc.rust-lang.org/reference/conditional-compilation.html#target_has_atomic).
HasAtomic(targ::HasAtomic),
/// [target_os](https://doc.rust-lang.org/reference/conditional-compilation.html#target_os)
Os(targ::Os),
/// [panic](https://doc.rust-lang.org/reference/conditional-compilation.html#panic)
Panic(targ::Panic),
/// [target_pointer_width](https://doc.rust-lang.org/reference/conditional-compilation.html#target_pointer_width)
PointerWidth(u8),
/// [target_vendor](https://doc.rust-lang.org/reference/conditional-compilation.html#target_vendor)
Vendor(targ::Vendor),
}
pub trait TargetMatcher {
fn matches(&self, tp: &TargetPredicate) -> bool;
}
impl TargetMatcher for targ::TargetInfo {
fn matches(&self, tp: &TargetPredicate) -> bool {
use TargetPredicate::{
Abi, Arch, Endian, Env, Family, HasAtomic, Os, Panic, PointerWidth, Vendor,
};
match tp {
// The ABI is allowed to be an empty string
Abi(abi) => match &self.abi {
Some(a) => abi == a,
None => abi.0.is_empty(),
},
Arch(a) => a == &self.arch,
Endian(end) => *end == self.endian,
// The environment is allowed to be an empty string
Env(env) => match &self.env {
Some(e) => env == e,
None => env.0.is_empty(),
},
Family(fam) => self.families.contains(fam),
HasAtomic(has_atomic) => self.has_atomics.contains(*has_atomic),
Os(os) => Some(os) == self.os.as_ref(),
PointerWidth(w) => *w == self.pointer_width,
Vendor(ven) => match &self.vendor {
Some(v) => ven == v,
None => ven == &targ::Vendor::unknown,
},
Panic(panic) => &self.panic == panic,
}
}
}
#[cfg(feature = "targets")]
impl TargetMatcher for target_lexicon::Triple {
#[allow(clippy::cognitive_complexity)]
#[allow(clippy::match_same_arms)]
fn matches(&self, tp: &TargetPredicate) -> bool {
use target_lexicon::*;
use TargetPredicate::{
Abi, Arch, Endian, Env, Family, HasAtomic, Os, Panic, PointerWidth, Vendor,
};
match tp {
Abi(_) => {
// `target_abi` is unstable. Assume false for this.
false
}
Arch(arch) => {
if arch == &targ::Arch::x86 {
matches!(self.architecture, Architecture::X86_32(_))
} else if arch == &targ::Arch::wasm32 {
self.architecture == Architecture::Wasm32
|| self.architecture == Architecture::Asmjs
} else if arch == &targ::Arch::arm {
matches!(self.architecture, Architecture::Arm(_))
} else if arch == &targ::Arch::bpf {
self.architecture == Architecture::Bpfeb
|| self.architecture == Architecture::Bpfel
} else {
match arch.0.parse::<Architecture>() {
Ok(a) => match (self.architecture, a) {
(Architecture::Aarch64(_), Architecture::Aarch64(_))
| (Architecture::Mips32(_), Architecture::Mips32(_))
| (Architecture::Mips64(_), Architecture::Mips64(_))
| (Architecture::Powerpc64le, Architecture::Powerpc64)
| (Architecture::Riscv32(_), Architecture::Riscv32(_))
| (Architecture::Riscv64(_), Architecture::Riscv64(_))
| (Architecture::Sparcv9, Architecture::Sparc64) => true,
(a, b) => a == b,
},
Err(_) => false,
}
}
}
Endian(end) => match self.architecture.endianness() {
Ok(endian) => matches!(
(end, endian),
(crate::targets::Endian::little, Endianness::Little)
| (crate::targets::Endian::big, Endianness::Big)
),
Err(_) => false,
},
Env(env) => {
// The environment is implied by some operating systems
match self.operating_system {
OperatingSystem::Redox => env == &targ::Env::relibc,
OperatingSystem::VxWorks => env == &targ::Env::gnu,
OperatingSystem::Freebsd => match self.architecture {
Architecture::Arm(ArmArchitecture::Armv6 | ArmArchitecture::Armv7) => {
env == &targ::Env::gnueabihf
}
_ => env.0.is_empty(),
},
OperatingSystem::Netbsd => match self.architecture {
Architecture::Arm(ArmArchitecture::Armv6 | ArmArchitecture::Armv7) => {
env == &targ::Env::eabihf
}
_ => env.0.is_empty(),
},
OperatingSystem::None_
| OperatingSystem::Cloudabi
| OperatingSystem::Hermit
| OperatingSystem::Ios => match self.environment {
Environment::LinuxKernel => env == &targ::Env::gnu,
_ => env.0.is_empty(),
},
_ => {
if env.0.is_empty() {
matches!(
self.environment,
Environment::Unknown
| Environment::Android
| Environment::Softfloat
| Environment::Androideabi
| Environment::Eabi
| Environment::Eabihf
| Environment::Sim
)
} else {
match env.0.parse::<Environment>() {
Ok(e) => {
// Rustc shortens multiple "gnu*" environments to just "gnu"
if env == &targ::Env::gnu {
match self.environment {
Environment::Gnu
| Environment::Gnuabi64
| Environment::Gnueabi
| Environment::Gnuspe
| Environment::Gnux32
| Environment::GnuIlp32
| Environment::Gnueabihf
| Environment::GnuLlvm => true,
// Rust 1.49.0 changed all android targets to have the
// gnu environment
Environment::Android | Environment::Androideabi
if self.operating_system
== OperatingSystem::Linux =>
{
true
}
Environment::Kernel => {
self.operating_system == OperatingSystem::Linux
}
_ => false,
}
} else if env == &targ::Env::musl {
matches!(
self.environment,
Environment::Musl
| Environment::Musleabi
| Environment::Musleabihf
| Environment::Muslabi64
)
} else if env == &targ::Env::uclibc {
matches!(
self.environment,
Environment::Uclibc
| Environment::Uclibceabi
| Environment::Uclibceabihf
)
} else {
self.environment == e
}
}
Err(_) => false,
}
}
}
}
}
Family(fam) => {
use target_lexicon::OperatingSystem::{
AmdHsa, Bitrig, Cloudabi, Cuda, Darwin, Dragonfly, Emscripten, Espidf, Freebsd,
Fuchsia, Haiku, Hermit, Horizon, Illumos, Ios, L4re, Linux, MacOSX, Nebulet,
Netbsd, None_, Openbsd, Redox, Solaris, Tvos, Uefi, Unknown, VxWorks, Wasi,
Watchos, Windows,
};
match self.operating_system {
AmdHsa | Bitrig | Cloudabi | Cuda | Hermit | Nebulet | None_ | Uefi => false,
Darwin
| Dragonfly
| Espidf
| Freebsd
| Fuchsia
| Haiku
| Illumos
| Ios
| L4re
| MacOSX { .. }
| Horizon
| Netbsd
| Openbsd
| Redox
| Solaris
| Tvos
| VxWorks
| Watchos => fam == &crate::targets::Family::unix,
Emscripten => {
match self.architecture {
// asmjs, wasm32 and wasm64 are part of both the wasm and unix families
Architecture::Asmjs | Architecture::Wasm32 => {
fam == &crate::targets::Family::wasm
|| fam == &crate::targets::Family::unix
}
_ => false,
}
}
Unknown => {
// asmjs, wasm32 and wasm64 are part of the wasm family.
match self.architecture {
Architecture::Asmjs | Architecture::Wasm32 | Architecture::Wasm64 => {
fam == &crate::targets::Family::wasm
}
_ => false,
}
}
Linux => {
// The 'kernel' environment is treated specially as not-unix
if self.environment != Environment::Kernel {
fam == &crate::targets::Family::unix
} else {
false
}
}
Wasi => fam == &crate::targets::Family::wasm,
Windows => fam == &crate::targets::Family::windows,
// I really dislike non-exhaustive :(
_ => false,
}
}
HasAtomic(_) => {
// atomic support depends on both the architecture and the OS. Assume false for
// this.
false
}
Os(os) => match os.0.parse::<OperatingSystem>() {
Ok(o) => match self.environment {
Environment::HermitKernel => os == &targ::Os::hermit,
_ => self.operating_system == o,
},
Err(_) => {
// Handle special case for darwin/macos, where the triple is
// "darwin", but rustc identifies the OS as "macos"
if os == &targ::Os::macos && self.operating_system == OperatingSystem::Darwin {
true
} else {
// For android, the os is still linux, but the environment is android
os == &targ::Os::android
&& self.operating_system == OperatingSystem::Linux
&& (self.environment == Environment::Android
|| self.environment == Environment::Androideabi)
}
}
},
Panic(_) => {
// panic support depends on the OS. Assume false for this.
false
}
Vendor(ven) => match ven.0.parse::<target_lexicon::Vendor>() {
Ok(v) => self.vendor == v,
Err(_) => false,
},
PointerWidth(pw) => {
// The gnux32 environment is a special case, where it has an
// x86_64 architecture, but a 32-bit pointer width
if !matches!(
self.environment,
Environment::Gnux32 | Environment::GnuIlp32
) {
*pw == match self.pointer_width() {
Ok(pw) => pw.bits(),
Err(_) => return false,
}
} else {
*pw == 32
}
}
}
}
}
impl TargetPredicate {
/// Returns true of the predicate matches the specified target
///
/// Note that when matching against a [`target_lexicon::Triple`], the
/// `has_target_atomic` and `panic` predicates will _always_ return `false`.
///
/// ```
/// use cfg_expr::{targets::*, expr::TargetPredicate as tp};
/// let win = get_builtin_target_by_triple("x86_64-pc-windows-msvc").unwrap();
///
/// assert!(
/// tp::Arch(Arch::x86_64).matches(win) &&
/// tp::Endian(Endian::little).matches(win) &&
/// tp::Env(Env::msvc).matches(win) &&
/// tp::Family(Family::windows).matches(win) &&
/// tp::Os(Os::windows).matches(win) &&
/// tp::PointerWidth(64).matches(win) &&
/// tp::Vendor(Vendor::pc).matches(win)
/// );
/// ```
pub fn matches<T>(&self, target: &T) -> bool
where
T: TargetMatcher,
{
target.matches(self)
}
}
#[derive(Clone, Debug)]
pub(crate) enum Which {
Abi,
Arch,
Endian(targ::Endian),
Env,
Family,
Os,
HasAtomic(targ::HasAtomic),
Panic,
PointerWidth(u8),
Vendor,
}
#[derive(Clone, Debug)]
pub(crate) struct InnerTarget {
which: Which,
span: Option<Range<usize>>,
}
/// A single predicate in a `cfg()` expression
#[derive(Debug, PartialEq, Eq)]
pub enum Predicate<'a> {
/// A target predicate, with the `target_` prefix
Target(TargetPredicate),
/// Whether rustc's test harness is [enabled](https://doc.rust-lang.org/reference/conditional-compilation.html#test)
Test,
/// [Enabled](https://doc.rust-lang.org/reference/conditional-compilation.html#debug_assertions)
/// when compiling without optimizations.
DebugAssertions,
/// [Enabled](https://doc.rust-lang.org/reference/conditional-compilation.html#proc_macro) for
/// crates of the proc_macro type.
ProcMacro,
/// A [`feature = "<name>"`](https://doc.rust-lang.org/nightly/cargo/reference/features.html)
Feature(&'a str),
/// [target_feature](https://doc.rust-lang.org/reference/conditional-compilation.html#target_feature)
TargetFeature(&'a str),
/// A generic bare predicate key that doesn't match one of the known options, eg `cfg(bare)`
Flag(&'a str),
/// A generic key = "value" predicate that doesn't match one of the known options, eg `cfg(foo = "bar")`
KeyValue { key: &'a str, val: &'a str },
}
#[derive(Clone, Debug)]
pub(crate) enum InnerPredicate {
Target(InnerTarget),
Test,
DebugAssertions,
ProcMacro,
Feature(Range<usize>),
TargetFeature(Range<usize>),
Other {
identifier: Range<usize>,
value: Option<Range<usize>>,
},
}
impl InnerPredicate {
fn to_pred<'a>(&self, s: &'a str) -> Predicate<'a> {
use InnerPredicate as IP;
use Predicate::{
DebugAssertions, Feature, Flag, KeyValue, ProcMacro, Target, TargetFeature, Test,
};
match self {
IP::Target(it) => match &it.which {
Which::Abi => Target(TargetPredicate::Abi(targ::Abi::new(
s[it.span.clone().unwrap()].to_owned(),
))),
Which::Arch => Target(TargetPredicate::Arch(targ::Arch::new(
s[it.span.clone().unwrap()].to_owned(),
))),
Which::Os => Target(TargetPredicate::Os(targ::Os::new(
s[it.span.clone().unwrap()].to_owned(),
))),
Which::Vendor => Target(TargetPredicate::Vendor(targ::Vendor::new(
s[it.span.clone().unwrap()].to_owned(),
))),
Which::Env => Target(TargetPredicate::Env(targ::Env::new(
s[it.span.clone().unwrap()].to_owned(),
))),
Which::Family => Target(TargetPredicate::Family(targ::Family::new(
s[it.span.clone().unwrap()].to_owned(),
))),
Which::Endian(end) => Target(TargetPredicate::Endian(*end)),
Which::HasAtomic(has_atomic) => Target(TargetPredicate::HasAtomic(*has_atomic)),
Which::Panic => Target(TargetPredicate::Panic(targ::Panic::new(
s[it.span.clone().unwrap()].to_owned(),
))),
Which::PointerWidth(pw) => Target(TargetPredicate::PointerWidth(*pw)),
},
IP::Test => Test,
IP::DebugAssertions => DebugAssertions,
IP::ProcMacro => ProcMacro,
IP::Feature(rng) => Feature(&s[rng.clone()]),
IP::TargetFeature(rng) => TargetFeature(&s[rng.clone()]),
IP::Other { identifier, value } => match value {
Some(vs) => KeyValue {
key: &s[identifier.clone()],
val: &s[vs.clone()],
},
None => Flag(&s[identifier.clone()]),
},
}
}
}
#[derive(Clone, Debug)]
pub(crate) enum ExprNode {
Fn(Func),
Predicate(InnerPredicate),
}
/// A parsed `cfg()` expression that can evaluated
#[derive(Clone, Debug)]
pub struct Expression {
pub(crate) expr: SmallVec<[ExprNode; 5]>,
// We keep the original string around for providing the arbitrary
// strings that can make up an expression
pub(crate) original: String,
}
impl Expression {
/// An iterator over each predicate in the expression
pub fn predicates(&self) -> impl Iterator<Item = Predicate<'_>> {
self.expr.iter().filter_map(move |item| match item {
ExprNode::Predicate(pred) => {
let pred = pred.clone().to_pred(&self.original);
Some(pred)
}
ExprNode::Fn(_) => None,
})
}
/// Evaluates the expression, using the provided closure to determine the value of
/// each predicate, which are then combined into a final result depending on the
/// functions not(), all(), or any() in the expression.
///
/// `eval_predicate` typically returns `bool`, but may return any type that implements
/// the `Logic` trait.
///
/// ## Examples
///
/// ```
/// use cfg_expr::{targets::*, Expression, Predicate};
///
/// let linux_musl = get_builtin_target_by_triple("x86_64-unknown-linux-musl").unwrap();
///
/// let expr = Expression::parse(r#"all(not(windows), target_env = "musl", any(target_arch = "x86", target_arch = "x86_64"))"#).unwrap();
///
/// assert!(expr.eval(|pred| {
/// match pred {
/// Predicate::Target(tp) => tp.matches(linux_musl),
/// _ => false,
/// }
/// }));
/// ```
///
/// Returning `Option<bool>`, where `None` indicates the result is unknown:
///
/// ```
/// use cfg_expr::{targets::*, Expression, Predicate};
///
/// let expr = Expression::parse(r#"any(target_feature = "sse2", target_env = "musl")"#).unwrap();
///
/// let linux_gnu = get_builtin_target_by_triple("x86_64-unknown-linux-gnu").unwrap();
/// let linux_musl = get_builtin_target_by_triple("x86_64-unknown-linux-musl").unwrap();
///
/// fn eval(expr: &Expression, target: &TargetInfo) -> Option<bool> {
/// expr.eval(|pred| {
/// match pred {
/// Predicate::Target(tp) => Some(tp.matches(target)),
/// Predicate::TargetFeature(_) => None,
/// _ => panic!("unexpected predicate"),
/// }
/// })
/// }
///
/// // Whether the target feature is present is unknown, so the whole expression evaluates to
/// // None (unknown).
/// assert_eq!(eval(&expr, linux_gnu), None);
///
/// // Whether the target feature is present is irrelevant for musl, since the any() always
/// // evaluates to true.
/// assert_eq!(eval(&expr, linux_musl), Some(true));
/// ```
pub fn eval<EP, T>(&self, mut eval_predicate: EP) -> T
where
EP: FnMut(&Predicate<'_>) -> T,
T: Logic + std::fmt::Debug,
{
let mut result_stack = SmallVec::<[T; 8]>::new();
// We store the expression as postfix, so just evaluate each license
// requirement in the order it comes, and then combining the previous
// results according to each operator as it comes
for node in self.expr.iter() {
match node {
ExprNode::Predicate(pred) => {
let pred = pred.to_pred(&self.original);
result_stack.push(eval_predicate(&pred));
}
ExprNode::Fn(Func::All(count)) => {
// all() with a comma separated list of configuration predicates.
let mut result = T::top();
for _ in 0..*count {
let r = result_stack.pop().unwrap();
result = result.and(r);
}
result_stack.push(result);
}
ExprNode::Fn(Func::Any(count)) => {
// any() with a comma separated list of configuration predicates.
let mut result = T::bottom();
for _ in 0..*count {
let r = result_stack.pop().unwrap();
result = result.or(r);
}
result_stack.push(result);
}
ExprNode::Fn(Func::Not) => {
// not() with a configuration predicate.
// It is true if its predicate is false
// and false if its predicate is true.
let r = result_stack.pop().unwrap();
result_stack.push(r.not());
}
}
}
result_stack.pop().unwrap()
}
/// The original string which has been parsed to produce this [`Expression`].
///
/// ```
/// use cfg_expr::Expression;
///
/// assert_eq!(
/// Expression::parse("any()").unwrap().original(),
/// "any()"
/// );
/// ```
#[inline]
pub fn original(&self) -> &str {
&self.original
}
}
/// [`PartialEq`] will do a **syntactical** comparaison, so will just check if both
/// expressions have been parsed from the same string, **not** if they are semantically
/// equivalent.
///
/// ```
/// use cfg_expr::Expression;
///
/// assert_eq!(
/// Expression::parse("any()").unwrap(),
/// Expression::parse("any()").unwrap()
/// );
/// assert_ne!(
/// Expression::parse("any()").unwrap(),
/// Expression::parse("unix").unwrap()
/// );
/// ```
impl PartialEq for Expression {
fn eq(&self, other: &Self) -> bool {
self.original.eq(&other.original)
}
}
/// A propositional logic used to evaluate `Expression` instances.
///
/// An `Expression` consists of some predicates and the `any`, `all` and `not` operators. An
/// implementation of `Logic` defines how the `any`, `all` and `not` operators should be evaluated.
pub trait Logic {
/// The result of an `all` operation with no operands, akin to Boolean `true`.
fn top() -> Self;
/// The result of an `any` operation with no operands, akin to Boolean `false`.
fn bottom() -> Self;
/// `AND`, which corresponds to the `all` operator.
fn and(self, other: Self) -> Self;
/// `OR`, which corresponds to the `any` operator.
fn or(self, other: Self) -> Self;
/// `NOT`, which corresponds to the `not` operator.
fn not(self) -> Self;
}
/// A boolean logic.
impl Logic for bool {
#[inline]
fn top() -> Self {
true
}
#[inline]
fn bottom() -> Self {
false
}
#[inline]
fn and(self, other: Self) -> Self {
self && other
}
#[inline]
fn or(self, other: Self) -> Self {
self || other
}
#[inline]
fn not(self) -> Self {
!self
}
}
/// A three-valued logic -- `None` stands for the value being unknown.
///
/// The truth tables for this logic are described on
/// [Wikipedia](https://en.wikipedia.org/wiki/Three-valued_logic#Kleene_and_Priest_logics).
impl Logic for Option<bool> {
#[inline]
fn top() -> Self {
Some(true)
}
#[inline]
fn bottom() -> Self {
Some(false)
}
#[inline]
fn and(self, other: Self) -> Self {
match (self, other) {
// If either is false, the expression is false.
(Some(false), _) | (_, Some(false)) => Some(false),
// If both are true, the expression is true.
(Some(true), Some(true)) => Some(true),
// One or both are unknown -- the result is unknown.
_ => None,
}
}
#[inline]
fn or(self, other: Self) -> Self {
match (self, other) {
// If either is true, the expression is true.
(Some(true), _) | (_, Some(true)) => Some(true),
// If both are false, the expression is false.
(Some(false), Some(false)) => Some(false),
// One or both are unknown -- the result is unknown.
_ => None,
}
}
#[inline]
fn not(self) -> Self {
self.map(|v| !v)
}
}