1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
#![no_std]

//! The `partial!` macro allows for partial application of a function.
//!
//! `partial!(some_fn => arg0, _, arg2, _)` returns the closure `|x1, x3| some_fn(arg0, x1, arg2, x3)`. <br>
//! Move closures are created by adding `move` in front of the function: `partial!(move ..)`
//!
//! ```rust
//! #[macro_use]
//! extern crate partial_application;
//!
//! fn foo(a: i32, b: i32, c: i32, d: i32, mul: i32, off: i32) -> i32 {
//!     (a + b*b + c.pow(3) + d.pow(4)) * mul - off
//! }
//!
//! fn main() {
//!     let bar = partial!(foo => _, _, 10, _, 10, 10);
//!     assert_eq!(
//!         foo(15, 15, 10, 42, 10, 10),
//!         bar(15, 15,     42)
//!     );
//! }
//! ```
//!
//! The expressions used to fix an argument are reevaluated on every call of the new function because of the straightforward translation behind the macro. <br>
//! ```rust
//! # #[macro_use]
//! # extern crate partial_application;
//! # fn main() {
//! #
//! fn identity(x: u32) -> u32 { x }
//!
//! let mut n = 0;
//! let mut f = partial!(identity => { n += 1; n});
//! assert_eq!(f(), 1);
//! assert_eq!(f(), 2);
//! # }
//! ```
//! Pre-compute arguments to be fixed in a local variable, if their creation is expensive or has unwanted side-effects.
//!
//! You can also use a comma (`,`) or semicolon (`;`) instead of the arrow (`=>`).
//! This strange syntax choice is due to limitations imposed on us by the macro system.
//! No other tokens may follow the expression token for the function.

/// The macro that creates a wrapping closure for a partially applied function
///
/// Syntax: `partial!(move? fn_name ("=>" | "," | ";") comma_separated_arg_list)`
///         `=>`, `,` and `;` are completely equivalent and act only as separator between
///         function and arguments.
///
/// Function arguments are either expressions or `_` <br>
/// `_` arguments have to be supplied on each call. They forward from the resulting closure into the function. <br>
/// Expressions are hardcoded into the function call. <br>
/// `partial!(foo => _)` => `|a| foo(a);` <br>
/// `partial!(foo => 2)` => `|| foo(2);`
///
/// Prepending `move` to the `fn_name` creates a move closure. Trailing commas are permitted.
#[macro_export]
macro_rules! partial {
    // The macro works with 3 lists
    // 1. closure args $cl_arg(s)
    //    The argument identifiers for the closure
    // 2. fn args      $fn_arg(s)
    //    The argument identifiers and forwarded expressions for the fn
    //
    //    Arg idents are passed around for hygiene reasons and to keep track
    //    of their number
    //
    // 3. the macro arguments $m_args
    //    A list of expressions and the forwarding sign '_'
    //    from which the former two lists are built up
    //
    // Until $m_args is empty, an element is popped off its front
    // and the appropiate pieces are pushed to cl_args and/or fn_args
    //
    // The fn ident and the move closure "boolean" (either "move" or "()")
    // are simpyl passed through during list processing inside $pt (pass-through)

    // exhausted macro arguments, create closure
    (@inner [(() $id:expr) ($($cl_arg:ident),*) ($($fn_arg:expr),*)] ()) => {
        |$($cl_arg),*| $id($($fn_arg),*);
    };
    // with move
    (@inner [(move $id:expr) ($($cl_arg:ident),*) ($($fn_arg:expr),*)] ()) => {
        move |$($cl_arg),*| $id($($fn_arg),*);
    };

    // process forwarder '_' ,
    (@inner [$pt:tt ($($cl_arg:ident),*) ($($fn_arg:expr),*)] (_ , $($m_arg:tt)*) ) => {
        partial!(
            @inner [$pt ($($cl_arg,)* a) ($($fn_arg,)* a)] ($($m_arg)*)
        )
    };
    // last forwarder (if no trailing comma)
    (@inner [$pt:tt ($($cl_arg:ident),*) ($($fn_arg:expr),*)] (_) ) => {
        partial!(
            @inner [$pt ($($cl_arg,)* a) ($($fn_arg,)* a)] ()
        )
    };

    // process given expr
    (@inner [$pt:tt $cl_args:tt ($($fn_arg:expr),*)] ($e:expr , $($m_arg:tt)*) ) => {
        partial!(
            @inner [$pt $cl_args ($($fn_arg,)* $e)] ($($m_arg)*)
        )
    };
    // last expr (if no trailing comma)
    (@inner [$pt:tt $cl_args:tt ($($fn_arg:expr),*)] ($e:expr) ) => {
        partial!(
            @inner [$pt $cl_args ($($fn_arg,)* $e)] ()
        )
    };

    // entry points
    // ordered to match eagerly
    // move
    (move $id:expr , $($args:tt)*) => {
        partial!(@inner [(move $id) () ()] ($($args)*))
    };
    (move $id:expr ; $($args:tt)*) => {
        partial!(@inner [(move $id) () ()] ($($args)*))
    };
    (move $id:expr => $($args:tt)*) => {
        partial!(@inner [(move $id) () ()] ($($args)*))
    };

    // no move
    ($id:expr , $($args:tt)*) => {
        partial!(@inner [(() $id) () ()] ($($args)*))
    };
    ($id:expr ; $($args:tt)*) => {
        partial!(@inner [(() $id) () ()] ($($args)*))
    };
    ($id:expr => $($args:tt)*) => {
        partial!(@inner [(() $id) () ()] ($($args)*))
    };
}

