#![no_std]
extern crate alloc;
extern crate proc_macro;
#[cfg(MULTICALL_DEBUG)]
extern crate std;
#[cfg(MULTICALL_DEBUG)]
use std::println;
use alloc::string::ToString;
use alloc::vec;
use alloc::vec::Vec;
use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree};
#[proc_macro]
pub fn multicall(input: TokenStream) -> TokenStream {
multicall_internal(input, false, false)
}
fn multicall_internal(input: TokenStream, is_recursed: bool, mut is_mut: bool) -> TokenStream {
let mut iter = input.into_iter();
#[cfg(MULTICALL_DEBUG)]
println!("creating new multicall block...");
let mut dat = if is_recursed {
#[cfg(MULTICALL_DEBUG)]
println!("inserting multicall item because this is a recursed block.");
let mut v = vec![
TokenTree::Punct(Punct::new('&', Spacing::Alone)),
TokenTree::Ident(Ident::new("mut", Span::call_site())),
TokenTree::Ident(Ident::new("__multicall_item__", Span::call_site())),
TokenTree::Punct(Punct::new('.', Spacing::Alone)),
];
if !is_mut {
v.remove(1);
}
v
} else {
Vec::new()
};
#[cfg(MULTICALL_DEBUG)]
println!("initialized. reading item...");
while let Some(item) = iter.next() {
if let TokenTree::Punct(ref x) = item {
if x.as_char() == ':' && x.spacing() == Spacing::Alone {
break;
}
}
if item.to_string() == "mut" && dat.len() == 1 {
is_mut = true;
}
dat.push(item)
}
#[cfg(MULTICALL_DEBUG)]
println!("item read. writing initial let statement.");
let mut ts = TokenStream::new();
ts.extend(
vec![
TokenTree::Ident(Ident::new("let", Span::call_site())),
TokenTree::Ident(Ident::new("__multicall_item__", Span::call_site())),
TokenTree::Punct(Punct::new('=', Spacing::Alone)),
]
.into_iter(),
);
ts.extend(dat.clone().into_iter());
ts.extend(vec![TokenTree::Punct(Punct::new(';', Spacing::Alone))].into_iter());
#[cfg(MULTICALL_DEBUG)]
println!("done. starting state machine and processing statements.");
#[derive(Default, PartialEq, Eq)]
enum State {
#[default]
InsertNew,
Set,
Inserted,
}
#[derive(Default)]
struct AccumState {
words: Vec<TokenTree>,
state: State,
}
ts.extend(
iter.fold(AccumState::default(), |mut accum, x| {
let o = x.to_string();
if let Some(x) = match x {
TokenTree::Group(ref x) if accum.state == State::InsertNew => Some(x),
_ => None,
} {
#[cfg(MULTICALL_DEBUG)]
println!("found group, making sub-call:");
accum
.words
.extend(multicall_internal(x.stream(), true, is_mut).into_iter());
accum.state = State::Inserted;
#[cfg(MULTICALL_DEBUG)]
println!("sub-call inserted.");
} else if o == ";" {
#[cfg(MULTICALL_DEBUG)]
println!("found semicolon. resetting.");
accum.state = State::InsertNew;
accum
.words
.push(TokenTree::Punct(Punct::new(';', Spacing::Alone)));
} else {
#[cfg(MULTICALL_DEBUG)]
println!("found statement. parsing...");
if accum.state == State::InsertNew {
#[cfg(MULTICALL_DEBUG)]
println!("detecting statement type...");
if o == "set" {
#[cfg(MULTICALL_DEBUG)]
println!("statement is 'set'.");
accum.state = State::Set;
return accum; } else if o == "exec" {
#[cfg(MULTICALL_DEBUG)]
println!("statement is 'exec'. marking for full replay.");
accum.state = State::Inserted;
return accum; }
#[cfg(MULTICALL_DEBUG)]
println!("inserting item.");
accum.words.push(TokenTree::Ident(Ident::new(
"__multicall_item__",
Span::call_site(),
)));
accum
.words
.push(TokenTree::Punct(Punct::new('.', Spacing::Alone)));
accum.state = State::Inserted;
#[cfg(MULTICALL_DEBUG)]
println!("done. replaying rest.");
}
if accum.state == State::Set {
if o == "=" {
#[cfg(MULTICALL_DEBUG)]
println!("replaying '='.");
accum.state = State::InsertNew;
}
}
#[cfg(MULTICALL_DEBUG)]
println!("replaying '{x}'");
accum
.words
.push(recursive_replace(x, "#", "__multicall_item__"));
}
accum
})
.words,
);
#[cfg(MULTICALL_DEBUG)]
println!("multicall block done.");
TokenStream::from(TokenTree::Group(Group::new(Delimiter::Brace, ts)))
}
fn recursive_replace(token: TokenTree, from: &str, to: &str) -> TokenTree {
match token {
TokenTree::Group(x) => TokenTree::Group({
let mut g = Group::new(
x.delimiter(),
TokenStream::from_iter(
x.stream()
.into_iter()
.map(|x| recursive_replace(x, from, to)),
),
);
g.set_span(x.span());
g
}),
TokenTree::Ident(x) => TokenTree::Ident(Ident::new(
x.to_string().replace(from, to).as_str(),
x.span(),
)),
TokenTree::Punct(x) if x.as_char() == from.chars().next().unwrap() && from.len() == 1 => {
TokenTree::Ident(Ident::new(to, x.span()))
}
x => x,
}
}