use proc_macro2::{TokenStream, TokenTree};
use quote::{ToTokens, TokenStreamExt};
use syn::{
braced, bracketed,
parse::{Parse, ParseStream},
parse_macro_input,
punctuated::Punctuated,
token, Expr, Ident, Token,
};
struct SyntacticFor {
ident: Ident,
_in_token: Token![in],
_bracket_token: token::Bracket,
exprs: Punctuated<Expr, Token![,]>,
_brace_token: token::Brace,
body: TokenStream,
}
impl Parse for SyntacticFor {
fn parse(input: ParseStream) -> syn::Result<Self> {
let brackets;
let braces;
Ok(SyntacticFor {
ident: input.parse()?,
_in_token: input.parse()?,
_bracket_token: bracketed!(brackets in input),
exprs: Punctuated::parse_terminated(&brackets)?,
_brace_token: braced!(braces in input),
body: braces.parse()?,
})
}
}
fn subs_group<'a, S, IntoIter>(
pattern: &Ident,
subs: &'a S,
tokens: TokenStream,
) -> syn::Result<TokenStream>
where
&'a S: IntoIterator<IntoIter = IntoIter> + Clone + 'a,
IntoIter: ExactSizeIterator,
<IntoIter as Iterator>::Item: ToTokens,
{
let mut output = TokenStream::new();
let mut tokens = tokens.into_iter().peekable();
while let Some(token) = tokens.next() {
match token {
TokenTree::Punct(punct) if punct.as_char() == '$' => match tokens.next() {
Some(TokenTree::Group(group)) => {
let separator = match tokens.peek() {
Some(TokenTree::Punct(punct)) if punct.as_char() == '*' => None,
Some(TokenTree::Punct(_)) => {
if let TokenTree::Punct(punct) = tokens.next().unwrap() {
Some(punct)
} else {
unreachable!()
}
}
Some(token) => {
return Err(syn::Error::new_spanned(
token,
format!("expected punctuation or `*`, found {}", token),
))
}
None => panic!("unexpected end of stream after group"),
};
match tokens.next() {
Some(TokenTree::Punct(punct)) if punct.as_char() == '*' => {}
Some(token) => {
return Err(syn::Error::new_spanned(
&token,
format!("expected `*`, found {}", token),
))
}
None => panic!("unexpected end of stream after group"),
}
let subs = subs.into_iter();
let len = subs.len();
for (i, sub) in subs.enumerate() {
output.extend(subs_ident(pattern, &sub, group.stream())?);
if i + 1 < len {
if let Some(separator) = &separator {
output.append(separator.clone());
}
}
}
}
Some(token) => {
return Err(syn::Error::new_spanned(
&token,
format!("expected group after `$`, found `{}`", token),
))
}
None => {
panic!("unexpected end of stream after `$`")
}
},
TokenTree::Group(group) => {
output.append(proc_macro2::Group::new(
group.delimiter(),
subs_group(pattern, subs, group.stream())?,
));
}
token => output.append(token),
}
}
Ok(output)
}
fn subs_ident<'a, I>(pattern: &Ident, sub: &'a I, tokens: TokenStream) -> syn::Result<TokenStream>
where
&'a I: ToTokens,
{
let mut output = TokenStream::new();
let mut tokens = tokens.into_iter();
while let Some(token) = tokens.next() {
match token {
TokenTree::Punct(punct) if punct.as_char() == '$' => match tokens.next() {
Some(TokenTree::Ident(ident)) if &ident == pattern => {
sub.to_tokens(&mut output);
}
Some(token) => {
return Err(syn::Error::new_spanned(
&token,
format!("expected `{}` after `$`, found `{}`", pattern, token),
))
}
None => {
panic!("unexpected end of stream after `$`")
}
},
TokenTree::Group(group) => {
output.append(proc_macro2::Group::new(
group.delimiter(),
subs_ident(pattern, sub, group.stream())?,
));
}
token => output.append(token),
}
}
Ok(output)
}
#[proc_macro]
pub fn syntactic_for(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let SyntacticFor {
ident, exprs, body, ..
} = parse_macro_input!(input as SyntacticFor);
subs_group(&ident, &exprs, body)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}