4

I need a macro that will call functions with different numbers of arguments or a macro that will generate a valid argument list from its (repeating) parameters.

I am fine with explicitly giving the information about the number of arguments to the macro, but I can't figure out how to generate the argument list for the function - I always stumble on the macros returning expressions rather than token tree.

I made the following playground example:

macro_rules! call (
    ($f: expr, $($params:tt)*) => {
        $f(make_params!($($params:tt)*))
    };
);

macro_rules! make_params {
    () => {};
    (I $($params: tt)*) => {
        1, make_params!($($params:tt)*)
    };
}


fn foo(a: i32, b: i32, c: i32) {
    println!("foo: {} {} {}", a, b, c);
}

fn bar(a: i32, b: i32) {
    println!("bar: {} {}", a, b);
}

fn main() {
    call!(foo, I I I);
    call!(bar, I I);
}

The compiler complains with the following:

error: macro expansion ignores token `,` and any following
  --> src/main.rs:10:10
   |
10 |         1, make_params!($($params:tt)*)
   |          ^
   |
note: caused by the macro expansion here; the usage of `make_params!` is likely invalid in expression context
  --> src/main.rs:3:12
   |
3  |         $f(make_params!($($params:tt)*))
   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...

How can I treat the return of make_params! as a token stream (or such) rather than expression?

My real use case is a bit more involved than this toy example. My functions have multiple parameter types which are constructed in different ways. In my case, just making macros call1, call2!, ... does not seem like a good solution, as I would need the likes of call_IIOOI, call_IIIO, etc.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366

1 Answers1

4

You need to build the function call progressively as you go and only emit it at once in the end:

macro_rules! call (
    ($f: expr, $($params:tt)*) => {
        make_call!($f, () $($params)*)
    };
);

macro_rules! make_call {
    ($f: expr, ($($args:tt)*)) => { $f($($args)*) };
    ($f: expr, () I $($params:tt)*) => {
        make_call!($f, (1) $($params)*)
    };
    ($f: expr, ($($args:tt)*) I $($params:tt)*) => {
        make_call!($f, ($($args)*, 1) $($params)*)
    };
}

playground

Jmb
  • 18,893
  • 2
  • 28
  • 55
  • I see this uses an accumulator to construct the parameter list. I was wondering about something in that direction but didn't see how to do it. This is more or less exactly what I needed - thanks! – Tomáš Gavenčiak Apr 26 '18 at 21:30