Skip to main content

pipelining_macro/
lib.rs

1#![cfg_attr(test, feature(macro_metavar_expr))]
2#![no_std]
3#![doc = include_str!("../README.md")]
4
5/// A macro which evaluates functions from left to right, rather than from inside to outside.
6///
7/// Syntax: `pipe!(init => fn1 => fn2 => ...)`
8///
9/// Each function is either the name of a single-argument function (optionally with parentheses),
10/// an expression which is parenthesizable and callable as a single-argument function (usually a lambda),
11/// or a name/parenthesized expression followed by a parenthesized comma-separated list of arguments with one or more
12/// arguments left as blank (`_`). All function calls and expressions to the left will be evaluated, stored in a temporary,
13/// and then inserted into the current function call in place of any blanks.
14#[macro_export]
15macro_rules! pipe {
16	($e:expr) => { $e };
17	($in:expr => $($i:ident).+ $(())? $(=> $($tail:tt)+)?) => {
18		pipe!($($i).+($in) $(=> $($tail)+)?)
19	};
20	($in:expr => $($i:ident).+ ($($($arg_head:expr_2021),*,)? $(_ $(, $arg_tail:expr_2021)*),*) $(=> $($tail:tt)+)?) => {
21		{
22			let pipe_temp = $in; // Eval once and cache
23			pipe!($($i).+($($($arg_head),*,)? $(pipe_temp $(, $arg_tail)*),*) $(=> $($tail)+)?)
24		}
25	};
26	($in:expr => ($e:expr) ($($($arg_head:expr_2021),*,)? $(_ $(, $arg_tail:expr_2021)*),*) $(=> $($tail:tt)+)?) => {
27		{
28			let pipe_temp = $in; // Eval once and cache
29			pipe!($e($($($arg_head),*,)? $(pipe_temp $(, $arg_tail)*),*) $(=> $($tail)+)?)
30		}
31	};
32	($in:expr => $e:expr $(=> $($tail:tt)+)?) => {
33		pipe!($e($in) $(=> $($tail)+)?)
34	};
35}
36
37#[cfg(test)]
38mod tests {
39	/// Tests the simple use case - piping to a function which only accepts a single argument.
40	#[test]
41	fn test_simple() {
42		fn test(x: u16) -> f32 {
43			f32::from(x + 1)
44		}
45
46		fn test2(x: f32) -> f32 {
47			x + 1.
48		}
49
50		let x = 3;
51
52		assert_eq!(pipe!(x+1 => test => test2), test2(test(x + 1)));
53		assert_eq!(pipe!(x+1 => test() => test2()), test2(test(x + 1)));
54	}
55
56	/// Tests piping one argument into a function which accepts multiple arguments.
57	#[test]
58	fn test_underscore_fill() {
59		fn test(x: u16) -> u16 {
60			x
61		}
62		fn test2(x: u16, y: u16) -> (u16, u16) {
63			(x, y)
64		}
65
66		let x = 3;
67		let y = 4;
68
69		assert_eq!(pipe!(x-1 => test => test2(_, y)), test2(test(x - 1), y));
70		assert_eq!(pipe!(x-1 => test => test2(y, _)), test2(y, test(x - 1),));
71		assert_eq!(
72			pipe!(x-1 => test => test2(_, _)),
73			test2(test(x - 1), test(x - 1))
74		);
75	}
76
77	/// Make sure associated functions are callable.
78	#[test]
79	fn test_associated_functions() {
80		fn test(x: u16) -> u16 {
81			x + 1
82		}
83
84		let x = 3;
85
86		assert_eq!(
87			pipe!(x-2 => test => u16::to_be => u16::isqrt),
88			u16::isqrt(u16::to_be(test(x - 2)))
89		);
90	}
91
92	/// Make sure we can pipe into methods.
93	#[test]
94	fn test_methods() {
95		fn test(x: u16) -> u16 {
96			x + 1
97		}
98
99		let x = 3;
100		let y = 4;
101		assert_eq!(
102			pipe!(x+2 => test => y.max => test),
103			test(y.max(test(x + 2)))
104		);
105
106		let mut y = None;
107		assert_eq!(
108			pipe!(x+2 => y.map_or(_, |x| x + 2) => test),
109			test(y.map_or(x + 2, |x| x + 2))
110		);
111
112		y = Some(x + 2);
113		assert_eq!(
114			pipe!(x+2 => y.map_or(_, |x| x + 2) => test),
115			test(y.map_or(x + 2, |x| x + 2))
116		);
117	}
118
119	/// Make sure we can pipe into a lambda.
120	#[test]
121	fn test_lambdas() {
122		fn test(x: u16) -> u16 {
123			x + 1
124		}
125
126		let x = 3;
127
128		assert_eq!(
129			pipe!(x => test => |x| {x - 2} => test),
130			test((|x| { x - 2 })(test(x)))
131		);
132	}
133
134	/// Make sure we can pipe into function-like objects returned by other macros
135	#[test]
136	fn test_macros() {
137		use paste::paste;
138
139		// A macro which partially evaluates a function
140		macro_rules! bind {
141			($($f:ident).+ ($($($head:expr_2021),*,)? $(_ $(, $tail:expr_2021)*),*)) => {
142				paste! {
143					|$([<x ${index()}>] $(${ignore($tail)})*),+| {
144						$($f).+($($($head),*,)? $([<x ${index()}>] $(, $tail)*),*)
145					}
146				}
147			};
148			($($f:ident).+ ($($head:expr_2021),*)) => {
149				|| {
150					$($f).+($($head),*)
151				}
152			};
153		}
154
155		fn test(x: u16, y: u16, z: u16) -> (u16, u16, u16) {
156			(x, y, z)
157		}
158
159		let x = 1;
160		let y = 2;
161		let z = 3;
162
163		assert_eq!(pipe!(x => bind!(test(_, y, z))), test(x, y, z));
164		assert_eq!(pipe!(y => bind!(test(x, _, z))), test(x, y, z));
165		assert_eq!(pipe!(x => (bind!(test(_, _, z))) (_, _)), test(x, x, z));
166	}
167}