summaryrefslogtreecommitdiff
path: root/improved_slice_patterns
diff options
context:
space:
mode:
Diffstat (limited to 'improved_slice_patterns')
-rw-r--r--improved_slice_patterns/Cargo.toml10
-rw-r--r--improved_slice_patterns/README.md23
-rw-r--r--improved_slice_patterns/src/lib.rs302
3 files changed, 335 insertions, 0 deletions
diff --git a/improved_slice_patterns/Cargo.toml b/improved_slice_patterns/Cargo.toml
new file mode 100644
index 0000000..9c99e0d
--- /dev/null
+++ b/improved_slice_patterns/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "improved_slice_patterns"
+version = "1.0.1" # remember to update html_root_url
+authors = ["Nadrieril <nadrieril@users.noreply.github.com>"]
+license = "MIT OR Apache-2.0"
+edition = "2018"
+description = "A tiny crate that provides macros to help matching on Vecs and iterators using the syntax of slice_patterns"
+readme = "README.md"
+repository = "https://github.com/Nadrieril/dhall-rust"
+documentation = "https://docs.rs/improved_slice_patterns/"
diff --git a/improved_slice_patterns/README.md b/improved_slice_patterns/README.md
new file mode 100644
index 0000000..ab8fd7e
--- /dev/null
+++ b/improved_slice_patterns/README.md
@@ -0,0 +1,23 @@
+# `improved_slice_patterns`
+
+A tiny crate that provides two macros to help matching
+on `Vec`s and iterators using the syntax of [`slice_patterns`][slice_patterns]
+
+[slice_patterns]: https://doc.rust-lang.org/nightly/unstable-book/language-features/slice-patterns.html
+
+## License
+
+Licensed under either of
+
+ * Apache License, Version 2.0
+ (http://www.apache.org/licenses/LICENSE-2.0)
+ * MIT license
+ (http://opensource.org/licenses/MIT)
+
+at your option.
+
+## Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
+dual licensed as above, without any additional terms or conditions
diff --git a/improved_slice_patterns/src/lib.rs b/improved_slice_patterns/src/lib.rs
new file mode 100644
index 0000000..3e459bb
--- /dev/null
+++ b/improved_slice_patterns/src/lib.rs
@@ -0,0 +1,302 @@
+#![feature(slice_patterns)]
+#![doc(html_root_url = "https://docs.rs/improved_slice_patterns/1.0.1")]
+
+//! A tiny crate that provides two macros to help matching
+//! on `Vec`s and iterators using the syntax of
+//! [`slice_patterns`][slice_patterns]
+//!
+//! [slice_patterns]: https://doc.rust-lang.org/nightly/unstable-book/language-features/slice-patterns.html
+
+/// Destructure an iterator using the syntax of slice_patterns.
+///
+/// Wraps the match body in `Some` if there was a match; returns
+/// `None` otherwise.
+///
+/// Contrary to slice_patterns, this allows moving out
+/// of the iterator.
+///
+/// A variable length pattern (`x..`) is only allowed as the last
+/// pattern, unless the iterator is double-ended.
+///
+/// Example:
+/// ```edition2018
+/// use improved_slice_patterns::destructure_iter;
+///
+/// let vec = vec![Some(1), Some(2), Some(3), None];
+///
+/// let res = destructure_iter!(vec.into_iter();
+/// [Some(x), y.., z] => {
+/// // x: usize
+/// // y: impl Iterator<Option<usize>>
+/// // z: Option<usize>
+/// (x, y.collect::<Vec<_>>(), z)
+/// }
+/// );
+///
+/// assert_eq!(res, Some((1, vec![Some(2), Some(3)], None)));
+///
+/// # Ok::<(), ()>(())
+/// ```
+///
+///
+#[macro_export]
+macro_rules! destructure_iter {
+ // Variable length pattern
+ (@match_forwards, $iter:expr, ($body:expr), $x:ident.., $($rest:tt)*) => {
+ $crate::destructure_iter!(@match_backwards,
+ $iter,
+ ({
+ let $x = $iter;
+ $body
+ }),
+ $($rest)*
+ )
+ };
+ // Special variable length pattern with a common unary variant
+ (@match_forwards, $iter:expr, ($body:expr),
+ $variant:ident ($x:ident).., $($rest:tt)*) => {
+ $crate::destructure_iter!(@match_backwards,
+ $iter,
+ ({
+ let $x = $iter
+ .map(|x| match x {
+ $variant(y) => y,
+ _ => unreachable!(),
+ });
+ $body
+ }),
+ $($rest)*
+ )
+ };
+ // Variable length pattern without a binder
+ (@match_forwards, $iter:expr, ($body:expr), .., $($rest:tt)*) => {
+ $crate::destructure_iter!(@match_backwards,
+ $iter,
+ ($body),
+ $($rest)*
+ )
+ };
+ // Single item pattern
+ (@match_forwards, $iter:expr, ($body:expr), $x:pat, $($rest:tt)*) => {
+ if let std::option::Option::Some($x) = $iter.next() {
+ $crate::destructure_iter!(@match_forwards,
+ $iter,
+ ($body),
+ $($rest)*
+ )
+ } else {
+ std::option::Option::None
+ }
+ };
+ // Single item pattern after a variable length one: declare reversed and take from the end
+ (@match_backwards, $iter:expr, ($body:expr), $x:pat, $($rest:tt)*) => {
+ $crate::destructure_iter!(@match_backwards, $iter, (
+ if let std::option::Option::Some($x) = $iter.next_back() {
+ $body
+ } else {
+ std::option::Option::None
+ }
+ ), $($rest)*)
+ };
+
+ // Check no elements remain
+ (@match_forwards, $iter:expr, ($body:expr) $(,)*) => {
+ if $iter.next().is_some() {
+ std::option::Option::None
+ } else {
+ $body
+ }
+ };
+ // After a variable length pattern, everything has already been consumed
+ (@match_backwards, $iter:expr, ($body:expr) $(,)*) => {
+ $body
+ };
+
+ ($iter:expr; [$($args:tt)*] => $body:expr) => {
+ {
+ #[allow(unused_mut)]
+ let mut iter = $iter;
+ $crate::destructure_iter!(@match_forwards,
+ iter,
+ (std::option::Option::Some($body)),
+ $($args)*,
+ )
+ }
+ };
+}
+
+/// Pattern-match on a vec using the syntax of slice_patterns.
+///
+/// Wraps the match body in `Some` if there was a match; returns
+/// `None` otherwise.
+///
+/// Contrary to slice_patterns, this allows moving out
+/// of the `Vec`.
+///
+/// A variable length pattern (`x..`) returns an iterator.
+///
+/// Example:
+/// ```edition2018
+/// #![feature(slice_patterns)]
+/// use improved_slice_patterns::match_vec;
+///
+/// let vec = vec![Some(1), Some(2), Some(3), None];
+///
+/// let res = match_vec!(vec;
+/// [Some(_), y.., None] => {
+/// y.collect::<Vec<_>>()
+/// },
+/// [None, None] => {
+/// vec![]
+/// },
+/// [..] => vec![]
+/// );
+///
+/// assert_eq!(res, Some(vec![Some(2), Some(3)]));
+///
+///
+/// let vec = vec![Some(1), Some(2), Some(3), None];
+///
+/// let res = match_vec!(vec;
+/// [Some(_), y.., Some(_)] => {
+/// y.collect::<Vec<_>>()
+/// },
+/// [None, None] => {
+/// vec![]
+/// },
+/// );
+///
+/// assert_eq!(res, None); // there was no match
+///
+/// # Ok::<(), ()>(())
+/// ```
+///
+///
+#[macro_export]
+macro_rules! match_vec {
+ // Variable length pattern
+ (@make_pat; ($($acc:tt)*), $x:ident.., $($rest:tt)*) => {
+ $crate::match_vec!(@make_pat;
+ ($($acc)*, $x..),
+ $($rest)*
+ )
+ };
+ // Special variable length pattern with a common unary variant
+ (@make_pat; ($($acc:tt)*), $variant:ident ($x:ident).., $($rest:tt)*) => {
+ $crate::match_vec!(@make_pat;
+ ($($acc)*, $x..),
+ $($rest)*
+ )
+ };
+ // Variable length pattern without a binder
+ (@make_pat; ($($acc:tt)*), .., $($rest:tt)*) => {
+ $crate::match_vec!(@make_pat;
+ ($($acc)*, ..),
+ $($rest)*
+ )
+ };
+ // Single item pattern
+ (@make_pat; ($($acc:tt)*), $x:pat, $($rest:tt)*) => {
+ $crate::match_vec!(@make_pat;
+ ($($acc)*, $x),
+ $($rest)*
+ )
+ };
+ (@make_pat; (, $($acc:tt)*), $(,)*) => {
+ [$($acc)*]
+ };
+ (@make_pat; ($($acc:tt)*), $(,)*) => {
+ [$($acc)*]
+ };
+
+ (@make_filter; $x:ident.., $($rest:tt)*) => {
+ $crate::match_vec!(@make_filter;
+ $($rest)*
+ )
+ };
+ (@make_filter; $variant:ident ($x:ident).., $($rest:tt)*) => {
+ {
+ // Circumvent https://github.com/rust-lang/rust/issues/59803
+ let is_all_variant = || $x.iter()
+ .all(|x| match x {
+ $variant(_) => true,
+ _ => false,
+ });
+ is_all_variant()
+ }
+ &&
+ $crate::match_vec!(@make_filter;
+ $($rest)*
+ )
+ };
+ (@make_filter; .., $($rest:tt)*) => {
+ $crate::match_vec!(@make_filter;
+ $($rest)*
+ )
+ };
+ (@make_filter; $x:pat, $($rest:tt)*) => {
+ $crate::match_vec!(@make_filter;
+ $($rest)*
+ )
+ };
+ (@make_filter; $(,)*) => {
+ true
+ };
+
+ ($arg:expr; $( [$($args:tt)*] => $body:expr ),* $(,)*) => {
+ {
+ let vec = $arg;
+ // Match as references to decide which branch to take
+ // I think `match_default_bindings` should make this always work but
+ // there may be some patterns this doesn't capture.
+ #[allow(unused_variables, unreachable_patterns)]
+ match vec.as_slice() {
+ $(
+ $crate::match_vec!(@make_pat; (), $($args)*,)
+ if
+ $crate::match_vec!(@make_filter; $($args)*,)
+ => {
+ // Actually consume the values
+ #[allow(unused_mut)]
+ let mut iter = vec.into_iter();
+ $crate::destructure_iter!(iter; [$($args)*] => $body)
+ }
+ )*
+ _ => std::option::Option::None,
+ }
+ }
+ };
+}
+
+#[test]
+fn test() {
+ let test = |v: Vec<Option<isize>>| {
+ match_vec!(v.into_iter();
+ [Some(_x), None, None] => 4,
+ [Some(_x), None] => 2,
+ [None, Some(y)] => 1,
+ [None, _y..] => 3,
+ [_x.., Some(y), Some(z), None] => y - z,
+ [Some(ys)..] => ys.sum(),
+ [] => 0,
+ [..] => -1,
+ )
+ .unwrap()
+ };
+
+ assert_eq!(test(vec![Some(0), None, None]), 4);
+ assert_eq!(test(vec![Some(0), None]), 2);
+ assert_eq!(test(vec![None, Some(0)]), 1);
+ assert_eq!(test(vec![Some(1), Some(2), Some(5), Some(14), None]), -9);
+ assert_eq!(test(vec![Some(1), Some(2), Some(3), Some(4)]), 10);
+ assert_eq!(test(vec![None]), 3);
+ assert_eq!(test(vec![]), 0);
+ assert_eq!(test(vec![Some(0), None, Some(1)]), -1);
+
+ // Test move out of pattern
+ struct Foo;
+ let _: (Foo, Foo) = match_vec!(vec![Some(Foo), Some(Foo)].into_iter();
+ [Some(f1), Some(f2)] => (f1, f2),
+ )
+ .unwrap();
+}