#![doc = include_str!("../README.md")]
use std::{collections::HashSet, iter};
use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
use quote::ToTokens;
use syn::parse::Parse;
use syn::parse::ParseStream;
use syn::{Attribute, Item};
struct AnyAttribute(Attribute);
impl Parse for AnyAttribute {
fn parse(input: ParseStream) -> syn::Result<Self> {
let content;
let attr = Attribute {
pound_token: input.parse()?,
style: input
.parse::<syn::token::Not>()
.ok()
.map_or_else(|| syn::AttrStyle::Outer, syn::AttrStyle::Inner),
bracket_token: syn::bracketed!(content in input),
meta: content.parse()?,
};
Ok(Self(attr))
}
}
struct Items(Vec<Item>);
impl Parse for Items {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut items = Vec::new();
while !input.is_empty() {
items.push(input.parse::<Item>()?);
}
Ok(Items(items))
}
}
fn parse_any_attr(ts: TokenStream) -> syn::Result<Attribute> {
syn::parse2::<AnyAttribute>(ts).map(|a| a.0)
}
fn find_cfg_attrs(ts: TokenStream) -> HashSet<Attribute> {
fn core(ts: TokenStream, out: &mut HashSet<Attribute>) {
let mut it = ts.into_iter().peekable();
while let Some(tt) = it.next() {
match &tt {
TokenTree::Group(g) => {
core(g.stream(), out);
}
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;
};
if !attr.path().is_ident("cfg") {
continue;
}
out.insert(attr);
let _ = it.next();
}
_ => {}
}
}
}
let mut out = HashSet::new();
core(ts, &mut out);
out
}
fn expand_for_cfg(ts: TokenStream, cfg: &Attribute) -> 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(), 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;
};
if !attr.path().is_ident("cfg") {
continue;
}
let _ = it.next();
let Some(target) = it.next() else { continue };
if cfg == &attr {
let target = if let TokenTree::Group(g) = target {
g.stream()
} else {
target.into_token_stream()
};
let expanded = expand_for_cfg(target, cfg);
out.extend([expanded]);
} else {
}
}
_ => {
out.extend([tt]);
}
}
}
out
}
#[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());
let mut out = TokenStream::new();
for cfg in &cfgs {
let expanded = expand_for_cfg(content.clone(), cfg);
let items = match syn::parse2::<Items>(expanded.clone()) {
Ok(items) => items.0.iter().map(|item| item.to_token_stream()).collect(),
Err(_) => vec![expanded],
};
for item in items {
out.extend([cfg.into_token_stream(), item]);
}
}
out.into()
}