Skip to content

Instantly share code, notes, and snippets.

@platy
Last active October 25, 2022 09:49
Show Gist options
  • Save platy/5e1cf28f688407ea54ccfb5fadec77d4 to your computer and use it in GitHub Desktop.
Save platy/5e1cf28f688407ea54ccfb5fadec77d4 to your computer and use it in GitHub Desktop.
Rust array pattern permutation generator macro
/// Use for pattern matching on arrays where you don't care about the order of the items.
/// About the simplest case would be where you have 2 options and need exactly one to be `Some`:
/// ```
/// let input = [Some(42), None];
/// if let permute!([Some(n)], [None]) = input {
/// assert_eq!(n, 42);
/// }
/// ```
/// In this case it matches just like `[Some(n), None] | [None, Some(n)]`, but for more pattern groups it could save a lot more code.
/// The parameters are some number of (comma-separated, square-bracket-enclosed) groups of comma-separated patterns, each group should contain patterns which would match the same inputs, as the ordering of these doesn't need to be permuted.
/// `permute!([Some(a), Some(b)], [None])` or permute!([Some(a)], [None, None])` are valid, but `permute!([Some(a), Some(1)], [None])` is not as `Some(1)` doesn't match everything that `Some(a)` matches.
#[allow(unused_macros)]
macro_rules! permute {
// down recursion terminal case, emits an array pattern
(@down[$($pre:tt),*]>) => {
[$($pre),*]
};
// down recursion general case, start an across recursion to handle each pattern group
(@down[$($pre:tt),*]> $([$($pat:pat),+]),+) => {
permute!(@across[$($pre),*]> <> $([$($pat),+]),+)
};
// remove an empty pattern group at the front (empty group could be avoided another way)
(@down[$($pre:pat),*]> [] $(,[$($after:pat),+])*) => {
permute!(@down[$($pre),*]> $([$($after),+]),*)
};
// remove an empty pattern group not at the front (empty group could be avoided another way)
(@down[$($pre:pat),*]> $([$($before:pat),+],)+ [] $(,[$($after:pat),+])*) => {
permute!(@down[$($pre),*]> $([$($before),+]),+ $(,[$($after),+])*)
};
// across recursion general case, for each group, remove one element from the front of the group, and recurse down with that as pre, then recuse across
(@across[$($pre:pat),*]> $([$($descended:pat),+]),* <> [$pat:pat $(, $tail:pat)*] $(,[$($todo:pat),+])+) => {
permute!(@down[$($pre,)* $pat] > $([$($descended),+],)* [$($tail),*] $(, [$($todo),+])*) |
permute!(@across[$($pre),*] > $([$($descended),+],)* [$pat $(, $tail)*] <> $([$($todo),+]),*)
};
// across recusion terminal case, for the last group, remove one element from the front of the group, and recurse down with that as pre
(@across[$($pre:pat),*]> $([$($descended:pat),+]),* <> [$pat:pat $(, $tail:pat)*]) => {
permute!(@down[$($pre,)* $pat] > $([$($descended),+],)* [$($tail),*])
};
($([$($all:pat),+]),*) => {
permute!(@down[]>$([$($all),+]),*)
};
}
#[deny(unreachable_code)]
#[cfg(test)]
mod test {
#[test]
fn test_2bool() {
match [true, false] {
[true, true] => panic!(),
[false, false] => panic!(),
permute!([true], [false]) => {},
}
if let permute!([true], [false]) = [true, false] {
} else {
panic!()
}
return;
}
#[test]
fn test_2opt() {
match [Some(1), None] {
[Some(_), Some(_)] => panic!(),
[None, None] => panic!(),
permute!([Some(1)], [None]) => {},
permute!([Some(_)], [None]) => panic!(),
}
if let permute!([Some(_)], [None]) = [Some(1), None] {
} else {
panic!()
}
return;
}
#[test]
fn test_21opt() {
match [Some(1), Some(2), None] {
[Some(_), Some(_), Some(_)] => panic!(),
[None, None, None] => panic!(),
permute!([Some(a), Some(b)], [None]) => {
let mut both = [a, b];
both.sort();
assert_eq!(both, [1, 2]);
},
permute!([Some(_)], [None, None]) => panic!(),
}
assert!(matches!([Some(1), None, None], permute!([Some(1)], [None, None])));
assert!(matches!([None, Some(1), None], permute!([Some(1)], [None, None])));
assert!(matches!([None, None, Some(1)], permute!([Some(1)], [None, None])));
assert!(matches!([None, Some(1), Some(1)], permute!([Some(1), Some(1)], [None])));
assert!(matches!([Some(1), None, Some(1)], permute!([Some(1), Some(1)], [None])));
assert!(matches!([Some(1), Some(1), None], permute!([Some(1), Some(1)], [None])));
return;
}
#[test]
fn test_3opt() {
assert!(matches!([1, 2, 3], permute!([1], [2], [3])));
assert!(matches!([1, 3, 2], permute!([1], [2], [3])));
assert!(matches!([2, 1, 3], permute!([1], [2], [3])));
assert!(matches!([2, 3, 1], permute!([1], [2], [3])));
assert!(matches!([3, 1, 2], permute!([1], [2], [3])));
assert!(matches!([3, 2, 1], permute!([1], [2], [3])));
match [Some(true), Some(false), None] {
[Some(_), Some(_), Some(_)] => panic!(),
[None, None, None] => panic!(),
permute!([Some(false)], [Some(true)], [None]) => {},
permute!([Some(true), Some(true)], [None]) => panic!(),
permute!([Some(false), Some(false)], [None]) => panic!(),
permute!([Some(_)], [None, None]) => panic!(),
}
return;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment