use proc_macro2::Delimiter;
use proc_macro2::Group;
use proc_macro2::TokenStream;
use proc_macro2::TokenTree;
use syn::Result;
use crate::parse::Binding;
use crate::parse::Template;
pub fn expand(input: TokenStream) -> Result<TokenStream> {
let Template { rows, template } = syn::parse2::<Template>(input)?;
let bindings_by_row = rows
.iter()
.map(|row| row.bindings.as_slice())
.collect::<Vec<_>>();
let mut found_splice = false;
let expanded = expand_splice_blocks(&bindings_by_row, template.clone(), &mut found_splice);
if found_splice {
return Ok(expanded);
}
let mut output = TokenStream::new();
for bindings in bindings_by_row {
output.extend(substitute_tokens(bindings, template.clone()));
}
Ok(output)
}
fn substitute_tokens(bindings: &[Binding], tokens: TokenStream) -> TokenStream {
let mut new_tokens = TokenStream::new();
for token in tokens {
match token {
TokenTree::Group(group) => {
let content = substitute_tokens(bindings, group.stream());
let mut new_group = Group::new(group.delimiter(), content);
new_group.set_span(group.span());
new_tokens.extend([TokenTree::Group(new_group)]);
}
TokenTree::Ident(ident) => {
debug_assert!(bindings.is_sorted_by_key(|b| &b.var));
if let Ok(index) = bindings.binary_search_by(|b| b.var.cmp(&ident)) {
new_tokens.extend(bindings[index].tokens.clone());
} else {
new_tokens.extend([TokenTree::Ident(ident)]);
}
}
other => new_tokens.extend([other]),
}
}
new_tokens
}
fn expand_splice_blocks(
bindings_by_row: &[&[Binding]],
tokens: TokenStream,
found_splice: &mut bool,
) -> TokenStream {
let mut tokens = tokens.into_iter().collect::<Vec<_>>();
let mut i = 0;
while i < tokens.len() {
if let TokenTree::Group(group) = &mut tokens[i] {
let content = expand_splice_blocks(bindings_by_row, group.stream(), found_splice);
let mut new_group = Group::new(group.delimiter(), content);
new_group.set_span(group.span());
*group = new_group;
i += 1;
continue;
}
let Some(template) = enter_splice_block(&tokens[i..]) else {
i += 1;
continue;
};
*found_splice = true;
let mut repeated = vec![];
for row_bindings in bindings_by_row {
repeated.extend(substitute_tokens(row_bindings, template.clone()));
}
let repeated_len = repeated.len();
tokens.splice(i..i + 3, repeated);
i += repeated_len;
}
tokens.into_iter().collect()
}
fn enter_splice_block(tokens: &[TokenTree]) -> Option<TokenStream> {
let [
TokenTree::Punct(at),
TokenTree::Ident(ident),
TokenTree::Group(group),
..,
] = tokens
else {
return None;
};
if at.as_char() != '@' || ident != "splice" || group.delimiter() != Delimiter::Brace {
return None;
}
Some(group.stream())
}