What you're trying to do is possible but tricky. Fortunately, I have in the past written a general tool which can be used to do this:
Playground: Rust Playground
#[test]
fn parse_euro() {
cartesian!{
["{}€" "{} Euro" "€{}"]
[{1} {32} {1823} {0} {39} {99999999}]
for_each!($fmt:tt {$n:expr})
=> {
assert_eq!(
Euro::parse(&format!($fmt, $n)).unwrap().1,
Euro::from($n),
);
}
}
}
The way it works is by defining a callback macro (solving the problem of how to replace the fmt
token), and repeatedly calling that macro. In addition to that, it accepts multiple sets of tokens and forms a cartesian product of them (support for this was essential because it cannot be used recursively due to technical limitations), so we can also include amt
in the loop. So at some intermediate point in the expansion, it looks like this:
macro_rules! mac {
($fmt:tt {$n:expr}) => {
assert_eq!(
Euro::parse(&format!($fmt, $n)).unwrap().1,
Euro::from($n),
);
};
}
mac!{ "{}€" {1} }
mac!{ "{}€" {32} }
mac!{ "{}€" {1823} }
// ...
mac!{ "€{}" {39} }
mac!{ "€{}" {99999999} }
Here's the part that defines the callback:
/// Higher-order macro that iterates over a cartesian product.
///
/// Useful for generating impls involving opaque traits of the sort
/// sometimes seen used as bounds in public APIs.
///
/// It takes a number of groups of token trees and a suitable definition
/// for a callback macro, and it calls the macro with one token tree from
/// each group in order.
macro_rules! cartesian {
(
$([$($groups:tt)*])*
for_each!($($mac_match:tt)*)
=> {$($mac_body:tt)*}$(;)*
)
=> {
// (this fixed name gets overwritten on each use. Technically, using a fixed name
// makes this macro not "re-entrant", in the sense that you can't call it in a nested
// fashion... but there is no reason to do so, because the macro has a monolithic design)
macro_rules! __cartesian__user_macro {
($($mac_match)*) => {$($mac_body)*};
}
cartesian__!{ @product::next($([$($groups)*])*) -> (__cartesian__user_macro!()) }
};
}
and this generates all the calls to the callback:
#[doc(hidden)]
macro_rules! cartesian__ {
(@product::next([$($token:tt)+] $($rest:tt)*) -> $cb:tt)
=> { cartesian__!{ @product::unpack([$($token)+] $($rest)*) -> $cb } };
// base case; direct product of no arguments
(@product::next() -> ($mac:ident!($($args:tt)*)))
=> {$mac!{$($args)*}};
// Each direct product in the invocation incurs a fixed number of recursions
// as we replicate the macro. First, we must smash anything we want to replicate
// into a single tt that can be matched without repetitions. Do this to `rest`.
(@product::unpack([$($token:tt)*] $($rest:tt)*) -> $cb:tt)
=> {cartesian__!{ @product::unpack_2([$($token)*] [$($rest)*]) -> $cb }};
// Replicate macro for each token.
(@product::unpack_2([$($token:tt)*] $rest:tt) -> $cb:tt)
=> { $( cartesian__!{ @product::unpack_3($token $rest) -> $cb } )* };
// Expand the unparsed arguments back to normal;
// add the token into the macro call
(@product::unpack_3($token:tt [$($rest:tt)*]) -> ($mac:ident!($($args:tt)*)))
=> {cartesian__!{ @product::next($($rest)*) -> ($mac!($($args)*$token)) }};
}
There are also different approaches to this particular problem. There is the strfmt
crate for doing string formatting on template strings constructed at runtime. You could also try iterating over formatting functions instead of format templates:
#[test]
fn parse_euro() {
let fmts: &[fn(u32) -> String] = &[
|x| format!("{}€", x),
|x| format!("{} Euro", x),
|x| format!("€{}", x),
];
// loop over all test cases
// (all combinations of formats and values)
for fmt in fmts {
for amt in vec![1, 32, 1823, 0, 39, 99999999] {
assert_eq!(
Euro::parse(&fmt(amt)).unwrap().1,
Euro::from(amt),
);
}
}
}