use std::collections::HashSet;
use std::fmt::{Display, Formatter};
use quote::ToTokens;
use syn::{Attribute, AttrStyle, Item, Lit, Meta, MetaList, parse_quote, Expr, ExprLit, MacroDelimiter, Type};
use syn::parse::Parser;
use syn::punctuated::Punctuated;
use crate::ast::{CommaPunctuated, Depunctuated};
use crate::composer::MaybeMacroLabeled;
use crate::ext::MaybeAttrs;
#[derive(Debug)]
pub enum MacroKind {
Export,
Opaque,
Register(Type)
}
impl TryFrom<&Item> for MacroKind {
type Error = ();
fn try_from(value: &Item) -> Result<Self, Self::Error> {
match value.maybe_attrs()
.and_then(|attrs| attrs.iter().find_map(MaybeMacroLabeled::maybe_macro_labeled)) {
Some(macro_type) => Ok(macro_type),
None => Err(())
}
}
}
#[derive(Clone, Hash, PartialEq, Eq, Debug)]
pub enum CfgMacroType {
Feature(String),
Test,
Not(Box<CfgMacroType>),
Any(Vec<CfgMacroType>),
All(Vec<CfgMacroType>),
}
impl Display for CfgMacroType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
CfgMacroType::Feature(feat) =>
f.write_fmt(format_args!("feature = \"{}\"", feat)),
CfgMacroType::Test =>
f.write_str("test"),
CfgMacroType::Not(cond) =>
f.write_fmt(format_args!("not({}))", cond)),
CfgMacroType::Any(conditions) =>
f.write_fmt(format_args!("any({}))", Vec::from_iter(conditions.iter().map(CfgMacroType::to_string)).join(", "))),
CfgMacroType::All(conditions) =>
f.write_fmt(format_args!("all({}))", Vec::from_iter(conditions.iter().map(CfgMacroType::to_string)).join(", "))),
}
}
}
impl CfgMacroType {
fn to_meta(&self) -> Meta {
match self {
CfgMacroType::Feature(feat) => {
Meta::NameValue(syn::MetaNameValue {
path: syn::Path::from(syn::Ident::new("feature", proc_macro2::Span::call_site())),
eq_token: Default::default(),
value: Expr::Lit(ExprLit { attrs: vec![], lit: Lit::Str(syn::LitStr::new(feat, proc_macro2::Span::call_site())) }),
})
}
CfgMacroType::Test => {
Meta::Path(syn::Path::from(syn::Ident::new("test", proc_macro2::Span::call_site())))
}
CfgMacroType::Not(cond) => {
Meta::List(MetaList {
path: syn::Path::from(syn::Ident::new("not", proc_macro2::Span::call_site())),
delimiter: MacroDelimiter::Paren(Default::default()),
tokens: cond.to_meta().to_token_stream(),
})
}
CfgMacroType::Any(conds) => {
Meta::List(MetaList {
path: syn::Path::from(syn::Ident::new("any", proc_macro2::Span::call_site())),
delimiter: MacroDelimiter::Paren(Default::default()),
tokens: Depunctuated::from_iter(conds.iter().map(|c| c.to_meta())).to_token_stream(),
})
}
CfgMacroType::All(conds) => {
Meta::List(MetaList {
path: syn::Path::from(syn::Ident::new("all", proc_macro2::Span::call_site())),
delimiter: MacroDelimiter::Paren(Default::default()),
tokens: Depunctuated::from_iter(conds.iter().map(|c| c.to_meta())).to_token_stream(),
})
}
}
}
fn from_meta(meta: &Meta) -> Vec<Self> {
if let Meta::List(MetaList { path, tokens, .. }) = meta {
let parsed = CommaPunctuated::<Meta>::parse_terminated.parse2(tokens.clone()).unwrap_or_default();
if path.is_ident("any") {
return vec![CfgMacroType::Any(
parsed.iter()
.flat_map(CfgMacroType::from_meta)
.collect()
)];
}
if path.is_ident("all") {
return vec![CfgMacroType::All(
parsed.iter()
.flat_map(CfgMacroType::from_meta)
.collect()
)];
}
if path.is_ident("not") {
return parsed.iter()
.flat_map(CfgMacroType::from_meta)
.map(|c| CfgMacroType::Not(Box::new(c)))
.collect();
}
}
if let Meta::NameValue(nv) = meta {
if nv.path.is_ident("feature") {
if let Expr::Lit(ExprLit { lit: Lit::Str(s), .. }) = &nv.value {
return vec![CfgMacroType::Feature(s.value())];
}
}
}
if let Meta::Path(path) = meta {
if path.is_ident("test") {
return vec![CfgMacroType::Test];
}
}
vec![]
}
}
fn merge_cfg_conditions(conditions: Vec<CfgMacroType>) -> Vec<CfgMacroType> {
let mut features = HashSet::new();
let mut tests = false;
let mut not_conditions = vec![];
let mut any_conditions = vec![];
let mut all_conditions = vec![];
for condition in conditions {
match condition {
CfgMacroType::Feature(feature) => {
features.insert(feature);
}
CfgMacroType::Test => {
tests = true;
}
CfgMacroType::Not(cond) => {
not_conditions.push(*cond);
}
CfgMacroType::Any(conds) => {
any_conditions.extend(conds);
}
CfgMacroType::All(conds) => {
all_conditions.extend(conds);
}
}
}
if !features.is_empty() {
any_conditions.push(CfgMacroType::Any(features.into_iter().map(CfgMacroType::Feature).collect()));
}
if tests {
any_conditions.push(CfgMacroType::Test);
}
if !not_conditions.is_empty() {
all_conditions.push(CfgMacroType::All(not_conditions));
}
if any_conditions.is_empty() {
all_conditions
} else {
vec![CfgMacroType::Any(any_conditions)]
}
}
pub fn expand_attributes(attrs: &HashSet<Option<Attribute>>) -> Vec<Attribute> {
let merged = merge_attributes(attrs);
if merged.is_empty() {
return vec![];
}
vec![Attribute {
pound_token: Default::default(),
style: AttrStyle::Outer,
bracket_token: Default::default(),
meta: Meta::List(MetaList {
path: parse_quote!(cfg),
delimiter: MacroDelimiter::Paren(Default::default()),
tokens: merged.to_token_stream(),
}),
}]
}
pub fn merge_attributes(attrs: &HashSet<Option<Attribute>>) -> CommaPunctuated<Meta> {
if attrs.contains(&None) {
Punctuated::new()
} else {
let mut all_conditions = vec![];
attrs.iter().for_each(|attr| match attr {
Some(Attribute { meta, .. }) if meta.path().is_ident("cfg") => match meta {
Meta::List(meta_list) => if let Ok(parsed) = CommaPunctuated::<Meta>::parse_terminated.parse2(meta_list.tokens.clone()) {
parsed.iter().for_each(|meta| all_conditions.extend(CfgMacroType::from_meta(meta)))
}
meta =>
all_conditions.extend(CfgMacroType::from_meta(meta))
}
_ => {}
});
merge_cfg_conditions(all_conditions)
.iter()
.map(CfgMacroType::to_meta)
.collect()
}
}