#![allow(dead_code)]
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::*;
use syn::{self, parse_macro_input};
use syn::{Expr, ExprCall, ExprMethodCall};
#[derive(Debug)]
enum Callable<'a> {
Function(&'a mut ExprCall),
Method(&'a mut ExprMethodCall),
}
#[derive(Debug, Clone)]
struct PipeInput {
expr: syn::Expr,
ops: syn::punctuated::Punctuated<Expr, syn::token::Comma>,
}
impl syn::parse::Parse for PipeInput {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
let expr: syn::Expr = input.parse()?;
input.parse::<syn::token::Comma>()?;
Ok(Self {
expr,
ops: input.parse_terminated(syn::Expr::parse)?, })
}
}
impl PipeInput {
fn pipe_output(mut self) -> Result<TokenStream, Box<dyn std::error::Error>> {
let arg0: syn::Expr = syn::ExprPath {
attrs: vec![],
qself: None,
path: syn::Ident::new("result", Span::call_site()).into(),
}
.into();
for op in self.ops.iter_mut() {
let insert_into = match get_rightmost_callable(op) {
Some(val) => val,
None => return Err("Nothing to inject arg into".into()),
};
match insert_into {
Callable::Function(f) => {
f.args.insert(0, arg0.clone());
}
Callable::Method(m) => {
m.args.insert(0, arg0.clone());
}
}
}
let init = self.expr;
let ops = self.ops.iter();
Ok(quote!({
let result = #init;
#(let result = #ops;)*
result
})
.into())
}
}
fn get_rightmost_callable(e: &mut Expr) -> Option<Callable> {
match e {
Expr::Call(f) => {
return Some(Callable::Function(f));
}
Expr::MethodCall(m) => {
let something = unsafe { &mut *(m.receiver.as_mut() as *mut _) };
match get_rightmost_callable(something) {
Some(val) => Some(val),
None => {
Some(Callable::Method(m))
}
}
}
Expr::Try(t) => get_rightmost_callable(t.expr.as_mut()),
Expr::Await(a) => get_rightmost_callable(a.base.as_mut()),
Expr::Field(f) => get_rightmost_callable(f.base.as_mut()),
Expr::Path(_) | Expr::Lit(_) | Expr::Block(_) => None,
_ => unimplemented!(),
}
}
#[proc_macro]
pub fn pipe(input: TokenStream) -> TokenStream {
let input: PipeInput = parse_macro_input!(input);
input.pipe_output().expect("Unacceptable input")
}