Last active
October 25, 2022 09:49
-
-
Save platy/5e1cf28f688407ea54ccfb5fadec77d4 to your computer and use it in GitHub Desktop.
Rust array pattern permutation generator 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
/// 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