1#![doc = include_str!("../README.md")]
2
3mod cfg;
4use cfg::*;
5mod find;
6use find::*;
7
8use std::{collections::HashSet, iter};
9
10use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
11use quote::ToTokens;
12use syn::{
13 Item, Stmt,
14 parse::{Parse, ParseStream},
15};
16
17struct Many<T>(Vec<T>);
18
19impl<T: Parse> Parse for Many<T> {
20 fn parse(input: ParseStream) -> syn::Result<Self> {
21 let mut items = Vec::new();
22 while !input.is_empty() {
23 items.push(input.parse::<T>()?);
24 }
25 Ok(Many(items))
26 }
27}
28
29fn expand_for_cfg(ts: TokenStream, active_cfg: &Cfg) -> TokenStream {
30 let mut it = ts.into_iter().peekable();
31 let mut out = TokenStream::new();
32 while let Some(tt) = it.next() {
33 match &tt {
34 TokenTree::Group(g) => {
35 let expanded = expand_for_cfg(g.stream(), active_cfg);
36 let expanded = TokenTree::Group(Group::new(g.delimiter(), expanded));
37 out.extend([expanded]);
38 }
39 TokenTree::Punct(p) if p.as_char() == '#' => {
40 let Some(TokenTree::Group(g)) = it.peek() else {
42 continue;
43 };
44 if g.delimiter() != Delimiter::Bracket {
45 continue;
46 }
47
48 let mut attr_ts = TokenStream::new();
50 attr_ts.extend(iter::once(tt.clone()));
51 attr_ts.extend(iter::once(TokenTree::Group(g.clone())));
52
53 let Ok(attr) = parse_any_attr(attr_ts) else {
54 continue;
55 };
56 let Some(cfg) = Cfg::from_attr(&attr) else {
57 continue;
58 };
59 if !attr.path().is_ident("cfg") {
60 continue;
61 }
62
63 let _ = it.next();
65
66 let Some(target) = it.next() else { continue };
68 if active_cfg.is_active_subset(&cfg) {
69 let target = if let TokenTree::Group(g) = target {
71 g.stream()
72 } else {
73 target.into_token_stream()
74 };
75 let expanded = expand_for_cfg(target, active_cfg);
76 out.extend([expanded]);
77 } else {
78 }
80 }
81 _ => {
82 out.extend([tt]);
83 }
84 }
85 }
86
87 out
88}
89
90fn generate_all_combinations(cfgs: Vec<Cfg>) -> Vec<Cfg> {
91 fn core<T: Clone>(
92 items: &[T],
93 i: usize,
94 acc: &mut Vec<(T, bool)>,
95 out: &mut impl FnMut(&Vec<(T, bool)>),
96 ) {
97 if i == items.len() {
98 out(acc);
99 return;
100 }
101
102 acc.push((items[i].clone(), false));
104 core(items, i + 1, acc, out);
105 acc.pop();
106
107 acc.push((items[i].clone(), true));
109 core(items, i + 1, acc, out);
110 acc.pop();
111 }
112
113 let mut acc = Vec::with_capacity(cfgs.len());
114 let mut out = Vec::with_capacity(cfgs.len() * cfgs.len());
115 core(&cfgs, 0, &mut acc, &mut |cfgs| {
116 let list = cfgs
117 .iter()
118 .cloned()
119 .map(
120 |(cfg, active)| {
121 if active { cfg } else { Cfg::Not(Box::new(cfg)) }
122 },
123 )
124 .collect();
125 out.push(Cfg::All(list));
126 });
127 out
128}
129
130fn find_base_cfgs(input: impl IntoIterator<Item = Cfg>) -> Vec<Cfg> {
131 let mut cfgs = HashSet::new();
132
133 for cfg in input.into_iter() {
135 match cfg {
136 Cfg::Not(inner) => cfgs.insert(*inner),
137 _ => cfgs.insert(cfg),
138 };
139 }
140
141 let cfgs: Vec<Cfg> = cfgs
143 .iter()
144 .filter(|cfg| match cfg {
145 Cfg::All(xs) => !xs.iter().all(|child| match child {
146 Cfg::Not(inner) => cfgs.contains(inner),
147 _ => cfgs.contains(child),
148 }),
149 _ => true,
150 })
151 .cloned()
152 .collect();
153
154 cfgs
155}
156
157#[proc_macro]
173pub fn cfg_tt(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
174 let content: TokenStream = input.into();
175
176 let cfgs = find_cfg_attrs(content.clone());
178 if cfgs.is_empty() {
179 return content.into();
181 }
182
183 let cfgs = find_base_cfgs(cfgs);
184
185 let configurations = generate_all_combinations(cfgs);
187
188 let mut out = TokenStream::new();
189 for cfg in &configurations {
190 let expanded = expand_for_cfg(content.clone(), cfg);
191 let items = match syn::parse2::<Many<Item>>(expanded.clone()) {
192 Ok(items) => items.0.iter().map(|item| item.to_token_stream()).collect(),
193 Err(_) => match syn::parse2::<Many<Stmt>>(expanded.clone()) {
194 Ok(stmts) => stmts.0.iter().map(|item| item.to_token_stream()).collect(),
195 Err(_) => vec![expanded],
196 },
197 };
198
199 for item in items {
200 out.extend([cfg.to_token_stream(), item]);
201 }
202 }
203
204 out.into()
207}