Hi folks!
I have a rather interesting macro problem.
I've created a system similar to axum's magic functions, but instead of creating a Context to accept functions, I needed to create the Context! So, two traits later and a bunch of impl blocks, I got it.
For my crate, this is the optional Repr object, which tells how some number should be formatted with: what unit, what metric system, what precision, and with a separator or not. And the user can simply send whatever options he wants, in any order! Just a System, a ("Unit", Sep), a (Sep, System, "Unit"), etc.
And now, I'm trying to generate the test cases. A macro that can take arguments once, and generate all possible combinations of them.
- Types supported:
- I support 3 types of units: &'static str, String, and Cow.
- I support 2 types of precision: enum Precision and i8.
- I support 2 types of separator: enum Separator and bool.
- And, on top of that, single arguments are accepted both directly and within a 1-tuple.
Well, I'm no expert in macros, but it does work for a single argument. How could I make it work for two, three, and four arguments, i.e. the cases that need changing the order of elements (on top of the supported types above)?
fn main() {
macro_rules! base {
($repr:expr => $unit:expr, $sys:expr, $prec:expr, $sep:expr) => {
println!("assert_eq!(Repr{{ {}, {}, {}, {} }}, {})",
stringify!($unit), stringify!($sys), stringify!($prec), stringify!($sep), stringify!($repr)
);
};
}
macro_rules! case {
// (($a:expr, $b:expr) => $($p:expr),*) => {
// base!(($a, $b) => $($p),*);
// base!(($b, $a) => $($p),*);
// };
($a:expr => $($p:expr),*) => {
base!($a => $($p),*);
base!(($a,) => $($p),*);
};
(@unit $a:expr => $($p:expr),*) => {
case!($a => $($p),*);
case!(String::from($a) => $($p),*);
case!(Cow::from($a) => $($p),*);
};
(@prec $a:expr => $($p:expr),*) => {
case!($a => $($p),*);
case!(Precision::from($a) => $($p),*);
};
(@sep $a:expr => $($p:expr),*) => {
case!($a => $($p),*);
case!(Separator::from($a) => $($p),*);
};
}
// --- one argument.
case!(@unit "U" => "U", None, None, None);
case!(System::SI2 => "", Some(System::SI2), None, None);
case!(@prec -5i8 => "", None, Some(Precision::Fixed(-5)), None);
case!(@sep false => "", None, None, Some(Separator::No));
// // --- two arguments.
// case!(("U", System::SI2) => "U", Some(System::SI2), None, None);
// case!(("U", -5) => "U", None, Some(Precision::Fixed(-5)), None);
// case!(("U", Separator::No) => "U", None, None, Some(Separator::No));
// case!((System::SI2, -5) => "", Some(System::SI2), Some(Precision::Fixed(-5)), None);
// case!((System::SI2, Separator::No) => "", Some(System::SI2), None, Some(Separator::No));
// case!((-5, Separator::No) => "", None, Some(Precision::Fixed(-5)), Some(Separator::No));
//
// // --- three arguments.
// case!(("U", System::SI2, -5) => "U", System::SI2, -5, None);
// case!(("U", System::SI2, Separator::No) => "U", System::SI2, None, Separator::No);
// case!((System::SI2, -5, Separator::No) => "", System::SI2, -5, Separator::No);
//
// // --- four arguments.
// case!(("U", System::SI2, -5, Separator::No) => "U", System::SI2, -5, Separator::No);
}
It prints this, i.e. 16 cases for only 4 case!() calls:
assert_eq!(Repr{ "U", None, None, None }, "U")
assert_eq!(Repr{ "U", None, None, None }, ("U",))
assert_eq!(Repr{ "U", None, None, None }, String::from("U"))
assert_eq!(Repr{ "U", None, None, None }, (String::from("U"),))
assert_eq!(Repr{ "U", None, None, None }, Cow::from("U"))
assert_eq!(Repr{ "U", None, None, None }, (Cow::from("U"),))
assert_eq!(Repr{ "", Some(System::SI2), None, None }, System::SI2)
assert_eq!(Repr{ "", Some(System::SI2), None, None }, (System::SI2,))
assert_eq!(Repr{ "", None, Some(Precision::Fixed(-5)), None }, -5i8)
assert_eq!(Repr{ "", None, Some(Precision::Fixed(-5)), None }, (-5i8,))
assert_eq!(Repr{ "", None, Some(Precision::Fixed(-5)), None }, Precision::from(-5i8))
assert_eq!(Repr{ "", None, Some(Precision::Fixed(-5)), None }, (Precision::from(-5i8),))
assert_eq!(Repr{ "", None, None, Some(Separator::No) }, false)
assert_eq!(Repr{ "", None, None, Some(Separator::No) }, (false,))
assert_eq!(Repr{ "", None, None, Some(Separator::No) }, Separator::from(false))
assert_eq!(Repr{ "", None, None, Some(Separator::No) }, (Separator::from(false),))
The case!
macro, even though it appears to have one entry point and some helpers, does in fact have four entry points. So, when I uncomment the two-tuple case, even though it does generate two reversed order cases, I don't know how to include the supported types...
Thank you very much.