Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use output of macro as parameter for another macro

Tags:

macros

rust

I am trying to implement a generic Point<T> type for small dimensions.

To achieve that, I wrote a macro that takes the name of the new type and the dimension of the point (since, as far as I know, Rust doesn't allow for numerical generics).

macro_rules! define_point {
($type_name: ident, $dimension: expr) => {
        pub struct $type_name<T> {
            coords: [T; $dimension]
        }
    }
}

I use it like this:

define_point!(Point2, 2);
define_point!(Point3, 3);

This works fine. I also implement the Index trait on my Point type in this macro to access the coordinates directly.

Now I want some convenience functions for accessing the coordinates of my point as follows: p.x(), p.y() or p.z() depending on the dimension.

To do that, I have another macro:

macro_rules! impl_point_accessors {    
    ($type_name: ident, $coord_name: ident, $coord_index: expr) => {
        impl<T> $type_name<T> {
            pub fn $coord_name(&self) -> T {
                &self[$coord_index]
            }
        }
    };

    ($type_name: ident, $coord_name: ident, $coord_index: expr, $($extra_coord_name: ident, $extra_coord_index: expr),+) => {
        impl_point_accessors!($type_name, $coord_name, $coord_index);
        impl_point_accessors!($type_name, $($extra_coord_name, $extra_coord_index), +);
    }
}

I use it as follows:

impl_point_accessors!(Point2, x, 0, y, 1);
impl_point_accessors!(Point3, x, 0, y, 1, z, 2);

This seems to work when I look at the result of rustc --pretty=expanded.

Now, as an exercise, I wrote this other macro that would give me the list x, 0, y, 1, ... from the dimension directly:

macro_rules! dimension_to_coord_pairs {
    (1) => {
        x, 0
    };
    (2) => {
        x, 0, y, 1
    };
    (3) => {
        x, 0, y, 1, z, 2
    };
    (4) => {
        x, 0, y, 1, z, 2, w, 3
    };
}

However, when I try to use the output of this new macro like this:

impl_point_accessors!($type_name, dimension_to_coord_pairs!($dimension));

It looks like the dimension_to_coord_pairs macro does not get expanded into the list of arguments that I want.

My question now: Is there any way to tell Rust to expand the macro and use the expanded syntax as my list of arguments in another macro ?

like image 397
Sunreef Avatar asked Aug 22 '18 11:08

Sunreef


People also ask

Can you call a macro from another macro?

Just type the word Call then space, then type the name of the macro to be called (run). The example below shows how to call Macro2 from Macro1. It's important to note that the two macros DO NOT run at the same time. Once the Call line is hit, Macro2 will be run completely to the end.

How do I pass a variable from one macro to another?

To do that, you create what are called arguments. You come up with a name for a variable that you can use in the macro and put it in the middle of the parentheses at the top of the macro. This argument can then be used within the macro as a regular variable.

How many ways you can pass parameters to macro?

There are two distinct approaches to passing parameters to macros.

How do I pass a parameter to a macro in Excel?

Yes, you can assign a macro to a button (or other excel controls/menu actions) and pass constant OR variable arguments to it. In the 'Assign Macro' window (right-click on object and select 'Assign Macro'): Enclose the macro name in single quotes e.g. to pass 2 constants: 'Button1_Click("A string!", 7)'


2 Answers

A macro can invoke another macro, but one cannot take the result of another. Each macro invocation must result in legal code, which can include other macro invocations, but the compiler should never have to figure out which macro you intended to be invoked first.

You can work around your problem by reorganising the macros to be completely top-down, something like this:

macro_rules! define_point {
    ($type_name: ident, $dimension: tt) => {
        pub struct $type_name<T> {
            coords: [T; $dimension]
        }
        impl_point_accessors!($type_name, $dimension);
    }
}

macro_rules! impl_point_accessors {    
    ($type_name: ident, $dimension: tt) => {
        impl<T> $type_name<T> {
            write_coord_getters!($dimension);
        }
    };
}

macro_rules! coord_getter {
    ($coord_name: ident, $coord_index: expr, $ret: ty) => {
        pub fn $coord_name(&self) -> &T {
            &self.coords[$coord_index]
        }
    }
}

macro_rules! write_coord_getters {
    (1) => {
        coord_getter!(x, 1, T);
    };
    (2) => {
        write_coord_getters!(1);
        coord_getter!(y, 2, T);
    };
    (3) => {
        write_coord_getters!(2);
        coord_getter!(z, 3, T);
    };
    (4) => {
        write_coord_getters!(3);
        coord_getter!(w, 4, T);
    };
}

It's not quite as tidy as you were attempting, but it still lets you invoke it the way you wanted:

define_point!(Point3, 3);

Notice that I changed $dimension: expr to $dimension: tt. I'm not 100% sure why this is the case but, inside a macro, a variable of type expr cannot match a literal.

Also, I changed the return type to &T instead of T. You could also fix the same problem by making T: Copy instead.

like image 192
Peter Hall Avatar answered Sep 27 '22 18:09

Peter Hall


My question now: Is there any way to tell Rust to expand the macro and use the expanded syntax as my list of arguments in another macro ?

No. Macros are syntactic, not lexical. That is, a macro cannot expand to an arbitrary bundle of tokens. Even if it could, you would need some way to force the compiler to expand the inner macro before the outer one, and you can't do that either.

The closest you can get is to use a "callback" style:

macro_rules! impl_point_accessors {    
    ($type_name: ident, $coord_name: ident, $coord_index: expr) => {
        impl<T> $type_name<T> {
            pub fn $coord_name(&self) -> T {
                panic!("coord {}", $coord_index);
            }
        }
    };

    ($type_name: ident, $coord_name: ident, $coord_index: expr, $($extra_coord_name: ident, $extra_coord_index: expr),+) => {
        impl_point_accessors!($type_name, $coord_name, $coord_index);
        impl_point_accessors!($type_name, $($extra_coord_name, $extra_coord_index), +);
    }
}

macro_rules! dimension_to_coord_pairs {
    (1, then $cb:ident!($($cb_args:tt)*)) => {
        $cb!($($cb_args)* x, 0);
    };
    (2, then $cb:ident!($($cb_args:tt)*)) => {
        $cb!($($cb_args)* x, 0, y, 1);
    };
    (3, then $cb:ident!($($cb_args:tt)*)) => {
        $cb!($($cb_args)* x, 0, y, 1, z, 2);
    };
    (4, then $cb:ident!($($cb_args:tt)*)) => {
        $cb!($($cb_args)* x, 0, y, 1, z, 2, w, 3);
    };
}

struct Point<T>(Vec<T>);

dimension_to_coord_pairs!(2, then impl_point_accessors!(Point,));
like image 20
DK. Avatar answered Sep 27 '22 18:09

DK.