Skip to content

Instantly share code, notes, and snippets.

@rust-play
Created October 18, 2019 01:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rust-play/e2781a6d7ac9eeea0c62fa4b6496a3fa to your computer and use it in GitHub Desktop.
Save rust-play/e2781a6d7ac9eeea0c62fa4b6496a3fa to your computer and use it in GitHub Desktop.
Code shared from the Rust Playground
/*
input: [i32 i64][ A B ]
prev_expanded: []
next_expanded: []
input: [i64][ A B ]
prev_expanded: []
next_expanded: [[i32]]
input: [][ A B ]
prev_expanded: []
next_expanded: [[i32][i64]]
input: [ A B ]
prev_expanded: [[i32][i64]]
next_expanded: []
input: [ B ]
prev_expanded: [[i32][i64]]
next_expanded: [[i32 A][i64 A]]
input: [ ]
prev_expanded: [[i32][i64]]
next_expanded: [[i32 A][i64 A][i32 B][i64 B]]
*/
/// 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__!{
step: next_group
input: ($([$($groups)*])*)
expanded: ([])
callback: (__cartesian__user_macro!)
}
};
}
#[doc(hidden)]
macro_rules! cartesian__ {
// are there any argument groups left?
(
step: next_group
input: ([$($token:tt)+] $($rest:tt)*)
expanded: $expanded:tt
callback: $cb:tt
) => { cartesian__!{
step: next_item
input: ( [$($token)+] $($rest)* )
prev_expanded: $expanded
next_expanded: []
callback: $cb
}};
// base case; direct product of no arguments
(
step: next_group
input: ()
expanded: ($($expanded:tt)*)
callback: ($mac:ident!)
) => {
// finish
$mac!{ $($expanded)* }
};
// Each item in each direct product in the invocation incurs a fixed number
// of recursions as we replicate previously expanded inputs. First, we must smash anything we want to replicate
// into a single tt that can be matched without repetitions. Do this to `rest`.
(
step: next_item
input: ( [$this_item:tt $($more_items:tt)*] $($rest:tt)* )
prev_expanded: ( $([ $($prev_expanded:tt)* ])* )
next_expanded: ( $($next_expanded:tt)* )
callback: $callback:tt
) => { cartesian__!{
step: next_item
input: ( [$($more_items)*] [$($rest)*] )
prev_expanded: ( $([ $($prev_expanded)* ])* )
next_expanded: (
$($next_expanded)*
$([ $($prev_expanded)* $this_item ])* // <-- add new things
)
callback: $callback
}};
(
step: next_item
input: ([] $($rest:tt)*)
prev_expanded: $prev_expanded:tt
prev_expanded: $next_expanded:tt
callback: $callback:tt
) => { cartesian__!{
step: next_group
input: ( [$($rest)*] )
expanded: $next_expanded
callback: $callback
}};
}
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]>();
}
#[derive(Debug, PartialEq, Eq)]
struct Euro;
impl Euro {
fn parse(_: &str) -> Result<(&str, Euro), ()> { Ok(("", Euro)) }
fn from(x: u32) -> Self { Euro }
}
#[test]
fn parse_euro() {
cartesian!{
[{"{}€"} {"{} Euro"} {"€{}"}]
[{1} {32} {1823} {0} {39} {99999999}]
for_each!({$f:literal} {$n:expr})
=> {
assert_eq!(
Euro::parse(&format!($f, $n)).unwrap().1,
Euro::from($n),
);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment