pipe_op/lib.rs
1#![allow(dead_code)] // TODO: remove
2
3extern crate proc_macro;
4use proc_macro::TokenStream;
5use proc_macro2::Span;
6use quote::*;
7use syn::{self, parse_macro_input};
8use syn::{Expr, ExprCall, ExprMethodCall};
9
10#[derive(Debug)]
11enum Callable<'a> {
12 Function(&'a mut ExprCall),
13 Method(&'a mut ExprMethodCall),
14}
15
16#[derive(Debug, Clone)]
17struct PipeInput {
18 expr: syn::Expr,
19 ops: syn::punctuated::Punctuated<Expr, syn::token::Comma>,
20}
21
22impl syn::parse::Parse for PipeInput {
23 fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
24 // get the inital expression
25 let expr: syn::Expr = input.parse()?;
26 input.parse::<syn::token::Comma>()?;
27
28 Ok(Self {
29 expr,
30 ops: input.parse_terminated(syn::Expr::parse)?, // parses the rest of the callables
31 })
32 }
33}
34
35impl PipeInput {
36 fn pipe_output(mut self) -> Result<TokenStream, Box<dyn std::error::Error>> {
37 // create the argument to inject
38 let arg0: syn::Expr = syn::ExprPath {
39 attrs: vec![],
40 qself: None,
41 path: syn::Ident::new("result", Span::call_site()).into(),
42 }
43 .into();
44
45 for op in self.ops.iter_mut() {
46 // Here we move right to left in the chaining calls.
47 // So func().method().field.call() we are going from right to left
48 // and keeping track of the leftmost value we can inject the arg
49 // into. When we are all the way to the right we do it.
50 let insert_into = match get_rightmost_callable(op) {
51 Some(val) => val,
52 None => return Err("Nothing to inject arg into".into()),
53 };
54 match insert_into {
55 Callable::Function(f) => {
56 f.args.insert(0, arg0.clone());
57 }
58 Callable::Method(m) => {
59 m.args.insert(0, arg0.clone());
60 }
61 }
62 }
63
64 // formatting the output
65 let init = self.expr;
66 let ops = self.ops.iter();
67 Ok(quote!({
68 let result = #init;
69 #(let result = #ops;)*
70 result
71 })
72 .into())
73 }
74}
75
76// goes threw an expression tree and gets the furthest left callable method
77// or function.
78fn get_rightmost_callable(e: &mut Expr) -> Option<Callable> {
79 match e {
80 Expr::Call(f) => {
81 // return this function
82 return Some(Callable::Function(f));
83 }
84 Expr::MethodCall(m) => {
85 // it is possible that we can go further so we don't break
86 // this is some vodoo magic passed down to me by Mutabah on discord
87 // Its safe because m is never actually mutated untill after this
88 // function call
89 let something = unsafe { &mut *(m.receiver.as_mut() as *mut _) };
90 match get_rightmost_callable(something) {
91 Some(val) => Some(val),
92 None => {
93 Some(Callable::Method(m))
94 }
95 }
96 }
97 Expr::Try(t) => get_rightmost_callable(t.expr.as_mut()),
98 Expr::Await(a) => get_rightmost_callable(a.base.as_mut()),
99 Expr::Field(f) => get_rightmost_callable(f.base.as_mut()),
100 Expr::Path(_) | Expr::Lit(_) | Expr::Block(_) => None,
101 _ => unimplemented!(),
102 }
103}
104
105#[proc_macro]
106/// pipe is a macro which allows you to do the same thing as `|>` in elixir
107///
108/// ```rust
109/// use pipe_op::pipe;
110///
111/// fn add(a: usize, b: usize) -> usize {
112/// a + b
113/// }
114///
115/// fn main() {
116/// let a = 10;
117/// assert_eq!(pipe!(a, add(10)), add(a, 10));
118/// }
119/// ```
120///
121/// pipe can be used to pipe any number of things into the next as long as
122/// the result of the expression to the left's type is the same as the first
123/// argument of the expression on the right. This means async, try, and any
124/// number of combinations will work.
125///
126/// ```rust
127/// use pipe_op::pipe;
128///
129/// fn add(a: usize, b: usize) -> Result<usize, Box<dyn std::error::Error>> {
130/// Ok(a + b)
131/// }
132///
133/// fn main() -> Result<(), Box<dyn std::error::Error>>{
134/// let e = 10;
135/// assert_eq!(pipe!(e, add(10)?), 20);
136/// Ok(())
137/// }
138/// ```
139///
140/// Methods work as well
141/// ```rust
142/// use pipe_op::pipe;
143///
144/// struct Adder {
145/// value: usize,
146/// }
147///
148/// impl Adder {
149/// fn add(&self, num: usize) -> usize {
150/// self.value + num
151/// }
152/// }
153///
154/// fn main() {
155/// let s = Adder { value: 10 };
156/// assert_eq!(11, pipe!(1, s.add()));
157/// }
158/// ```
159///
160/// and so does many operations
161///
162/// ```rust
163/// use pipe_op::pipe;
164///
165/// fn add(a: usize, b: usize) -> usize {
166/// a + b
167/// }
168///
169/// fn main() {
170/// assert_eq!(
171/// pipe!(
172/// 1,
173/// add(10),
174/// add(10),
175/// add(10),
176/// add(10),
177/// add(10),
178/// add(10),
179/// add(10),
180/// add(10),
181/// add(10),
182/// add(10)
183/// ),
184/// 101
185/// );
186/// }
187/// ```
188pub fn pipe(input: TokenStream) -> TokenStream {
189 let input: PipeInput = parse_macro_input!(input);
190 input.pipe_output().expect("Unacceptable input")
191}