#![doc = include_str!("../README.md")]
mod cfg;
use cfg::*;
mod find;
use find::*;
use std::{collections::HashSet, iter};
use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
use quote::ToTokens;
use syn::{
Item, Stmt,
parse::{Parse, ParseStream},
};
struct Many<T>(Vec<T>);
impl<T: Parse> Parse for Many<T> {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut items = Vec::new();
while !input.is_empty() {
items.push(input.parse::<T>()?);
}
Ok(Many(items))
}
}
fn expand_for_cfg(ts: TokenStream, active_cfg: &Cfg) -> TokenStream {
let mut it = ts.into_iter().peekable();
let mut out = TokenStream::new();
while let Some(tt) = it.next() {
match &tt {
TokenTree::Group(g) => {
let expanded = expand_for_cfg(g.stream(), active_cfg);
let expanded = TokenTree::Group(Group::new(g.delimiter(), expanded));
out.extend([expanded]);
}
TokenTree::Punct(p) if p.as_char() == '#' => {
let Some(TokenTree::Group(g)) = it.peek() else {
continue;
};
if g.delimiter() != Delimiter::Bracket {
continue;
}
let mut attr_ts = TokenStream::new();
attr_ts.extend(iter::once(tt.clone()));
attr_ts.extend(iter::once(TokenTree::Group(g.clone())));
let Ok(attr) = parse_any_attr(attr_ts) else {
continue;
};
let Some(cfg) = Cfg::from_attr(&attr) else {
continue;
};
if !attr.path().is_ident("cfg") {
continue;
}
let _ = it.next();
let Some(target) = it.next() else { continue };
if active_cfg.is_active_subset(&cfg) {
let target = if let TokenTree::Group(g) = target {
g.stream()
} else {
target.into_token_stream()
};
let expanded = expand_for_cfg(target, active_cfg);
out.extend([expanded]);
} else {
}
}
_ => {
out.extend([tt]);
}
}
}
out
}
fn generate_all_combinations(cfgs: Vec<Cfg>) -> Vec<Cfg> {
fn core<T: Clone>(
items: &[T],
i: usize,
acc: &mut Vec<(T, bool)>,
out: &mut impl FnMut(&Vec<(T, bool)>),
) {
if i == items.len() {
out(acc);
return;
}
acc.push((items[i].clone(), false));
core(items, i + 1, acc, out);
acc.pop();
acc.push((items[i].clone(), true));
core(items, i + 1, acc, out);
acc.pop();
}
let mut acc = Vec::with_capacity(cfgs.len());
let mut out = Vec::with_capacity(cfgs.len() * cfgs.len());
core(&cfgs, 0, &mut acc, &mut |cfgs| {
let list = cfgs
.iter()
.cloned()
.map(
|(cfg, active)| {
if active { cfg } else { Cfg::Not(Box::new(cfg)) }
},
)
.collect();
out.push(Cfg::All(list));
});
out
}
fn find_base_cfgs(input: impl IntoIterator<Item = Cfg>) -> Vec<Cfg> {
let mut cfgs = HashSet::new();
for cfg in input.into_iter() {
match cfg {
Cfg::Not(inner) => cfgs.insert(*inner),
_ => cfgs.insert(cfg),
};
}
let cfgs: Vec<Cfg> = cfgs
.iter()
.filter(|cfg| match cfg {
Cfg::All(xs) => !xs.iter().all(|child| match child {
Cfg::Not(inner) => cfgs.contains(inner),
_ => cfgs.contains(child),
}),
_ => true,
})
.cloned()
.collect();
cfgs
}
#[proc_macro]
pub fn cfg_tt(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let content: TokenStream = input.into();
let cfgs = find_cfg_attrs(content.clone());
if cfgs.is_empty() {
return content.into();
}
let cfgs = find_base_cfgs(cfgs);
let configurations = generate_all_combinations(cfgs);
let mut out = TokenStream::new();
for cfg in &configurations {
let expanded = expand_for_cfg(content.clone(), cfg);
let items = match syn::parse2::<Many<Item>>(expanded.clone()) {
Ok(items) => items.0.iter().map(|item| item.to_token_stream()).collect(),
Err(_) => match syn::parse2::<Many<Stmt>>(expanded.clone()) {
Ok(stmts) => stmts.0.iter().map(|item| item.to_token_stream()).collect(),
Err(_) => vec![expanded],
},
};
for item in items {
out.extend([cfg.to_token_stream(), item]);
}
}
out.into()
}