How to let a metavar match the innermost repetition in macro expansion?

I want to insert code only when "a" and "b" are both passed in like foo!(a;b). But that code won't exist in foo!(a) and foo!(;b). Is there any way to do it.

Below is how I want to write it. The issue is the nested repetition.

macro_rules! ignore {
    ($to_ignore:ident) => {}
}

macro_rules! foo {
    ($($a:ident)?$(;$b:ident)?) => {
        /* duplicated code */

        $(
            /* code that need $a */
            ignore!($a);
            $(
                ignore!($b);
                /* code that need both $a and $b */
            )?
        )?

        /* duplicated code */

        $(
            ignore!($a);
            /* code that only need $a */
        )?

        /* duplicated code */

        $(
            ignore!($b);
            /* code that only need $b */
        )?

        /* duplicated code */
    };
}

fn func_1(a: i32) {
    foo!(a);
}

fn func_2(b: i32) {
    foo!(;b);
}

fn func_3(a: i32, b: i32) {
    foo!(a;b);
}

I want this part

        $(
            /* code that only need $a */
            ignore!($a);
            $(
                ignore!($b);
                /* code that need both $a and $b */
            )?
        )?

to be expanded like

foo!(;b) becomes nothing cause there's no $a

foo!(a) becomes

            /* code that only need $a */

foo!(a;b) becomes

            /* code that only need $a */
                /* code that need both $a and $b */

but the compiler says

error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth
  --> src/main.rs:12:14
   |
12 |               $(
   |  ______________^
13 | |                 ignore!($b);
14 | |                 /* code that need both $a and $b */
15 | |             )?
   | |_____________^

error: meta-variable `a` repeats 1 time, but `b` repeats 0 times
  --> src/main.rs:9:10
   |
9  |           $(
   |  __________^
10 | |             /* code that only need $a */
11 | |             ignore!($a);
12 | |             $(
...  |
15 | |             )?
16 | |         )?
   | |_________^

error: meta-variable `a` repeats 0 times, but `b` repeats 1 time
  --> src/main.rs:9:10
   |
9  |           $(
   |  __________^
10 | |             /* code that only need $a */
11 | |             ignore!($a);
12 | |             $(
...  |
15 | |             )?
16 | |         )?
   | |_________^

the depth of expansion must match the depth of the capture.

for example, in order to be able expand like this:

$(
    use $a;
    $(
        use $b;
     )?
)?

the meta variable must be captured at the same depth, the macro syntax match arm must be something like (notice how they are nested):

($($a:ident $(; $b:ident)?)?) => {};

but this capture does not match the invcation of the form foo!(;b), so you need a separate matching rule to deal with it:

macro_rules! foo {
	(;$b:ident) => {
		foo!()
	};
	($($a:ident $(;$b:ident)?)?) => {
		/* duplicated code */
		$(
			/* code that need $a */
			ignore!($a);
			$(
				ignore!($b);
				/* code that need both $a and $b */
			)?
		)?
		/* duplicated code */
		$(
			ignore!($a);
			/* code that only need $a */
		)?
	};
}
1 Like

Thanks a lot, I know how to deal with it now. I write another macro bar for the nested part and call it in macro foo.

In real code, writing another macro for nested does'n work due to some hygiene problem.
Copy and paste the code from cargo expand worked.

My real code, learning soft rendering. I want to use macro to save the duplicated viewport_transform code and insert mvp_transform code and code that calculate the normal.

macro_rules! ignore {
    ($to_ignore:ident) => {};
}

macro_rules! viewport_transform {
    ($self:ident,$object:ident,$f:ident$(,$model_transform:ident)?$(;$normal:ident)?) => {
        $(
        let mvp_transform = &($self.camera.get_transform() * $model_transform);
        )?

        $(
        ignore!($normal);
        $(
        let mv_transform = &($self.camera.view.get_transform() * $model_transform);
        )?
        )?

        let viewport_transform = &{
            let translation = Matrix3::from_row(&[
                [1.0,  0.0, 1.0],
                [0.0, -1.0, 1.0],
                [0.0,  0.0, 1.0],
            ]);
            let w = $self.w as f32;
            let h = $self.h as f32;
            let scale = Matrix3::from_row(&[
                [w / 2.0,     0.0, 0.0],
                [    0.0, h / 2.0, 0.0],
                [    0.0,     0.0, 1.0],
            ]);
            scale * translation
        };

        for face in &$object.faces {
            let vertex0_index = face.0;
            let vertex1_index = face.1;
            let vertex2_index = face.2;
            let v0 = &$object.vertices[vertex0_index];
            let v1 = &$object.vertices[vertex1_index];
            let v2 = &$object.vertices[vertex2_index];

            $(
            ignore!($model_transform);
            let v0 = &v0.to_homogenous();
            let v1 = &v1.to_homogenous();
            let v2 = &v2.to_homogenous();
            )?

            $(
            let $normal = {
                $(
                ignore!($model_transform);
                let v0 = $mv_transform * $v0;
                let v1 = $mv_transform * $v1;
                let v2 = $mv_transform * $v2;
                let v0 = &Vector3::from_homogenous(&v0);
                let v1 = &Vector3::from_homogenous(&v1);
                let v2 = &Vector3::from_homogenous(&v2);
                )?
                ($v2-$v0).cross($v1-$v0).normalize()
            };
            )?

            $(
            ignore!($model_transform);
            let v0 = mvp_transform * v0;
            let v1 = mvp_transform * v1;
            let v2 = mvp_transform * v2;
            let v0 = &Vector3::from_homogenous(&v0);
            let v1 = &Vector3::from_homogenous(&v1);
            let v2 = &Vector3::from_homogenous(&v2);
            )?

            let v0 = v0.truncate().to_homogenous();
            let v1 = v1.truncate().to_homogenous();
            let v2 = v2.truncate().to_homogenous();
            let p0: Point2 = (viewport_transform * v0).truncate().into();
            let p1: Point2 = (viewport_transform * v1).truncate().into();
            let p2: Point2 = (viewport_transform * v2).truncate().into();

            $f($self, &Triangle2::new(&p0, &p1, &p2), $(&$normal)?);
        }
    };
}

impl Renderer {
    fn process_object<F>(&mut self, object: &Object, mut f: F)
    where
        F: FnMut(&mut Self, &Triangle2),
    {
        viewport_transform!(self, object, f);
    }
    fn process_object_normal<F>(&mut self, object: &Object, mut f: F)
    where
        F: FnMut(&mut Self, &Triangle2, &Vector3),
    {
        viewport_transform!(self, object, f; normal);
    }

    fn process_model<F>(&mut self, model_transform: &Matrix4, object: &Object, mut f: F)
    where
        F: FnMut(&mut Self, &Triangle2),
    {
        viewport_transform!(self, object, f, model_transform);
    }
    fn process_model_normal<F>(&mut self, model_transform: &Matrix4, object: &Object, mut f: F)
    where
        F: FnMut(&mut Self, &Triangle2, &Vector3),
    {
        viewport_transform!(self, object, f, model_transform; normal);
    }
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.