#![doc = include_str!("../README.md")]
use proc_macro::TokenStream;
use proc_macro2::Ident;
use proc_macro2::Span;
use quote::ToTokens;
use syn::parse_quote;
use syn::visit_mut::VisitMut;
use syn::{parse_macro_input, visit_mut, Expr, UnOp};
struct LiftMonadic {
lifted: Vec<(Expr, Ident)>,
}
impl LiftMonadic {
pub fn new() -> LiftMonadic {
LiftMonadic { lifted: Vec::new() }
}
}
impl VisitMut for LiftMonadic {
fn visit_expr_mut(&mut self, i: &mut Expr) {
if let Expr::Unary(un_expr) = i {
self.visit_expr_unary_mut(un_expr);
if let UnOp::Not(_) = un_expr.op {
let id = self.lifted.len();
let fresh_ident = Ident::new(
format!("__bang_inner_bind_{id}").as_str(),
Span::call_site(),
);
self.lifted
.push((*un_expr.expr.clone(), fresh_ident.clone()));
*i = parse_quote!(#fresh_ident);
}
return;
}
visit_mut::visit_expr_mut(self, i);
}
}
#[proc_macro]
pub fn bang(input: TokenStream) -> TokenStream {
let mut ast: Expr = parse_macro_input!(input as Expr);
let mut collector = LiftMonadic::new();
collector.visit_expr_mut(&mut ast);
let ast = collector
.lifted
.iter()
.rev()
.fold(ast, |cur_ast, (expr, ident)| {
parse_quote! {
(#expr).and_then(|#ident| { #cur_ast })
}
});
ast.to_token_stream().into()
}