TL;DR
I'm trying to write a macro that will do the following transformation:
magic_formatter!(["_{}", "{}_", "_{}_"], "foo") ==
[format!("_{}", "foo"),
format!("{}_", "foo"),
format!("_{}_", "foo")]
(a solution that will give ["_foo", "foo_", "_foo_"]
and works for varargs is also welcome)
Full story:
I'm writing a parser, and many of it's tests do stuff like this:
let ident = identifier().parse("foo ").unwrap();
assert_eq!(ident, Syntax::ident("foo"));
let ident = identifier().parse(" foo").unwrap();
assert_eq!(ident, Syntax::ident("foo"));
let ident = identifier().parse(" foo ").unwrap();
assert_eq!(ident, Syntax::ident("foo"));
so I tried to reduce repetition by doing this:
for f in [" {}", "{} ", " {} "] {
let inp = format!(f, "foo");
let ident = identifier().parse(inp).unwrap();
assert_eq!(ident, Syntax::ident("foo"));
}
which of course doesn't compile.
However, it seems to me that there isn't really any unknown information preventing from the whole array to be generated at compile time, so I searched the webz, hoping that this has been solved somewhere already, but my google-fu can't seem to find anything that just does what I want.
So I thought I'd get my hands dirty and write an actually useful rust macro for the first time(!).
I read the macro chapter of Rust by Example, and failed for a while. Then I tried reading the actual reference which I feel that got me a few steps further but I still couldn't get it right. Then I really got into it and found this cool explanation and thought that I actually had it this time, but I still can't seem to get my macro to work properly and compile at the same time.
my latest attempt looks is this:
macro_rules! map_fmt {
(@accum () -> $($body:tt),*) => { map_fmt!(@as_expr [$($body),*]) };
(@accum ([$f:literal, $($fs:literal),*], $args:tt) -> $($body:tt),*) => {
map_fmt!(@accum ([$($fs),*], $args) -> (format!($f, $args) $($body),*))
};
(@as_expr $e:expr) => { $e };
([$f:literal, $($fs:literal),*], $args:expr) => {
map_fmt!(@accum ([$f, $($fs),*], $args) -> ())
};
}
I'll appreciate if someone could help me understand what is my macro missing? and how to fix it, if even possible? and if not is there some other technique I could/should use to reduce the repetition in my tests?
Edit:
this is the final solution I'm using, which is the correct answer provided by @finomnis, which I slightly modified to support variadic arguments in the format!
expression
macro_rules! map_fmt {
(@accum ([$f:literal], $($args:tt),*) -> ($($body:tt)*)) => { [$($body)* format!($f, $($args),*)] };
(@accum ([$f:literal, $($fs:literal),*], $($args:tt),*) -> ($($body:tt)*)) => {
map_fmt!(@accum ([$($fs),*], $($args),*) -> ($($body)* format!($f, $($args),*),))
};
([$f:literal, $($fs:literal),*], $($args:expr),*) => {
map_fmt!(@accum ([$f, $($fs),*], $($args),*) -> ())
};
}