Created
September 8, 2019 16:48
-
-
Save ExpHP/8ffa060c24c424d8a9a7086bafab201c to your computer and use it in GitHub Desktop.
cartesian product macro
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// 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. | |
/// | |
/// See the examples module in the source for example usage. | |
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!()) } | |
}; | |
} | |
/// implementation detail, go away | |
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)) }}; | |
} | |
mod examples { | |
trait Trait { } | |
// NOTE: Braces around the alternatives are not strictly necessary | |
// (cartesian simply iterates over token trees), but they tend | |
// to help group tokens and resolve any would-be ambiguities in | |
// the callback's match pattern. | |
cartesian!{ | |
[{i32} {u32}] | |
[{0} {1} {2} {3}] | |
for_each!({$T:ty} {$n:expr}) | |
=> { | |
impl Trait for [$T; $n] { } | |
} | |
} | |
#[test] | |
fn example_works() { | |
fn assert_trait<T:Trait>() {} | |
assert_trait::<[u32; 0]>(); | |
assert_trait::<[i32; 2]>(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment