How to match optional ret type in macro?


#1

How can I make the => $ret:ty optional when calling the macro, and use () for $ret type when omitted?
http://dpaste.com/108HTH7
By optional I mean “on a per $fn basis” (not either omitted for all or given for all).
So that e.g. line 48, 69 and 81 can be written without => ().

Is it possible? :slight_smile:

As you can see I need all $fns inside the macro body to generate the struct members…


#2

Edit: Just do what @dtolnay said.

With that syntax? You’d need an incremental parser, which would not be a lot of fun.

If you change the syntax so that the function type is inside a group (i.e. (), [], or {}), you can blindly match the whole group, then feed that to a different macro that can distinguish between “has trailing return type” and not on a fn-by-fn basis.

Or you could just write the => () out and keep the macro simple.


#3

I would use a $( )* repetition, i.e.:

macro_rules! api {
    ($($fn:ident = $($arg_name:ident : $arg_type:ty),* $(=> $ret:ty)*;)+) => {

If a caller of the macro writes more than one => $ret, it will match this pattern but fail to compile anyway because the macro would expand to invalid syntax.

There is a $( )? matcher in the nightly compiler which accepts 0 or 1 of its contents, so in the future you would use $(=> $ret:ty)?. Tracked in rust-lang/rust#48075.


#4

@DanielKeep

  1. Why does it need to be in a group, why can’t I just match tokens until each ;? Like ($($($tok:tt)+;)+) => ... and then delegate $($tok)+?
  2. If I was using a group, how should I match the inner stuff if not with this?: ($([$($tok:tt)+])+) => ...

@dtolnay Thanks! And then, how can I conditionally use () as default return type? Should I make a new sub-macro (or internal branch with @foo) that has 2 cases to match each one and then delegate to that sub-macro for generating the stuff in line 3 and line 12-17?


#5

Because the macro matcher isn’t that clever. As soon as you start a $($tok:tt)*, it will eat everything until the end of the current token group.

My point about using a group was so that you delegate parsing the function arguments to another macro, which can just have two rules: one for functions with a return type, one for functions without.

… but then dtolnay reminded me that in this case you should be able to get away with a repetition match on the => $ret:ty bit. And yes, you’d want to delegate to another macro or sub-rule that expands (=> $ret:ty) => {$ret} or () => {()}.


#6

@dtolnay @DanielKeep Thanks :slight_smile:

This works:

macro_rules! api {
	(@retty $ret:ty) => { $ret };
	(@retty) => { () };
	($($fn:ident = $($arg_name:ident : $arg_type:ty),* $(=> $ret:ty)?;)+) => {
		$(pub type $fn = extern fn($($arg_name: $arg_type),*) -> api!(@retty $($ret)?);)*
		pub struct ReaperApi {
			$(pub $fn: $fn,)*
		}
		impl ReaperApi {
			pub fn get(host: &HostCallback) -> Option<Self> {
				$(let $fn: $fn = host.get_reaper_fn(stringify!($fn))?;)*
				Some(ReaperApi { $($fn),+ })
			}
			$(
				#[allow(dead_code)]
				#[inline(always)]
				pub fn $fn(&self, $($arg_name: $arg_type),*) -> api!(@retty $($ret)?) {
					(self.$fn)($($arg_name),*)
				}
			)*
		}
	}
}

#7

nice exactly what i was missing


#8

I would recommend removing the @retty stuff and using just:

extern fn($($arg_name: $arg_type),*) $(-> $ret)*

#9

Ah yes, thanks :slight_smile:
I’m using $(-> $ret)? in the expansion now.

Btw, when to use $(..)+ in the expansion and when $(..)*?
Should I use the same that I used for matching in the args?
But I was using $(..)* to expand stuff matched with $(..)+ and it didn’t complain…


#10

Probably. I’m only slightly surprised you can mismatch them like that, but it’s poor form to do so. :stuck_out_tongue: