use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
use crate::diagnostic::compile_error_str;
use crate::scan::is_arrow;
pub(crate) fn angle_collect(
tokens: &[TokenTree],
) -> Result<Vec<TokenTree>, TokenStream> {
let mut out = vec![];
let mut i = 0;
while i < tokens.len() {
match &tokens[i] {
TokenTree::Group(g) if g.delimiter() == Delimiter::None => {
let inner: Vec<_> = g.stream().into_iter().collect();
out.extend(angle_collect(&inner)?);
i += 1;
}
TokenTree::Group(g) if g.delimiter() == Delimiter::Parenthesis => {
let inner: Vec<_> = g.stream().into_iter().collect();
out.push(
Group::new(
g.delimiter(),
angle_collect(&inner)?.into_iter().collect(),
)
.into(),
);
i += 1;
}
TokenTree::Group(g) if g.delimiter() == Delimiter::Bracket => {
if i > 0
&& matches!(&tokens[i - 1], TokenTree::Punct(p)
if p.as_char() == '!' || p.as_char() == '#')
{
out.push(tokens[i].clone());
} else {
let inner: Vec<_> = g.stream().into_iter().collect();
out.push(
Group::new(
g.delimiter(),
angle_collect(&inner)?.into_iter().collect(),
)
.into(),
);
}
i += 1;
}
TokenTree::Group(_) => {
out.push(tokens[i].clone());
i += 1;
}
TokenTree::Punct(p) if p.as_char() == '<' => {
let Some(close) = find_angle_close(tokens, i) else {
return Err(compile_error_str(
"batch-impl: 未闭合的 `<`(缺少匹配的 `>`)",
));
};
let inner: Vec<_> = tokens[i + 1..close].to_vec();
out.push(
Group::new(
Delimiter::None,
angle_collect(&inner)?.into_iter().collect(),
)
.into(),
);
i = close + 1;
}
TokenTree::Punct(p) if p.as_char() == '>' && !is_arrow(tokens, i) => {
return Err(compile_error_str(
"batch-impl: 多余的 `>`(缺少匹配的 `<`)",
));
}
_ => {
out.push(tokens[i].clone());
i += 1;
}
}
}
Ok(out)
}
fn find_angle_close(tokens: &[TokenTree], open: usize) -> Option<usize> {
let mut depth = 0usize;
for (idx, token) in tokens.iter().enumerate().skip(open + 1) {
if is_punct(token, '<') {
depth += 1;
} else if is_punct(token, '>') && !is_arrow(tokens, idx) {
if depth == 0 {
return Some(idx);
}
depth -= 1;
}
}
None
}
fn is_punct(token: &TokenTree, ch: char) -> bool {
matches!(token, TokenTree::Punct(p) if p.as_char() == ch)
}
pub(crate) fn render_angles(stream: TokenStream) -> TokenStream {
let mut out = TokenStream::new();
for tt in stream {
match tt {
TokenTree::Group(g) if g.delimiter() == Delimiter::None => {
let inner = render_angles(g.stream());
out.extend([TokenTree::from(proc_macro2::Punct::new(
'<',
proc_macro2::Spacing::Alone,
))]);
out.extend(inner);
out.extend([TokenTree::from(proc_macro2::Punct::new(
'>',
proc_macro2::Spacing::Alone,
))]);
}
TokenTree::Group(g)
if matches!(
g.delimiter(),
Delimiter::Parenthesis | Delimiter::Bracket
) =>
{
let inner = render_angles(g.stream());
let mut new_g = Group::new(g.delimiter(), inner);
new_g.set_span(g.span());
out.extend([TokenTree::Group(new_g)]);
}
other => out.extend([other]),
}
}
out
}