use proc_macro2::{Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
use crate::codegen::VarSeg;
use crate::util::{MAX_NEST_DEPTH, compile_error_str, depth_err, is_punct_at};
pub(crate) fn expand_repeat_blocks(
tokens: TokenStream, segs: &[VarSeg],
) -> Result<TokenStream, TokenStream> {
let v = fix_literal_at(tokens.into_iter().collect::<Vec<_>>());
expand_stream(&v, segs, 0).map(|out| out.into_iter().collect())
}
fn fix_literal_at(tokens: Vec<TokenTree>) -> Vec<TokenTree> {
let mut out = vec![];
let mut i = 0;
while i < tokens.len() {
if let TokenTree::Literal(lit) = &tokens[i] {
let s = lit.to_string();
if s.ends_with('.')
&& is_punct_at(&tokens, i + 1, '@')
&& let Ok(n) = s[..s.len() - 1].parse::<u64>()
{
out.push(TokenTree::Literal(Literal::u64_unsuffixed(n)));
out.push(TokenTree::Punct(Punct::new('.', Spacing::Alone)));
i += 1;
continue;
}
}
if let TokenTree::Group(g) = &tokens[i] {
let inner = fix_literal_at(g.stream().into_iter().collect::<Vec<_>>());
let mut ng = Group::new(g.delimiter(), inner.into_iter().collect());
ng.set_span(g.span());
out.push(TokenTree::Group(ng));
i += 1;
continue;
}
out.push(tokens[i].clone());
i += 1;
}
out
}
fn expand_stream(
tokens: &[TokenTree], segs: &[VarSeg], depth: usize,
) -> Result<Vec<TokenTree>, TokenStream> {
if depth > MAX_NEST_DEPTH {
return Err(depth_err(tokens, ""));
}
let mut out = vec![];
let mut i = 0;
while i < tokens.len() {
if is_punct_at(tokens, i, '@') {
if let Some(TokenTree::Ident(id)) = tokens.get(i + 1)
&& let Some(TokenTree::Group(g)) = tokens.get(i + 2)
&& g.delimiter() == delimiter![()]
&& is_punct_at(tokens, i + 3, '.')
&& is_punct_at(tokens, i + 4, '.')
{
let body = g.stream().into_iter().collect::<Vec<_>>();
out.extend(expand_block(&body, segs, depth + 1, Some(id.clone()))?);
i += 5;
continue;
}
if let Some(TokenTree::Group(g)) = tokens.get(i + 1)
&& g.delimiter() == delimiter![()]
&& is_punct_at(tokens, i + 2, '.')
&& is_punct_at(tokens, i + 3, '.')
{
let body = g.stream().into_iter().collect::<Vec<_>>();
out.extend(expand_block(&body, segs, depth + 1, None)?);
i += 4;
continue;
}
return Err(compile_error_str(
"batch-impl: `@` inside an impl body must start a repeat block \
`@(...)..` (or `@ident(...)..` with the driving segment declared)",
tokens[i].span(),
));
}
if let TokenTree::Group(g) = &tokens[i] {
if depth + 1 > MAX_NEST_DEPTH {
return Err(depth_err(&tokens[i..i + 1], ""));
}
let inner = g.stream().into_iter().collect::<Vec<_>>();
let expanded = expand_stream(&inner, segs, depth + 1)?;
let mut ng = Group::new(g.delimiter(), expanded.into_iter().collect());
ng.set_span(g.span());
out.push(TokenTree::Group(ng));
i += 1;
continue;
}
out.push(tokens[i].clone());
i += 1;
}
Ok(out)
}
fn expand_block(
body: &[TokenTree], segs: &[VarSeg], depth: usize, driver: Option<Ident>,
) -> Result<Vec<TokenTree>, TokenStream> {
if depth > MAX_NEST_DEPTH {
return Err(depth_err(body, " in a repeat block"));
}
let body = expand_nested(body, segs, depth)?;
let (inner_prefixes, inner_len) = super::repeat_drivers::collect_drivers(&body, segs)?;
let len = match driver {
Some(id) => {
let prefix = id.to_string();
let Some(seg) = segs.iter().find(|s| s.prefix == prefix) else {
return Err(compile_error_str(
&format!(
"batch-impl: repeat block driver `@{}` is not a variadic \
segment (the `impl{{...}}` template declares no `{}@..`)",
prefix, prefix,
),
id.span(),
));
};
for p in &inner_prefixes {
if *p != prefix {
return Err(compile_error_str(
&format!(
"batch-impl: repeat block driver `@{}` conflicts with the \
inner segment reference `@{}` (they must be the same)",
prefix, p,
),
id.span(),
));
}
}
seg.len
}
None => match inner_len {
Some(l) => l,
None if segs.len() == 1 => segs[0].len,
None => {
return Err(compile_error_str(
"batch-impl: a repeat block needs a driving segment to determine \
its length — write `@ident(...)..` with the segment declared, or \
reference a segment inside",
body.first().map_or_else(Span::call_site, |t| t.span()),
));
}
},
};
let mut out = vec![];
for round in 0..len {
out.extend(super::repeat_drivers::substitute(&body, segs, round, depth + 1)?);
}
Ok(out)
}
fn expand_nested(
tokens: &[TokenTree], segs: &[VarSeg], depth: usize,
) -> Result<Vec<TokenTree>, TokenStream> {
if depth > MAX_NEST_DEPTH {
return Err(depth_err(tokens, " in a repeat block"));
}
let mut out = vec![];
let mut i = 0;
while i < tokens.len() {
if is_punct_at(tokens, i, '@')
&& let Some(TokenTree::Ident(id)) = tokens.get(i + 1)
&& let Some(TokenTree::Group(g)) = tokens.get(i + 2)
&& g.delimiter() == delimiter![()]
&& is_punct_at(tokens, i + 3, '.')
&& is_punct_at(tokens, i + 4, '.')
{
let body = g.stream().into_iter().collect::<Vec<_>>();
out.extend(expand_block(&body, segs, depth + 1, Some(id.clone()))?);
i += 5;
continue;
}
if is_punct_at(tokens, i, '@')
&& let Some(TokenTree::Group(g)) = tokens.get(i + 1)
&& g.delimiter() == delimiter![()]
&& is_punct_at(tokens, i + 2, '.')
&& is_punct_at(tokens, i + 3, '.')
{
let body = g.stream().into_iter().collect::<Vec<_>>();
out.extend(expand_block(&body, segs, depth + 1, None)?);
i += 4;
continue;
}
if let TokenTree::Group(g) = &tokens[i] {
if depth + 1 > MAX_NEST_DEPTH {
return Err(depth_err(&tokens[i..i + 1], ""));
}
let inner = g.stream().into_iter().collect::<Vec<_>>();
let expanded = expand_nested(&inner, segs, depth + 1)?;
let mut ng = Group::new(g.delimiter(), expanded.into_iter().collect());
ng.set_span(g.span());
out.push(TokenTree::Group(ng));
i += 1;
continue;
}
out.push(tokens[i].clone());
i += 1;
}
Ok(out)
}