Rollup merge of #72310 - jyn514:peekable-next-if, r=dtolnay

Add Peekable::next_if

Prior art:

`rust_analyzer` uses [`Parser::eat`](50f4ae798b/crates/ra_parser/src/parser.rs (L94)), which is `next_if` specialized to `|y| self.next_if(|x| x == y)`.

Basically every other parser I've run into in Rust has an equivalent of `Parser::eat`; see for example

- [cranelift](94190d5724/cranelift/reader/src/parser.rs (L498))
- [rcc](a8159c3904/src/parse/mod.rs (L231))
- [crunch](8521874fab/crates/crunch-parser/src/parser/mod.rs (L213-L241))

Possible extensions: A specialization of `next_if` to using `Eq::eq`. The only difficulty here is the naming - maybe `next_if_eq`?

Alternatives:
- Instead of `func: impl FnOnce(&I::Item) -> bool`, use `func: impl FnOnce(I::Item) -> Option<I::Item>`. This has the advantage that `func` can move the value if necessary, but means that there is no guarantee `func` will return the same value it was given.
- Instead of `fn next_if(...) -> Option<I::Item>`, use `fn next_if(...) -> bool`. This makes the common case of `iter.next_if(f).is_some()` easier, but makes the unusual case impossible.

Bikeshedding on naming:
- `next_if` could be renamed to `consume_if` (to match `eat`, but a little more formally)
- `next_if_eq` could be renamed to `consume`. This is more concise but less self-explanatory if you haven't written a lot of parsers.
- Both of the above, but with `consume` replaced by `eat`.
This commit is contained in:
Dylan DPC 2020-05-29 20:21:11 +02:00 committed by GitHub
commit cbcc4c4f05
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 88 additions and 0 deletions

View file

@ -1619,6 +1619,69 @@ impl<I: Iterator> Peekable<I> {
let iter = &mut self.iter;
self.peeked.get_or_insert_with(|| iter.next()).as_ref()
}
/// Consume the next value of this iterator if a condition is true.
///
/// If `func` returns `true` for the next value of this iterator, consume and return it.
/// Otherwise, return `None`.
///
/// # Examples
/// Consume a number if it's equal to 0.
/// ```
/// #![feature(peekable_next_if)]
/// let mut iter = (0..5).peekable();
/// // The first item of the iterator is 0; consume it.
/// assert_eq!(iter.next_if(|&x| x == 0), Some(0));
/// // The next item returned is now 1, so `consume` will return `false`.
/// assert_eq!(iter.next_if(|&x| x == 0), None);
/// // `next_if` saves the value of the next item if it was not equal to `expected`.
/// assert_eq!(iter.next(), Some(1));
/// ```
///
/// Consume any number less than 10.
/// ```
/// #![feature(peekable_next_if)]
/// let mut iter = (1..20).peekable();
/// // Consume all numbers less than 10
/// while iter.next_if(|&x| x < 10).is_some() {}
/// // The next value returned will be 10
/// assert_eq!(iter.next(), Some(10));
/// ```
#[unstable(feature = "peekable_next_if", issue = "72480")]
pub fn next_if(&mut self, func: impl FnOnce(&I::Item) -> bool) -> Option<I::Item> {
match self.next() {
Some(matched) if func(&matched) => Some(matched),
other => {
// Since we called `self.next()`, we consumed `self.peeked`.
assert!(self.peeked.is_none());
self.peeked = Some(other);
None
}
}
}
/// Consume the next item if it is equal to `expected`.
///
/// # Example
/// Consume a number if it's equal to 0.
/// ```
/// #![feature(peekable_next_if)]
/// let mut iter = (0..5).peekable();
/// // The first item of the iterator is 0; consume it.
/// assert_eq!(iter.next_if_eq(&0), Some(0));
/// // The next item returned is now 1, so `consume` will return `false`.
/// assert_eq!(iter.next_if_eq(&0), None);
/// // `next_if_eq` saves the value of the next item if it was not equal to `expected`.
/// assert_eq!(iter.next(), Some(1));
/// ```
#[unstable(feature = "peekable_next_if", issue = "72480")]
pub fn next_if_eq<R>(&mut self, expected: &R) -> Option<I::Item>
where
R: ?Sized,
I::Item: PartialEq<R>,
{
self.next_if(|next| next == expected)
}
}
/// An iterator that rejects elements while `predicate` returns `true`.

View file

@ -813,6 +813,30 @@ fn test_iterator_peekable_rfold() {
assert_eq!(i, xs.len());
}
#[test]
fn test_iterator_peekable_next_if_eq() {
// first, try on references
let xs = vec!["Heart", "of", "Gold"];
let mut it = xs.into_iter().peekable();
// try before `peek()`
assert_eq!(it.next_if_eq(&"trillian"), None);
assert_eq!(it.next_if_eq(&"Heart"), Some("Heart"));
// try after peek()
assert_eq!(it.peek(), Some(&"of"));
assert_eq!(it.next_if_eq(&"of"), Some("of"));
assert_eq!(it.next_if_eq(&"zaphod"), None);
// make sure `next()` still behaves
assert_eq!(it.next(), Some("Gold"));
// make sure comparison works for owned values
let xs = vec![String::from("Ludicrous"), "speed".into()];
let mut it = xs.into_iter().peekable();
// make sure basic functionality works
assert_eq!(it.next_if_eq("Ludicrous"), Some("Ludicrous".into()));
assert_eq!(it.next_if_eq("speed"), Some("speed".into()));
assert_eq!(it.next_if_eq(""), None);
}
/// This is an iterator that follows the Iterator contract,
/// but it is not fused. After having returned None once, it will start
/// producing elements if .next() is called again.

View file

@ -43,6 +43,7 @@
#![feature(leading_trailing_ones)]
#![feature(const_forget)]
#![feature(option_unwrap_none)]
#![feature(peekable_next_if)]
extern crate test;