use crate::{errors::ExpressionParseError, Error, Platform, Triple};
use cfg_expr::{Expression, Predicate};
use std::{borrow::Cow, str::FromStr, sync::Arc};
#[derive(Clone, Debug)]
pub enum TargetSpec {
Triple(Triple),
Expression(TargetExpression),
}
impl TargetSpec {
pub fn new(input: impl Into<Cow<'static, str>>) -> Result<Self, Error> {
let input = input.into();
if input.starts_with("cfg(") {
Ok(TargetSpec::Expression(TargetExpression::new(&input)?))
} else {
match Triple::new(input) {
Ok(triple) => Ok(TargetSpec::Triple(triple)),
Err(parse_err) => Err(Error::UnknownTargetTriple(parse_err)),
}
}
}
#[inline]
pub fn eval(&self, platform: &Platform) -> Option<bool> {
match self {
TargetSpec::Triple(triple) => Some(triple.eval(platform)),
TargetSpec::Expression(expr) => expr.eval(platform),
}
}
}
impl FromStr for TargetSpec {
type Err = Error;
fn from_str(input: &str) -> Result<Self, Self::Err> {
Self::new(input.to_owned())
}
}
#[derive(Clone, Debug)]
pub struct TargetExpression {
inner: Arc<Expression>,
}
impl TargetExpression {
pub fn new(input: &str) -> Result<Self, Error> {
let expr = Expression::parse(input)
.map_err(|err| Error::InvalidExpression(ExpressionParseError::new(input, err)))?;
Ok(Self {
inner: Arc::new(expr),
})
}
#[inline]
pub fn expression_str(&self) -> &str {
self.inner.original()
}
pub fn eval(&self, platform: &Platform) -> Option<bool> {
self.inner.eval(|pred| {
match pred {
Predicate::Target(target) => Some(platform.triple().matches(target)),
Predicate::TargetFeature(feature) => platform.target_features().matches(feature),
Predicate::Test | Predicate::DebugAssertions | Predicate::ProcMacro => {
Some(false)
}
Predicate::Feature(_) => {
Some(false)
}
Predicate::Flag(flag) => {
Some(platform.has_flag(flag))
}
Predicate::KeyValue { .. } => {
Some(false)
}
}
})
}
}
impl FromStr for TargetExpression {
type Err = Error;
fn from_str(input: &str) -> Result<Self, Self::Err> {
Self::new(input)
}
}
#[cfg(test)]
mod tests {
use super::*;
use cfg_expr::{
targets::{Abi, Arch, Family, Os},
Predicate, TargetPredicate,
};
#[test]
fn test_triple() {
let res = TargetSpec::new("x86_64-apple-darwin");
assert!(matches!(
res,
Ok(TargetSpec::Triple(triple)) if triple.as_str() == "x86_64-apple-darwin"
));
}
#[test]
fn test_single() {
let expr = match TargetSpec::new("cfg(windows)").unwrap() {
TargetSpec::Triple(triple) => {
panic!("expected expression, got triple: {:?}", triple)
}
TargetSpec::Expression(expr) => expr,
};
assert_eq!(
expr.inner.predicates().collect::<Vec<_>>(),
vec![Predicate::Target(TargetPredicate::Family(Family::windows))],
);
}
#[test]
fn test_target_abi() {
let expr =
match TargetSpec::new("cfg(any(target_arch = \"wasm32\", target_abi = \"unknown\"))")
.unwrap()
{
TargetSpec::Triple(triple) => {
panic!("expected expression, got triple: {:?}", triple)
}
TargetSpec::Expression(expr) => expr,
};
assert_eq!(
expr.inner.predicates().collect::<Vec<_>>(),
vec![
Predicate::Target(TargetPredicate::Arch(Arch("wasm32".into()))),
Predicate::Target(TargetPredicate::Abi(Abi("unknown".into()))),
],
);
}
#[test]
fn test_not() {
assert!(matches!(
TargetSpec::new("cfg(not(windows))"),
Ok(TargetSpec::Expression(_))
));
}
#[test]
fn test_testequal() {
let expr = match TargetSpec::new("cfg(target_os = \"windows\")").unwrap() {
TargetSpec::Triple(triple) => {
panic!("expected spec, got triple: {:?}", triple)
}
TargetSpec::Expression(expr) => expr,
};
assert_eq!(
expr.inner.predicates().collect::<Vec<_>>(),
vec![Predicate::Target(TargetPredicate::Os(Os::windows))],
);
}
#[test]
fn test_unknown_triple() {
let err = TargetSpec::new("cannotbeknown").expect_err("unknown triple");
assert!(matches!(
err,
Error::UnknownTargetTriple(parse_err) if parse_err.triple_str() == "cannotbeknown"
));
}
#[test]
fn test_unknown_flag() {
let expr = match TargetSpec::new("cfg(foo)").unwrap() {
TargetSpec::Triple(triple) => {
panic!("expected spec, got triple: {:?}", triple)
}
TargetSpec::Expression(expr) => expr,
};
assert_eq!(
expr.inner.predicates().collect::<Vec<_>>(),
vec![Predicate::Flag("foo")],
);
}
#[test]
fn test_unknown_predicate() {
let expr = match TargetSpec::new("cfg(bogus_key = \"bogus_value\")")
.expect("unknown predicate should parse")
{
TargetSpec::Triple(triple) => {
panic!("expected spec, got triple: {:?}", triple)
}
TargetSpec::Expression(expr) => expr,
};
assert_eq!(
expr.inner.predicates().collect::<Vec<_>>(),
vec![Predicate::KeyValue {
key: "bogus_key",
val: "bogus_value"
}],
);
let platform = Platform::current().unwrap();
assert_eq!(expr.eval(&platform), Some(false));
let expr = TargetSpec::new("cfg(not(bogus_key = \"bogus_value\"))")
.expect("unknown predicate should parse");
assert_eq!(expr.eval(&platform), Some(true));
}
#[test]
fn test_extra() {
let res = TargetSpec::new("cfg(unix)this-is-extra");
res.expect_err("extra content at the end");
}
#[test]
fn test_incomplete() {
let res = TargetSpec::new("cfg(not(unix)");
res.expect_err("missing ) at the end");
}
}