use std::mem::take;
use proc_macro::{Delimiter, Spacing, TokenTree};
#[proc_macro]
pub fn akin(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let mut vars: Vec<(String, Vec<String>)> = Vec::with_capacity(2);
let mut tokens = input.into_iter();
let mut first = tokens.next().expect("akin: expected code to duplicate");
let mut second = tokens.next().expect("akin: expected code to duplicate");
while matches!(&first, TokenTree::Ident(id) if id.to_string() == "let")
&& matches!(&second, TokenTree::Punct(p) if p.as_char() == '&')
{
vars.push(parse_var(&mut tokens, &vars));
first = tokens.next().expect("akin: expected code to duplicate");
second = tokens.next().expect("akin: expected code to duplicate");
}
let mut previous = first.clone();
let init = fold_tt(
fold_tt(String::new(), first, &mut previous),
second,
&mut previous,
);
let out_raw = tokens.fold(init, |acc, tt| fold_tt(acc, tt, &mut previous));
let out = duplicate(&out_raw, &vars);
out.parse().unwrap()
}
fn parse_var(
tokens: &mut proc_macro::token_stream::IntoIter,
vars: &[(String, Vec<String>)],
) -> (String, Vec<String>) {
let name = format!(
"*{}",
tokens.next().expect("akin: expected code to duplicate")
);
let mut prev = tokens.next().expect("akin: expected code to duplicate"); let mut values: Vec<String> = Vec::new();
let group =
if let TokenTree::Group(g) = tokens.next().expect("akin: expected code to duplicate") {
g
} else {
return (name, values);
};
if group.delimiter() == Delimiter::Bracket {
let mut add = String::new();
for var in group.stream() {
let new = match &var {
TokenTree::Group(g) if g.delimiter() == Delimiter::Brace => g
.stream()
.into_iter()
.fold(String::new(), |acc, tt| fold_tt(acc, tt, &mut prev)),
TokenTree::Punct(p) if p.as_char() != ',' => {
add = var.to_string();
continue;
}
_ => var.to_string(),
};
if new == "NONE" {
values.push(String::new())
} else if new != "," {
values.push(duplicate(&(take(&mut add) + &new), vars));
}
}
} else {
let fold = group
.stream()
.into_iter()
.fold(String::new(), |acc, tt| fold_tt(acc, tt, &mut prev));
values.push(duplicate(&fold, vars));
}
if !matches!(tokens.next(), Some(TokenTree::Punct(p)) if p.as_char() == ';') {
panic!(
"akin: expected ';' on end of {}'s declaration",
name.replacen('*', "&", 1)
);
}
(name, values)
}
fn duplicate(stream: &str, vars: &[(String, Vec<String>)]) -> String {
let (vars, times) = get_used_vars(stream, vars);
let mut out = String::with_capacity(stream.len() * times);
for i in 0..times {
out += stream;
for var in &vars {
out = out.replace(
&var.0,
var.1.get(i).unwrap_or_else(|| var.1.last().unwrap()),
)
}
}
if out.is_empty() {
stream.into()
} else {
out
}
}
fn get_used_vars<'a>(
stream: &str,
vars: &'a [(String, Vec<String>)],
) -> (Vec<&'a (String, Vec<String>)>, usize) {
let mut used = Vec::with_capacity(vars.len());
let mut times = 0;
for var in vars {
if stream.contains(&var.0) {
used.push(var);
times = times.max(var.1.len());
}
}
(used, times)
}
fn get_delimiters(delimiter: Delimiter) -> (char, char) {
match delimiter {
Delimiter::Parenthesis => ('(', ')'),
Delimiter::Brace => ('{', '}'),
Delimiter::Bracket => ('[', ']'),
Delimiter::None => ('\0', '\0'),
}
}
fn fold_tt(a: String, tt: TokenTree, prev: &mut TokenTree) -> String {
let ret = if let TokenTree::Group(g) = &tt {
let (start, end) = get_delimiters(g.delimiter());
let group = g
.stream()
.into_iter()
.fold(String::new(), |acc, tt| fold_tt(acc, tt, prev));
format!("{a}{start}{group}{end}")
} else if matches!(&tt, TokenTree::Punct(p) if p.as_char() == '~') {
a } else if matches!(&prev, TokenTree::Punct(p) if p.spacing() == Spacing::Joint || matches!(p.as_char(), '*' | '~')) {
format!("{a}{tt}")
} else {
format!("{a} {tt}")
};
*prev = tt;
ret
}