use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{
Expr, ExprCall, ExprPath, Result, Token,
parse::{Parse, ParseStream},
parse_macro_input,
punctuated::Punctuated,
visit_mut::{self, VisitMut},
};
struct WithStateInput {
state_expr: Expr,
content: TokenStream2,
}
impl Parse for WithStateInput {
fn parse(input: ParseStream) -> Result<Self> {
let state_expr = input.parse::<Expr>()?;
input.parse::<Token![;]>()?;
let content = input.parse()?;
Ok(WithStateInput {
state_expr,
content,
})
}
}
struct StatefulCallVisitor<'a> {
state_expr: &'a Expr,
}
impl VisitMut for StatefulCallVisitor<'_> {
fn visit_expr_call_mut(&mut self, call: &mut ExprCall) {
visit_mut::visit_expr_call_mut(self, call);
if let Expr::Path(ExprPath { path, .. }) = &mut *call.func {
if path.leading_colon.is_some() {
let mut new_path = path.clone();
new_path.leading_colon = None;
call.func = Box::new(Expr::Path(ExprPath {
attrs: vec![],
qself: None,
path: new_path,
}));
let mut new_args = Punctuated::new();
new_args.push(self.state_expr.clone());
for arg in call.args.iter() {
new_args.push(arg.clone());
}
call.args = new_args;
}
}
}
}
pub fn with_state(input: TokenStream) -> TokenStream {
let WithStateInput {
state_expr,
content,
} = parse_macro_input!(input as WithStateInput);
let mut parsed_content = match syn::parse2::<syn::Block>(quote! { { #content } }) {
Ok(block) => block.stmts,
Err(_) => {
return TokenStream::from(quote! {
compile_error!("Failed to parse macro content")
});
}
};
let mut visitor = StatefulCallVisitor {
state_expr: &state_expr,
};
for stmt in &mut parsed_content {
visitor.visit_stmt_mut(stmt);
}
TokenStream::from(quote! {
#(#parsed_content)*
})
}