#[cfg(test)]
mod test {
    // compile time check for maximum arity
    // 60 with default recursion limit
    #[allow(unused)]
    #[rustfmt::skip]
    fn high_arity(
        _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (),
        _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (),
        _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (),
        _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (),
        _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (), _: (),
        _: (), _: ()
    ) {
        let c = partial!(high_arity,
            (), (), (), (),     (), (), (), (),     (), (), (), (),
            (), (), (), (),     (), (), (), (),     (), (), (), (),
            (), (), (), (),     (), (), (), (),     (), (), (), (),
            (), (), (), (),     (), (), (), (),     (), (), (), (),
            (), (), (), (),     (), (), (), (),     (), (), (), (),
            (), ()
        );
    }

    #[test]
    fn argument_order() {
        // non-commutative arguments
        // wrong forwarding order will result in error
        fn foo(a: u32, b: u32) -> u32 {
            100 + a - b
        }

        for i in 0..10 {
            for j in 0..10 {
                assert_eq!(foo(i, j), partial!(foo => i, _)(j));
            }
        }
    }

    #[test]
    // tests preservation of argument order in a more complex setting
    fn interspersed_expr_and_forwarders() {
        fn foo(a: bool, b: bool, c: bool, d: bool, e: bool, f: bool) -> u8 {
            fn shift(b: bool, n: usize) -> u8 {
                (b as u8) << n
            }
            // in reverse so a is most significant
            // resulting number will be abcdef
            // where each letter represents a bit
            [f, e, d, c, b, a]
                .iter()
                .cloned()
                .enumerate()
                .fold(0, |acc, (n, arg)| acc | shift(arg, n))
        }

        let reduced_foo = partial!(foo => true, _, _, true, true, _);
        assert_eq!(reduced_foo(false, false, false), 0b100110);
    }
}

// moving a !Copy type forces FnOnce
// which should fail to compile on reuse
#[allow(unused)]
/// ```compile_fail
/// #[macro_use] extern crate partial_application;
/// fn main() {
///     struct Foo(u32);
///     let sub = |a: u32, b: Foo| a - b.0;
///
///     let f = Foo(5);
///     let sub5 = partial!(move sub => _, f);
///
///     sub5(5);
///     sub5(5);
/// }
/// ```
struct MoveCompileFail;

#[cfg(test)]
#[allow(unused)]
// compile check
fn syntax_check() {
    partial!(::core::option::Option::<i32>::is_some => _ );

    #[derive(Clone)]
    struct NoCopy;

    fn foo(_: u8, _: u8, _: u8, _: u8, _: u8, _: u8, _: NoCopy) {}

    let a = (NoCopy,);
    let b = (5,);
    let five: fn() -> u8 = || 5;

    let num = 10;

    // test various forms of expressions
    // and trailing commas for forwarders and expressions
    partial!(foo => 2, _, num, {stringify!(boo); 2}, b.0, five(), _);
    partial!(foo => 2, _, num, {stringify!(boo); 2}, b.0, five(), _,);
    partial!(foo => 2, _, num, {stringify!(boo); 2}, b.0, five(), a.clone().0,);
    partial!(foo => 2, _, num, {stringify!(boo); 2}, b.0, five(), a.clone().0);

    partial!(move foo => 2, _, num, {stringify!(boo); 2}, b.0, five(), _);
    partial!(move foo => 2, _, num, {stringify!(boo); 2}, b.0, five(), _,);
    let s = a.clone();
    partial!(move foo => 2, _, num, {stringify!(boo); 2}, b.0, five(), s.clone().0,);
    let s = a.clone();
    partial!(move foo => 2, _, num, {stringify!(boo); 2}, b.0, five(), s.clone().0);
    let s = a;
    partial!(move foo => 2, _, num, {stringify!(boo); 2}, b.0, five(), s.0);
}