/** * @license * Copyright The Closure Library Authors. * SPDX-License-Identifier: Apache-2.0 */ /** * @fileoverview Utilities for working with ES6 iterables. * * The goal is that this should be a replacement for goog.iter which uses * a now non-standard approach to iterables. * * @see https://goo.gl/Rok5YQ */ goog.module('goog.collections.iters'); goog.module.declareLegacyNamespace(); /** * Get the iterator for an iterable. * @param {!Iterable} iterable * @return {!Iterator} * @template VALUE */ function getIterator(iterable) { return iterable[goog.global.Symbol.iterator](); } exports.getIterator = getIterator; /** * Call a function with every value of an iterable. * * Warning: this function will never halt if given an iterable that * is never exhausted. * * @param {!Iterable} iterable * @param {function(VALUE) : *} f * @template VALUE */ exports.forEach = function(iterable, f) { for (const elem of iterable) { f(elem); } }; /** * An Iterable that wraps a child iterable, and maps every element of the child * iterator to a new value, using a mapping function. Similar to Array.map, but * for Iterable. * @template TO,FROM * @implements {IteratorIterable} */ class MapIterator { /** * @param {!Iterable} childIter * @param {function(FROM, number): TO} mapFn */ constructor(childIter, mapFn) { /** @private @const {!Iterator} */ this.childIterator_ = getIterator(childIter); /** @private @const {function(FROM, number): TO} */ this.mapFn_ = mapFn; /** @private {number} */ this.nextIndex_ = 0; } [Symbol.iterator]() { return this; } /** @override */ next() { const childResult = this.childIterator_.next(); // Always return a new object, even when childResult.done == true. This is // so that we don't accidentally preserve generator return values, which // are unlikely to be meaningful in the context of this MapIterator. return { value: childResult.done ? undefined : this.mapFn_.call(undefined, childResult.value, this.nextIndex_++), done: childResult.done, }; } } /** * Maps the values of one iterable to create another iterable. * * When next() is called on the returned iterable, it will call the given * function `f` with the next value of the given iterable * `iterable` until the given iterable is exhausted. * * @param {!Iterable} iterable * @param {function(VALUE, number): RESULT} f * @return {!IteratorIterable} The created iterable that gives the * mapped values. * @template VALUE, RESULT */ exports.map = function(iterable, f) { return new MapIterator(iterable, f); }; /** * An Iterable that wraps a child Iterable and returns a subset of the child's * items, based on a filter function. Similar to Array.filter, but for * Iterable. * @template T * @implements {IteratorIterable} */ class FilterIterator { /** * @param {!Iterable} childIter * @param {function(T, number): boolean} filterFn */ constructor(childIter, filterFn) { /** @private @const {!Iterator} */ this.childIter_ = getIterator(childIter); /** @private @const {function(T, number): boolean} */ this.filterFn_ = filterFn; /** @private {number} */ this.nextIndex_ = 0; } [Symbol.iterator]() { return this; } /** @override */ next() { while (true) { const childResult = this.childIter_.next(); if (childResult.done) { // Don't return childResult directly, because that would preserve // generator return values, and we want to ignore them. return {done: true, value: undefined}; } const passesFilter = this.filterFn_.call(undefined, childResult.value, this.nextIndex_++); if (passesFilter) { return childResult; } } } } /** * Filter elements from one iterator to create another iterable. * * When next() is called on the returned iterator, it will call next() on the * given iterator and call the given function `f` with that value until `true` * is returned or the given iterator is exhausted. * * @param {!Iterable} iterable * @param {function(VALUE, number): boolean} f * @return {!IteratorIterable} The created iterable that gives the mapped * values. * @template VALUE */ exports.filter = function(iterable, f) { return new FilterIterator(iterable, f); }; /** * @template T * @implements {IteratorIterable} */ class ConcatIterator { /** @param {!Array>} iterators */ constructor(iterators) { /** @private @const {!Array>} */ this.iterators_ = iterators; /** @private {number} */ this.iterIndex_ = 0; } [Symbol.iterator]() { return this; } /** @override */ next() { while (this.iterIndex_ < this.iterators_.length) { const result = this.iterators_[this.iterIndex_].next(); if (!result.done) { return result; } this.iterIndex_++; } return /** @type {!IIterableResult} */ ({done: true}); } } /** * Concatenates multiple iterators to create a new iterable. * * When next() is called on the return iterator, it will call next() on the * current passed iterator. When the current passed iterator is exhausted, it * will move on to the next iterator until there are no more left. * * All generator return values will be ignored (i.e. when childIter.next() * returns {done: true, value: notUndefined} it will be treated as just * {done: true}). * * @param {...!Iterable} iterables * @return {!IteratorIterable} * @template VALUE */ exports.concat = function(...iterables) { return new ConcatIterator(iterables.map(getIterator)); };