use crate::{codegen::CfgExprRules, SourceLocation};
#[derive(Debug, Clone, PartialEq)]
pub enum CfgExpr {
Feature(String),
TargetArch(String),
TargetVendor(String),
TargetOs(String),
TargetEnv(String),
Not(Box<CfgExpr>),
All(Vec<CfgExpr>),
Any(Vec<CfgExpr>),
Other(String),
False,
}
impl CfgExpr {
pub fn parse_from_tokens(tokens: &proc_macro2::TokenStream) -> Result<Self, String> {
let token_string = tokens.to_string();
Self::parse_from_string(&token_string)
}
pub fn parse_from_string(input: &str) -> Result<Self, String> {
let input = input.trim();
if let Some(inner) = strip_function_call(input, "not") {
let inner_expr = Self::parse_from_string(&inner)?;
return Ok(CfgExpr::Not(Box::new(inner_expr)));
}
if let Some(inner) = strip_function_call(input, "all") {
let exprs = parse_comma_separated(&inner)?;
return Ok(CfgExpr::All(exprs));
}
if let Some(inner) = strip_function_call(input, "any") {
let exprs = parse_comma_separated(&inner)?;
return Ok(CfgExpr::Any(exprs));
}
if let Some(feature_name) = extract_simple_feature(input) {
return Ok(CfgExpr::Feature(feature_name));
}
if let Some(arch) = extract_target_arch(input) {
return Ok(CfgExpr::TargetArch(arch));
}
if let Some(vendor) = extract_target_vendor(input) {
return Ok(CfgExpr::TargetVendor(vendor));
}
if let Some(os) = extract_target_os(input) {
return Ok(CfgExpr::TargetOs(os));
}
if let Some(env) = extract_target_env(input) {
return Ok(CfgExpr::TargetEnv(env));
}
Ok(CfgExpr::Other(input.to_string()))
}
pub fn apply_rules(
&self,
rules: &CfgExprRules,
source_location: &SourceLocation,
) -> Option<Self> {
match self {
CfgExpr::Feature(name) => {
if rules.enabled_features.contains(name) {
None } else if rules.disabled_features.contains(name) {
Some(CfgExpr::False) } else if let Some(new_name) = rules.feature_mappings.get(name) {
Some(CfgExpr::Feature(new_name.clone()))
} else if rules.disable_unknown_features {
Some(CfgExpr::False)
} else {
panic!("unmapped feature: {name} (at {source_location})");
}
}
CfgExpr::TargetVendor(val) => {
if let Some(sel) = rules.enabled_target_vendor.as_ref() {
if val == sel {
None
} else {
Some(CfgExpr::False)
}
} else {
Some(self.clone())
}
}
CfgExpr::TargetArch(val) => {
if let Some(sel) = rules.enabled_target_arch.as_ref() {
if val == sel {
None
} else {
Some(CfgExpr::False)
}
} else {
Some(self.clone())
}
}
CfgExpr::All(exprs) => {
let mut processed_exprs = Vec::new();
for expr in exprs {
match expr.apply_rules(rules, source_location) {
Some(CfgExpr::False) => {
return Some(CfgExpr::False);
}
Some(processed) => processed_exprs.push(processed),
None => {
}
}
}
match processed_exprs.len() {
0 => None, 1 => Some(processed_exprs.into_iter().next().unwrap()), _ => Some(CfgExpr::All(processed_exprs)),
}
}
CfgExpr::Any(exprs) => {
let mut processed_exprs = Vec::new();
let mut has_true = false;
for expr in exprs {
match expr.apply_rules(rules, source_location) {
Some(CfgExpr::False) => {
}
Some(processed) => processed_exprs.push(processed),
None => {
has_true = true;
}
}
}
if has_true {
None } else {
match processed_exprs.len() {
0 => Some(CfgExpr::False), 1 => Some(processed_exprs.into_iter().next().unwrap()), _ => Some(CfgExpr::Any(processed_exprs)),
}
}
}
CfgExpr::Not(expr) => {
match expr.apply_rules(rules, source_location) {
Some(CfgExpr::False) => None, Some(processed) => Some(CfgExpr::Not(Box::new(processed))),
None => Some(CfgExpr::False), }
}
CfgExpr::TargetOs(val) => {
if let Some(sel) = rules.enabled_target_os.as_ref() {
if val == sel {
None
} else {
Some(CfgExpr::False)
}
} else {
Some(self.clone())
}
}
CfgExpr::TargetEnv(val) => {
if let Some(sel) = rules.enabled_target_env.as_ref() {
if val == sel {
None
} else {
Some(CfgExpr::False)
}
} else {
Some(self.clone())
}
}
CfgExpr::Other(_) => Some(self.clone()),
CfgExpr::False => Some(CfgExpr::False),
}
}
pub fn to_tokens(&self) -> proc_macro2::TokenStream {
match self {
CfgExpr::Feature(name) => {
quote::quote! { feature = #name }
}
CfgExpr::TargetArch(arch) => {
quote::quote! { target_arch = #arch }
}
CfgExpr::TargetVendor(vendor) => {
quote::quote! { target_vendor = #vendor }
}
CfgExpr::TargetOs(os) => {
quote::quote! { target_os = #os }
}
CfgExpr::TargetEnv(env) => {
quote::quote! { target_env = #env }
}
CfgExpr::All(exprs) => {
let tokens: Vec<_> = exprs.iter().map(|e| e.to_tokens()).collect();
quote::quote! { all(#(#tokens),*) }
}
CfgExpr::Any(exprs) => {
let tokens: Vec<_> = exprs.iter().map(|e| e.to_tokens()).collect();
quote::quote! { any(#(#tokens),*) }
}
CfgExpr::Not(expr) => {
let inner = expr.to_tokens();
quote::quote! { not(#inner) }
}
CfgExpr::Other(content) => {
if let Ok(tokens) = content.parse::<proc_macro2::TokenStream>() {
tokens
} else {
let ident = proc_macro2::Ident::new(content, proc_macro2::Span::call_site());
quote::quote! { #ident }
}
}
CfgExpr::False => {
quote::quote! { any() } }
}
}
}
fn extract_simple_feature(input: &str) -> Option<String> {
use regex::Regex;
let feature_regex = Regex::new(r#"feature\s*=\s*"([^"]+)""#).unwrap();
feature_regex
.captures(input)
.map(|captures| captures[1].to_string())
}
fn extract_target_arch(input: &str) -> Option<String> {
use regex::Regex;
let arch_regex = Regex::new(r#"target_arch\s*=\s*"([^"]+)""#).unwrap();
arch_regex
.captures(input)
.map(|captures| captures[1].to_string())
}
fn extract_target_vendor(input: &str) -> Option<String> {
use regex::Regex;
let re = Regex::new(r#"target_vendor\s*=\s*\"([^\"]+)\""#).unwrap();
re.captures(input).map(|c| c[1].to_string())
}
fn extract_target_os(input: &str) -> Option<String> {
use regex::Regex;
let re = Regex::new(r#"target_os\s*=\s*\"([^\"]+)\""#).unwrap();
re.captures(input).map(|c| c[1].to_string())
}
fn extract_target_env(input: &str) -> Option<String> {
use regex::Regex;
let re = Regex::new(r#"target_env\s*=\s*\"([^\"]+)\""#).unwrap();
re.captures(input).map(|c| c[1].to_string())
}
fn strip_function_call(input: &str, function_name: &str) -> Option<String> {
let input = input.trim();
let pattern1 = format!("{function_name}(");
let pattern2 = format!("{function_name} (");
if (input.starts_with(&pattern1) || input.starts_with(&pattern2)) && input.ends_with(')') {
let start = if input.starts_with(&pattern1) {
pattern1.len()
} else {
pattern2.len()
};
let end = input.len() - 1;
if start < end {
Some(input[start..end].to_string())
} else {
Some(String::new())
}
} else {
None
}
}
fn parse_comma_separated(input: &str) -> Result<Vec<CfgExpr>, String> {
let mut exprs = Vec::new();
let mut current = String::new();
let mut paren_depth = 0;
let mut in_quotes = false;
let mut escape_next = false;
for ch in input.chars() {
if escape_next {
current.push(ch);
escape_next = false;
continue;
}
match ch {
'\\' => {
escape_next = true;
current.push(ch);
}
'"' => {
in_quotes = !in_quotes;
current.push(ch);
}
'(' if !in_quotes => {
paren_depth += 1;
current.push(ch);
}
')' if !in_quotes => {
paren_depth -= 1;
current.push(ch);
}
',' if !in_quotes && paren_depth == 0 => {
if !current.trim().is_empty() {
exprs.push(CfgExpr::parse_from_string(current.trim())?);
}
current.clear();
}
_ => {
current.push(ch);
}
}
}
if !current.trim().is_empty() {
exprs.push(CfgExpr::parse_from_string(current.trim())?);
}
Ok(exprs)
}