How to generate => in macro

Hi,
I want to implement a macro to do something like this, match_index(x; y; z) would generate
0 => x,
1 => y,
2 => z,

The following code can't accomplish the task. I think the problem is that I can't use => in macro content.
What can I do to fix it?

#[macro_export]
macro_rules! match_index {
    ($n:expr,) => {};
    ($n:expr, $arm:expr $(;$arms:expr)*) => {
        usize{$n} => $arm,
        $crate::match_index!($n+1, $($arms);*)
    };
    ($($arms:expr);+) => {
        $crate::match_index!(0, $($arms);+)
    }
}

You have to generate the whole match, you can't just generate the arms.

In this case, how can I do to recursively generate arms if the whole match is required?

The output of macros have to be valid rust code, but the arguments don't. You can get around this limitation by passing the intermediate results as macro arguments, and only producing the match statement at the end:

macro_rules! match_index {
    // Wrap the user-provided arguments in `@(...)`
    ($e:expr; $($rest:tt)*) => {
        match_index!{@(0; $e; $($rest)*,)}
    };
    
    // Interpret one match arm
    (@($idx:expr; $e:expr; $val:expr, $($rest:tt)*) $($arms:tt)*) => {
        match_index!{
            // Next index, match argument, remaining match results
            @(1+$idx; $e; $($rest)*)
            $($arms)*
            x if x == $idx => $val,
        }
    };
    
    // No more match arms, produce final output
    (@($idx:expr; $e:expr; $(,)?) $($arms:tt)* ) => {
        match $e {
            $($arms)*
            _ => unreachable!()
        }
    };
}

(Playground)

5 Likes

Additionally, for this example you don't need a recursive macro at all:

macro_rules! match_index {
    ($e:expr; $($result:expr),*) => {{
        let mut i = 0..;
        let e = $e;
        $(if e == i.next().unwrap() { $result } else)*
        { unreachable!() }
    }}
}

(Playground)

Minor nit @2e71828: when binding a caller-provided :expr to a local, use a match rather than a let, so as to mimic as much as possible the semantics of the hypothetical equivalent match_index() function call (get let … in { … } semantics):

macro_rules! match_index {
    ($e:expr; $($result:expr),*) => ({
        let mut i = 0..;
        match $e { e => {
            $(if e == i.next().unwrap() { $result } else)*
            { unreachable!() }
        }}
    })
}
2 Likes

Yes, you are right, I also stumbled over this, but still, I want to know a match implementation for study.
I somehow figured out a solution just like you suggested, but it failed when the arms number goes larger than 2. I think it's a stupid problem, but I can't just figure out why.

#[macro_export]
macro_rules! match_index {
    ($index:ident, $n:expr,[$($indexes:expr),+], [$($arms:expr),+],) => {
        match $index {
            $(_i if _i == $indexes => $arms,)+
            _ => panic!("Out of index")
        }
    };
    ($index:ident, $n:expr, [$($indexes:expr),*], [$($arms:expr)*], $arm:expr $(;$rest:expr)*) => {
        $crate::match_index!($index, $n+1, [$($indexes,)* $n], [$($arms,)* $arm], $($rest);*)
    };
    ($index:ident, $($arms:expr);+) => {
        $crate::match_index!($index, 0, [], [], $($arms);+)
    }
}

Indeed, it's a stupid problem, I missed a comma in the arms bracket.

#[macro_export]
macro_rules! match_index {
    ($index:ident, $n:expr,[$($indexes:expr),+], [$($arms:expr),+],) => {
        match $index {
            $(_i if _i == $indexes => $arms,)+
            _ => panic!("Out of index")
        }
    };
    ($index:ident, $n:expr, [$($indexes:expr),*], [$($arms:expr),*], $arm:expr $(;$rest:expr)*) => {
        $crate::match_index!($index, $n+1, [$($indexes,)* $n], [$($arms,)* $arm], $($rest);*)
    };
    ($index:ident, $($arms:expr);+) => {
        $crate::match_index!($index, 0, [], [], $($arms);+)
    }
}
1 Like