use crate::{
Error, Platform, Triple,
errors::{ExpressionParseError, PlainStringParseError},
};
use cfg_expr::{Expression, Predicate};
use std::{borrow::Cow, fmt, str::FromStr, sync::Arc};
#[derive(Clone, Debug)]
pub enum TargetSpec {
Expression(TargetSpecExpression),
PlainString(TargetSpecPlainString),
}
impl TargetSpec {
pub fn new(input: impl Into<Cow<'static, str>>) -> Result<Self, Error> {
let input = input.into();
if Self::looks_like_expression(&input) {
match TargetSpecExpression::new(&input) {
Ok(expression) => Ok(Self::Expression(expression)),
Err(error) => Err(Error::InvalidExpression(error)),
}
} else {
match TargetSpecPlainString::new(input) {
Ok(plain_str) => Ok(Self::PlainString(plain_str)),
Err(error) => Err(Error::InvalidTargetSpecString(error)),
}
}
}
pub fn looks_like_expression(input: &str) -> bool {
input.starts_with("cfg(")
}
pub fn looks_like_plain_string(input: &str) -> bool {
TargetSpecPlainString::validate(input).is_ok()
}
pub fn is_known(&self) -> bool {
match self {
TargetSpec::PlainString(plain_str) => {
Triple::new(plain_str.as_str().to_owned()).is_ok()
}
TargetSpec::Expression(_) => true,
}
}
#[inline]
pub fn eval(&self, platform: &Platform) -> Option<bool> {
match self {
TargetSpec::PlainString(plain_str) => Some(platform.triple_str() == plain_str.as_str()),
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())
}
}
impl fmt::Display for TargetSpec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TargetSpec::Expression(expr) => write!(f, "{expr}"),
TargetSpec::PlainString(plain_str) => write!(f, "{plain_str}"),
}
}
}
#[derive(Clone, Debug)]
pub struct TargetSpecExpression {
inner: Arc<Expression>,
}
impl TargetSpecExpression {
pub fn new(input: &str) -> Result<Self, ExpressionParseError> {
let expr = Expression::parse(input).map_err(|err| 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 TargetSpecExpression {
type Err = ExpressionParseError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
Self::new(input)
}
}
impl fmt::Display for TargetSpecExpression {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.expression_str())
}
}
#[derive(Clone, Debug)]
pub struct TargetSpecPlainString(Cow<'static, str>);
impl TargetSpecPlainString {
pub fn new(input: impl Into<Cow<'static, str>>) -> Result<Self, PlainStringParseError> {
let input = input.into();
Self::validate(&input)?;
Ok(Self(input))
}
pub fn as_str(&self) -> &str {
&self.0
}
fn validate(input: &str) -> Result<(), PlainStringParseError> {
if let Some((char_index, c)) = input
.char_indices()
.find(|&(_, c)| !(c.is_alphanumeric() || c == '_' || c == '-' || c == '.'))
{
Err(PlainStringParseError::new(input.to_owned(), char_index, c))
} else {
Ok(())
}
}
}
impl FromStr for TargetSpecPlainString {
type Err = PlainStringParseError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
Self::new(input.to_owned())
}
}
impl fmt::Display for TargetSpecPlainString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use cfg_expr::{
Predicate, TargetPredicate,
targets::{Abi, Arch, Family, Os},
};
#[test]
fn test_triple() {
let res = TargetSpec::new("x86_64-apple-darwin");
assert!(matches!(
res,
Ok(TargetSpec::PlainString(triple)) if triple.as_str() == "x86_64-apple-darwin"
));
}
#[test]
fn test_single() {
let expr = match TargetSpec::new("cfg(windows)").unwrap() {
TargetSpec::PlainString(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::PlainString(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::PlainString(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_identifier_like_triple() {
let spec = TargetSpec::new("cannotbeknown")
.expect("triples that look like identifiers should parse correctly");
assert!(!spec.is_known(), "spec isn't known");
}
#[test]
fn test_triple_string_identifier() {
let valid = ["", "foo", "foo-bar", "foo_baz", "-foo", "quux-"];
let invalid = ["foo+bar", "foo bar", " "];
for input in valid {
assert!(
TargetSpec::looks_like_plain_string(input),
"`{input}` looks like triple string"
);
}
for input in invalid {
assert!(
!TargetSpec::looks_like_plain_string(input),
"`{input}` does not look like triple string"
);
}
}
#[test]
fn test_unknown_triple() {
let err = TargetSpec::new("cannotbeknown!!!")
.expect_err("unknown triples should parse correctly");
assert!(matches!(
err,
Error::InvalidTargetSpecString(s) if s.input == "cannotbeknown!!!"
));
}
#[test]
fn test_unknown_flag() {
let expr = match TargetSpec::new("cfg(foo)").unwrap() {
TargetSpec::PlainString(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::PlainString(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::build_target().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");
}
}