use proc_macro2::{Spacing, TokenStream, TokenTree};
use syn::{Expr, ExprLit, Lit};
pub(crate) fn parse_attributes(
attrs: TokenStream,
) -> (Vec<Expr>, Vec<TokenStream>, Option<String>) {
let segments = segment_input(attrs);
let mut segments_stream: Vec<TokenStream> = segments
.iter()
.map(|x| x.iter().cloned().collect::<TokenStream>())
.collect();
let rewritten_segs: Vec<_> = segments.into_iter().map(rewrite).collect();
let mut conds: Vec<Expr> = vec![];
for seg in rewritten_segs {
let expr = match syn::parse2::<Expr>(seg) {
Ok(val) => val,
Err(err) => Expr::Verbatim(err.to_compile_error()),
};
conds.push(expr);
}
let desc = conds
.last()
.map(|expr| match expr {
Expr::Lit(ExprLit {
lit: Lit::Str(str), ..
}) => Some(str.value()),
_ => None,
})
.unwrap_or(None);
if desc.is_some() {
conds.pop();
segments_stream.pop();
}
(conds, segments_stream, desc)
}
fn rewrite(segments: Vec<TokenTree>) -> proc_macro2::TokenStream {
let mut lhs = vec![];
let mut rhs: Option<_> = None;
let mut span: Option<_> = None;
let mut idx = 0;
'segment: while let Some(tt) = segments.get(idx) {
match tt {
TokenTree::Group(group) => {
let stream: Vec<_> = group.stream().into_iter().collect();
let new_stream: TokenStream = rewrite(stream).into_iter().collect();
let mut new_group = proc_macro2::Group::new(group.delimiter(), new_stream);
new_group.set_span(group.span());
lhs.push(TokenTree::Group(new_group));
idx += 1;
}
TokenTree::Ident(_) => {
lhs.push(tt.clone());
idx += 1;
}
TokenTree::Literal(_) => {
lhs.push(tt.clone());
idx += 1;
}
TokenTree::Punct(_) => {
let punct = |idx: usize, c: char, s: Spacing| -> bool {
let tt = if let Some(val) = segments.get(idx) {
val
} else {
return false;
};
if let TokenTree::Punct(p) = tt {
p.as_char() == c && p.spacing() == s
} else {
false
}
};
if punct(idx, '-', Spacing::Joint) && punct(idx + 1, '>', Spacing::Alone) {
let rest = Vec::from(&segments[idx + 2..]);
let rhs_stream = rewrite(rest);
rhs = Some(rhs_stream);
span = Some(segments[idx + 1].span());
break 'segment;
} else {
'op: while let Some(tt) = segments.get(idx) {
match tt {
TokenTree::Punct(p) => {
if p.spacing() == Spacing::Alone {
lhs.push(tt.clone());
idx += 1;
break 'op;
} else {
lhs.push(tt.clone());
idx += 1;
}
}
_ => {
break 'op;
}
}
}
}
}
}
}
match (rhs, span) {
(None, None) => lhs.into_iter().collect(),
(None, Some(_)) => {
unreachable!("If there's a span there should be an implication")
}
(Some(_), None) => unreachable!("Invalid spans"),
(Some(rhs), Some(span)) => {
let lhs: TokenStream = lhs.into_iter().collect();
quote::quote_spanned! {
span =>
(!(#lhs) || #rhs)
}
}
}
}
fn segment_input(tts: TokenStream) -> Vec<Vec<TokenTree>> {
let mut groups = vec![];
let mut group = vec![];
for tt in tts {
match tt {
TokenTree::Punct(p) if p.as_char() == ',' && p.spacing() == Spacing::Alone => {
groups.push(group);
group = vec![];
}
t => group.push(t),
}
}
if !group.is_empty() {
groups.push(group);
}
groups
}