How to separate last expression in repeated expressions in a Rust macro? - rust

I'd like to have this macro:
macro_rules! macro1 {
($f1:expr, $($f2:expr),+, $f3:expr) => {
$f1(|a| {
$(
$f2(a, |b| {
$f3(a, b);
});
)*
});
}
}
This, however, errors with local ambiguity when calling macro macro1: multiple parsing options: built-in NTs expr ('f3') or expr ('f2').. I can resolve this error if I replace the last , in the macro rule with a ; instead so it clearly separates the repeated f2 and the last f3, but I'd like to keep the comma if possible to resemble a function. I can also swap f2 and f3, but in my code f2 tends to be very short and f3 will be quite long, so I'd like to also keep this order if possible.
Is there another way to write this macro to achieve this effect?

Only with the very powerful but hard to understand TT munching:
macro_rules! macro1 {
($f1:expr, $($f2_and_3:expr),+) => {
macro1! { #transpose $f1, [ ] $($f2_and_3),+ }
};
(#transpose $f1:expr, [ $($f2_parsed:expr,)* ] $f2:expr, $($f2_rest_and_3:expr),+ ) => {
macro1! { #transpose $f1, [ $($f2_parsed,)* $f2, ] $($f2_rest_and_3),+ }
};
(#transpose $f1:expr, [ $($f2:expr,)+ ] $f3:expr ) => {
$f1(|a| {
$(
$f2(a, |b| {
$f3(a, b);
});
)*
});
}
}

Related

How to match a struct instantiation with macro_rules

Since this took me a while to figure out, I may as well share how I fixed it.
I was trying to wrap every item on a struct with some function, in my case Arc::new(Mutex::new(item)) with macro_rules
My initial attempt was many variations on this:
macro_rules! decl_sr {
(
$name:ident {
$( $it:ident : $value:expr) ,*
}
) => {
$name {
$( $it: Arc::new(Mutex::new( $value )) ),*
}
};
}
And the idea was to use it like this:
let mut value = decl_sr!{
StructName {
field_1: Value1::from_function_call(parameter1, parameter2),
// -- snip
field_n: ValueN::from_function_call(parameter1, parameter2),
}
}
So it actually resulted in this:
let mut value = decl_sr!{
StructName {
field_1: Arc::new(Mutex::new(Value1::from_function_call(parameter1, parameter2))),
// -- snip
field_n: Arc::new(Mutex::new(ValueN::from_function_call(parameter1, parameter2))),
}
}
The correct answer was this:
macro_rules! decl_sr {
(
$name:ident {
$( $it:ident : $value:expr, )*
}
) => {
$name {
$( $it: Arc::new(Mutex::new( $value )) ),*
}
};
With the comma (',') inside the repetition pattern.
Otherwise it would throw the classic macro error no rules expected token '}'. -Z macro-backtrace and trace_macros!(true); didn't help at all.
The only thing that tipped me off was this other question, but it wasn't exactly about this.
I may have picked too loose/bad fragment specifiers, feel free to correct me/suggest better options in the comments. Most of the time trying to make this works was thinking they were the problem, not the comma so I was glad it worked at all.

Question on invoking another macro_rules in macro_rules definition

I'm implementing writing TLV packet to somewhat impl std::io::Write.
First I implement WriteBE<T> trait, whose write_be(&mut self, data: T) method can write data with type T to Self. (implementation details omitted)
And I'm trying to use macro_rules! to implement calculation of total packet length in compile time (because most packets have fixed length in my case). macros are as follows:
macro_rules! len_in_expr {
(
self.write_be( $data: expr $(,)? ) $(?)? $(;)*
) => {
std::mem::size_of_val(&$data)
};
(
write_be(self, $data: expr $(,)? ) $(?)? $(;)*
) => {
std::mem::size_of_val(&$data)
};
(
$other: expr
) => {
0
};
}
/// calculate total write size in block
macro_rules! tlv_len_in_block {
({
$( $e: expr );* $(;)?
}) => {
0 $(
+ ( len_in_expr!($e) )
)*
};
}
But when I calculating total length like this:
fn main() {
let y = tlv_len_in_block!({
write_be(self, 0u32,)?;
});
println!("y={}", y);
}
I get a result 0.
If I comment the $other: expr match arm, I get a compile error:
6 | macro_rules! len_in_expr {
| ------------------------ when calling this macro
...
30 | + ( len_in_expr!($e) )
| ^^ no rules expected this token in macro call
...
39 | let y = tlv_len_in_block!({
| _____________-
40 | | write_be(self, 0u32,)?;
41 | | });
| |______- in this macro invocation
What's the problem with my code? And how can I fix it?
Once metavariables inside macro_rules! are captured into some fragment specifier (e.g. expr), they cannot be decomposed anymore. Quoting the reference:
When forwarding a matched fragment to another macro-by-example, matchers in the second macro will see an opaque AST of the fragment type. The second macro can't use literal tokens to match the fragments in the matcher, only a fragment specifier of the same type. The ident, lifetime, and tt fragment types are an exception, and can be matched by literal tokens. The following illustrates this restriction:
macro_rules! foo {
($l:expr) => { bar!($l); }
// ERROR: ^^ no rules expected this token in macro call
}
macro_rules! bar {
(3) => {}
}
foo!(3);
The following illustrates how tokens can be directly matched after matching a tt fragment:
// compiles OK
macro_rules! foo {
($l:tt) => { bar!($l); }
}
macro_rules! bar {
(3) => {}
}
foo!(3);
Once tlv_len_in_block!() captured write_be(self, 0u32,)? inside $e, it cannot be decomposed into write_be(self, $data:expr $(,)? ) and thus, cannot be matched by the second case of the len_in_expr!()` macro, as it should have been.
There are generally two solutions to this problem:
The first is, if possible, decomposing them from the beginning. The problem is that this is not always possible. In this case, for example, I don't see a way for that to work.
The second way is much more complicated and it is using the Push-down Accumulation technique together with tt Munching.
The idea is as follows: instead of parsing the input as whole, we parse each piece one at a time. Then, recursively, we forward the parsed bits and the yet-to-parse bit to ourselves. We also should have a stop condition on an empty input.
Here is how it will look like in your example:
macro_rules! tlv_len_in_block_impl {
// Stop condition - no input left to parse.
(
parsed = [ $($parsed:tt)* ]
rest = [ ]
) => {
$($parsed)*
};
(
parsed = [ $($parsed:tt)* ]
rest = [
self.write_be( $data:expr $(,)? ) $(?)? ;
$($rest:tt)*
]
) => {
tlv_len_in_block_impl!(
parsed = [
$($parsed)*
+ std::mem::size_of_val(&$data)
]
rest = [ $($rest)* ]
)
};
(
parsed = [ $($parsed:tt)* ]
rest = [
write_be(self, $data:expr $(,)? ) $(?)? ;
$($rest:tt)*
]
) => {
tlv_len_in_block_impl!(
parsed = [
$($parsed)*
+ std::mem::size_of_val(&$data)
]
rest = [ $($rest)* ]
)
};
}
/// calculate total write size in block
macro_rules! tlv_len_in_block {
({
$($input:tt)*
}) => {
tlv_len_in_block_impl!(
parsed = [ 0 ]
rest = [ $($input)* ]
)
};
}
(Note that this is not exactly the same as your macro - mine requires a trailing semicolon, while in yours it's optional. It's possible to make it optional here, too, but it will be much more complicated.
Playground.

Why does a macro call inside another macro not expand, but instead I get "no rules expected the token `!`"?

I'm calling a macro within a macro, i.e
macro_rules! foo {
(yes) => {
true
};
() => {
false
};
}
macro_rules! baz {
() => {
[(); 0]
};
($args: tt) => {
$args
};
}
macro_rules! parse_rule {
($rule: tt, $args: tt, $newline: expr) => {
println!("The rule is {}, with args {:?}", $rule, $args);
if $newline {
println!()
}
};
}
macro_rules! bar {
($($rule: tt $([$($args: tt),*])? $($flag: ident)?);+) => {
$(parse_rule!($rule, baz!($([$($args),*])?), foo!($($flag)?)));+
}
}
fn main() {
bar!("hi" yes; "there" ["are", "some", "args"]; "no" yes);
}
The compiler complains about me calling baz within the parse_rule invocation:
error: no rules expected the token `!`
--> src/main.rs:30:33
|
19 | macro_rules! parse_rule {
| ----------------------- when calling this macro
...
30 | $(parse_rule!($rule, baz!($([$($args),*])?), foo!($($flag)?)));+
| ^ no rules expected this token in macro call
...
35 | bar!("hi" yes; "there" ["are", "some", "args"]; "no" yes);
| ---------------------------------------------------------- in this macro invocation
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
Why does it not expand?
Macros are are invoked with their unexpanded arguments. macro_call!(arg) is three token trees, not one:
macro_rules! example {
($($x:tt)+) => {
$(eprintln!(">{}<", stringify!($x));)*
}
}
fn main() {
example!(macro_call!(arg));
}
>macro_call<
>!<
>(arg)<
Your macro only allows for a single token tree ($args: tt). That matches the macro's name, leaving the !. That isn't matched by anything, so you get your error.
You probably want $args: expr.
is there a way to expand it before? I want to parse some of the elements of the array manually
There's no "more eager" expansion, that I know of. It has been suggested in various RFCs (e.g. Eager Macro Expansion — 2320).
I'd suggest reflowing your code such that parse_rule calls foo / bar itself, or baking the logic of foo / bar directly into parse_rule. Internal rules are quite common for this.
See also:
What does an # symbol mean in a Rust declarative macro?

Can I 'enumerate' with Rust's variadic macros?

Essentially I have a macro that looks like:
macro_rules! my_macro {
( $expr:expr; $( $pat:pat ),* ) => {
match $expr {
$(
$pat => $(some-macro-magic-here),
)*
}
}
}
Is there anything that can go into $(some-macro-magic-here), so that
my_macro!(foo; A, B, C)
will expand to
match foo {
A => 2,
B => 4,
C => 6,
}
?
Is there some other way I might be able to get a similar feature that effectively lets me "enumerate" over the sequence of inputs for the macro?
I think I could probably write a recursive macro to get a similar effect, but I'm wondering if there's a more elegant/idiomatic way about it than what I'm thinking of
Because macros aren't allowed to store or manipulate "variables" in any form, this problem becomes very difficult. You could, however, use an iterator to do something to the same effect, by creating an iterator that "enumerates" over the input the way you want it (using std::iter::successors, for example), and simply calling iterator.next().unwrap() in $(some-macro-magic-here).
You cannot create such match statement as Rust do not allow creating match branches via macro, aka this will not work right now:
match val {
my_macro! (A, B, C)
}
However in this case we can "hack it around" by using nested if let blocks and using recursive macro:
macro_rules! my_macro {
($expr:expr; $($pat:pat),*) => {
my_macro!($expr; 2, 2; $($pat),*)
};
($expr:expr; $curr:expr, $step:literal; $pat:pat) => {
if let $pat = $expr {
$curr
} else {
unreachable!()
}
};
($expr:expr; $curr:expr, $step:literal; $pat:pat, $($rest:pat),*) => {
if let $pat = $expr {
$curr
} else {
my_macro! ($expr; $curr+$step, $step; $($rest),*)
}
}
}
Playground
It will generate the nested entries with enough 2 added to create the expected constants. Alternatively you could replace that with multiplication, but it should be optimised out by the compiler anyway.

A variable is still repeating at this depth error in macro

I wrote the simple macro:
macro_rules! my_macro {
{
f($x:expr, $y:expr, $z:expr);
$($c:expr => {
$($m:expr => {
$($s:expr => $b:expr),+
}),+
}),+
} => {{
match ($x, $y, $z) {
$(
($c, $m, $s => $b),
)+
}
}};
}
fn main(){
let c = 0;
let m = 0;
let s = 0;
my_macro! {
f (c, m, s);
cc => {
mm => {
ss => b
}
}
}
}
and it gets compiler errors:
error: variable 'm' is still repeating at this depth
--> project/src/mod.rs
|
39 | ($c, $m, $s => $b),
| ^^^^^^^
I do not completely understand why it did happen.
Why and how to repair that?
In your rule for your macro,
f($x:expr, $y:expr, $z:expr);
$($c:expr => {
$($m:expr => {
$($s:expr => $b:expr),+
}),+
}),+
$x, $y and $z bind a single expr. However, a whole list of $cs are bound. Moreover, associated to each $c is a whole list of $m because $m is still inside the $(...).+ block associated with $c. For each of those $ms, a whole list of $ss is bound.
Now in your output,
{{
match ($x, $y, $z) {
$(
($c, $m, $s => $b),
)+
}
}};
you only unpack one layer of lists with $(...)+. After one layer, $m is still a list of expressions ("repeating"), so it can't be used directly. Remember that $m is really a list of lists (since there's a list of $m for each $c) and $s is really a list of lists of lists.
You're either going to need to restructure the rule so that there's only one $m and one $s for each $c (all within one $(...),+) or you're going to need to restructure the output so that $m and $s get unpacked an appropriate number of times.
After trying the first approach (a single $(...),+ in the input rule), I'd suggest making $c, $m and $s patterns rather than expressions (as in $c: pat instead of $c: expr). This allows them to be used in the left side of a match branch.
macro_rules! my_macro {
{
f($x:expr, $y:expr, $z:expr);
$($c:pat => {
$m:pat => {
$s:pat => $b:expr
}
}),+
} => {
match ($x, $y, $z) {
$(
($c, $m, $s) => $b,
)+
}
};
}
The real solution depends a lot on what exactly you're trying to accomplish.

Resources