use std::collections::HashSet;
use crate::platforms::{Endian, SystemInfo};
use proc_macro2::Ident;
use syn::punctuated::Punctuated;
use syn::token::Paren;
use syn::{parenthesized, Meta};
use syn::{
parse::{Parse, ParseStream},
LitStr, Token,
};
#[derive(Debug)]
pub struct CfgAttrGroup {
pub cfg: CfgPredicate,
pub comma: Token![,],
pub attrs: Punctuated<Meta, Token![,]>,
}
impl Parse for CfgAttrGroup {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self {
cfg: input.parse()?,
comma: input.parse()?,
attrs: Punctuated::parse_terminated(input)?,
})
}
}
pub trait CfgEval {
fn eval(&self, features: &HashSet<String>, platform: &SystemInfo) -> bool;
}
macro_rules! parse_struct {
($s:ty, $($i:ident),*) => {
impl Parse for $s {
fn parse(input: ParseStream) -> syn::Result<Self> {
let x = Ok(
Self {
$($i: Parse::parse(input)?),*
}
);
x
}
}
}
}
pub type CfgPredicateList = Punctuated<CfgPredicate, Token![,]>;
#[derive(Debug, PartialEq, Eq)]
pub enum CfgPredicate {
Option(CfgOption),
All(CfgAll),
Not(CfgNot),
Any(CfgAny),
}
impl CfgEval for CfgPredicate {
fn eval(&self, features: &HashSet<String>, platform: &SystemInfo) -> bool {
match self {
Self::Option(x) => x.eval(features, platform),
Self::All(x) => x.eval(features, platform),
Self::Not(x) => x.eval(features, platform),
Self::Any(x) => x.eval(features, platform),
}
}
}
impl Parse for CfgPredicate {
fn parse(input: ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::all) {
input.parse().map(CfgPredicate::All)
} else if lookahead.peek(kw::not) {
input.parse().map(CfgPredicate::Not)
} else if lookahead.peek(kw::any) {
input.parse().map(CfgPredicate::Any)
} else {
input.parse().map(CfgPredicate::Option)
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct CfgOption {
pub ident: Ident,
pub inner: Option<CfgOptionInner>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct CfgOptionInner {
pub eq: Token![=],
pub val: LitStr,
}
impl CfgEval for CfgOption {
fn eval(&self, features: &HashSet<String>, platform: &SystemInfo) -> bool {
let inner = self.inner.as_ref().map(|inner| inner.val.value());
let ident = self.ident.to_string();
match ident.as_str() {
"feature" => match inner {
Some(inner) => features.contains(&inner),
None => true,
},
"target_arch" => match inner {
Some(inner) => platform.target_arch == inner,
None => false,
},
"target_feature" => match inner {
Some(inner) => platform.target_features.contains(&inner),
None => false,
},
"target_os" => match inner {
Some(inner) => platform.target_os == inner,
None => false,
},
"target_family" => match inner {
Some(inner) => platform.target_family.contains(&inner),
None => false,
},
"target_env" => match inner {
Some(inner) => platform.target_env == inner,
None => false,
},
"target_endian" => match inner {
Some(inner) => inner
.parse::<Endian>()
.map(|x| x == platform.target_endian)
.unwrap_or(false),
None => false,
},
"target_pointer_width" => match inner {
Some(inner) => inner
.parse::<usize>()
.map(|x| x == platform.target_pointer_width)
.unwrap_or(false),
None => false,
},
"target_vendor" => match inner {
Some(inner) => platform.target_vendor == inner,
None => false,
},
"test" => platform.test,
"debug_assertions" => platform.debug_assertions,
"proc_macro" => platform.proc_macro,
"panic" => match inner {
Some(inner) => platform.panic == inner,
None => false,
},
_ => false,
}
}
}
parse_struct! { CfgOptionInner, eq, val }
impl Parse for CfgOption {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self {
ident: input.parse()?,
inner: {
let lookahead = input.lookahead1();
if lookahead.peek(Token!(=)) {
Some(input.parse()?)
} else {
None
}
},
})
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct CfgAll {
pub all: kw::all,
pub paren_token: Paren,
pub elem: CfgPredicateList,
}
impl CfgEval for CfgAll {
fn eval(&self, features: &HashSet<String>, platform: &SystemInfo) -> bool {
let mut x = true;
for elem in &self.elem {
x &= elem.eval(features, platform);
}
x
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct CfgNot {
pub not: kw::not,
pub paren_token: Paren,
pub elem: Box<CfgPredicate>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct CfgAny {
pub any: kw::any,
pub paren_token: Paren,
pub elem: CfgPredicateList,
}
impl CfgEval for CfgNot {
fn eval(&self, features: &HashSet<String>, platform: &SystemInfo) -> bool {
!self.elem.eval(features, platform)
}
}
impl CfgEval for CfgAny {
fn eval(&self, features: &HashSet<String>, platform: &SystemInfo) -> bool {
let mut x = false;
for elem in &self.elem {
x |= elem.eval(features, platform);
}
x
}
}
impl Parse for CfgAll {
fn parse(input: ParseStream) -> syn::Result<Self> {
let content;
Ok(Self {
all: input.parse()?,
paren_token: parenthesized!(content in input),
elem: CfgPredicateList::parse_terminated(&content)?,
})
}
}
impl Parse for CfgAny {
fn parse(input: ParseStream) -> syn::Result<Self> {
let content;
Ok(Self {
any: input.parse()?,
paren_token: parenthesized!(content in input),
elem: CfgPredicateList::parse_terminated(&content)?,
})
}
}
impl Parse for CfgNot {
fn parse(input: ParseStream) -> syn::Result<Self> {
let content;
Ok(Self {
not: input.parse()?,
paren_token: parenthesized!(content in input),
elem: content.parse()?,
})
}
}
mod kw {
syn::custom_keyword!(cfg);
syn::custom_keyword!(all);
syn::custom_keyword!(not);
syn::custom_keyword!(any);
}
#[cfg(test)]
mod test {
use proc_macro2::Span;
use super::*;
#[test]
fn test_cfg_option() {
let res: CfgOption = syn::parse_quote! {
feature = "true"
};
assert_eq!(res.ident.to_string(), "feature");
assert_eq!(res.inner.unwrap().val.value(), "true");
let res: CfgOption = syn::parse_quote! {
test
};
assert_eq!(res.ident.to_string(), "test");
assert!(res.inner.is_none());
}
#[test]
fn test_not() {
let res: CfgNot = syn::parse_quote! {
not(feature = "true")
};
if let CfgPredicate::Option(res) = *res.elem {
assert_eq!(res.ident.to_string(), "feature");
assert_eq!(res.inner.unwrap().val.value(), "true");
} else {
panic!("Should be an option");
}
}
#[test]
fn test_any() {
let res: CfgAny = syn::parse_quote! {
any(feature = "f1", feature = "f2")
};
assert_eq!(
res,
CfgAny {
any: kw::any(Span::call_site()),
paren_token: Paren::default(),
elem: {
let mut p = Punctuated::new();
p.push(CfgPredicate::Option(CfgOption {
ident: Ident::new("feature", Span::call_site()),
inner: Some(CfgOptionInner {
eq: Token!(=)(Span::call_site()),
val: LitStr::new("f1", Span::call_site()),
}),
}));
p.push(CfgPredicate::Option(CfgOption {
ident: Ident::new("feature", Span::call_site()),
inner: Some(CfgOptionInner {
eq: Token!(=)(Span::call_site()),
val: LitStr::new("f2", Span::call_site()),
}),
}));
p
}
}
);
}
}