+// ♓🌟 Piscēs ∷ iterable.js
+// ====================================================================
+//
+// Copyright © 2023 Lady [@ Lady’s Computer].
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
+
+import { bind, call, identity } from "./function.js";
+import {
+ defineOwnProperty,
+ getPrototype,
+ objectCreate,
+} from "./object.js";
+import { ITERATOR, TO_STRING_TAG } from "./value.js";
+
+export const {
+ /**
+ * Returns an iterator function which iterates over the values in its
+ * provided arraylike object and yields them.
+ *
+ * If a first argument is provided, it must be a generator function.
+ * The yielded values are instead those yielded by calling that
+ * function with each value in turn.
+ *
+ * If a second argument is provided, it is used as the string tag for
+ * the resulting iterator.
+ *
+ * The resulting function also takes a second argument, which will be
+ * used as the `this` value when calling the provided generator
+ * function, if provided.
+ *
+ * ※ The returned function is an ordinary nonconstructible (arrow)
+ * function which, when called with an array, returns an iterator.
+ *
+ * ※ The prototypes of iterators returned by a single iterator
+ * function will all be the same.
+ */
+ arrayIteratorFunction,
+
+ /**
+ * Returns an iterator function which iterates over the values
+ * yielded by its provided generator function and yields them.
+ *
+ * If a first argument is provided, it must be a generator function.
+ * The yielded values are instead those yielded by calling that
+ * function with each value in turn.
+ *
+ * If a second argument is provided, it is used as the string tag for
+ * the resulting iterator.
+ *
+ * The resulting function also takes a second argument, which will be
+ * used as the `this` value when calling the provided generator
+ * function, if provided.
+ *
+ * ※ The returned function is an ordinary nonconstructible (arrow)
+ * function which, when called with a generator function, returns an
+ * iterator.
+ *
+ * ☡ There is no way to detect whether the function provided to the
+ * returned function is, in fact, a generator function. Consequently,
+ * if a non‐generator function is provided, it will not throw until
+ * the first attempt at reading a value.
+ *
+ * ※ The prototypes of iterators returned by a single iterator
+ * function will all be the same.
+ */
+ generatorIteratorFunction,
+
+ /**
+ * Returns an iterator function which iterates over the values in its
+ * provided map and yields them.
+ *
+ * If a first argument is provided, it must be a generator function.
+ * The yielded values are instead those yielded by calling that
+ * function with each value in turn.
+ *
+ * If a second argument is provided, it is used as the string tag for
+ * the resulting iterator.
+ *
+ * The resulting function also takes a second argument, which will be
+ * used as the `this` value when calling the provided generator
+ * function, if provided.
+ *
+ * ※ The returned function is an ordinary nonconstructible (arrow)
+ * function which, when called with a map, returns an iterator.
+ *
+ * ※ The prototypes of iterators returned by a single iterator
+ * function will all be the same.
+ */
+ mapIteratorFunction,
+
+ /**
+ * Returns an iterator function which iterates over the values in its
+ * provided set and yields them.
+ *
+ * If a first argument is provided, it must be a generator function.
+ * The yielded values are instead those yielded by calling that
+ * function with each value in turn.
+ *
+ * If a second argument is provided, it is used as the string tag for
+ * the resulting iterator.
+ *
+ * The resulting function also takes a second argument, which will be
+ * used as the `this` value when calling the provided generator
+ * function, if provided.
+ *
+ * ※ The returned function is an ordinary nonconstructible (arrow)
+ * function which, when called with a set, returns an iterator.
+ *
+ * ※ The prototypes of iterators returned by a single iterator
+ * function will all be the same.
+ */
+ setIteratorFunction,
+
+ /**
+ * Returns an iterator function which iterates over the characters in
+ * its provided string and yields them.
+ *
+ * If a first argument is provided, it must be a generator function.
+ * The yielded values are instead those yielded by calling that
+ * function with each value in turn.
+ *
+ * If a second argument is provided, it is used as the string tag for
+ * the resulting iterator.
+ *
+ * The resulting function also takes a second argument, which will be
+ * used as the `this` value when calling the provided generator
+ * function, if provided.
+ *
+ * ※ This iterator function iterates over characters; use
+ * `arrayIteratorFunction` to iterate over code units.
+ *
+ * ※ The returned function is an ordinary nonconstructible (arrow)
+ * function which, when called with a string, returns an iterator.
+ *
+ * ※ The prototypes of iterators returned by a single iterator
+ * function will all be the same.
+ */
+ stringIteratorFunction,
+} = (() => {
+ const { [ITERATOR]: arrayIterator } = Array.prototype;
+ const arrayIteratorPrototype = getPrototype(
+ [][ITERATOR](),
+ );
+ const { next: arrayIteratorNext } = arrayIteratorPrototype;
+ const { [ITERATOR]: stringIterator } = String.prototype;
+ const stringIteratorPrototype = getPrototype(
+ ""[ITERATOR](),
+ );
+ const { next: stringIteratorNext } = stringIteratorPrototype;
+ const { [ITERATOR]: mapIterator } = Map.prototype;
+ const mapIteratorPrototype = getPrototype(
+ new Map()[ITERATOR](),
+ );
+ const { next: mapIteratorNext } = mapIteratorPrototype;
+ const { [ITERATOR]: setIterator } = Set.prototype;
+ const setIteratorPrototype = getPrototype(
+ new Set()[ITERATOR](),
+ );
+ const { next: setIteratorNext } = setIteratorPrototype;
+ const generatorIteratorPrototype = getPrototype(
+ function* () {}.prototype,
+ );
+ const { next: generatorIteratorNext } = generatorIteratorPrototype;
+ const iteratorPrototype = getPrototype(
+ generatorIteratorPrototype,
+ );
+
+ /**
+ * An iterator generated by an iterator function.
+ *
+ * This class provides the internal data structure of all the
+ * iterator functions as well as the `::next` behaviour they all use.
+ *
+ * ※ This class extends the identity function to allow for arbitrary
+ * construction of its superclass instance based on the provided
+ * prototype.
+ *
+ * ※ This class is not exposed.
+ */
+ const Iterator = class extends identity {
+ #baseIterator;
+ #baseIteratorNext;
+ #generateNext;
+ #nextIterator = null;
+
+ /**
+ * Constructs a new iterator with the provided prototype which
+ * wraps the provided base iterator (advance·able using the
+ * provided next method) and yields the result of calling the
+ * provided generator function with each value (or just yields each
+ * value if no generator function is provided).
+ *
+ * ☡ It is not possible to type·check the provided next method or
+ * generator function to ensure that they actually are correct and
+ * appropriately callable; if they aren’t, an error will be thrown
+ * when attempting to yield the first value.
+ */
+ constructor(
+ prototype,
+ baseIterator,
+ baseIteratorNext,
+ generateNext = null,
+ ) {
+ super(objectCreate(prototype));
+ this.#baseIterator = baseIterator;
+ this.#baseIteratorNext = baseIteratorNext;
+ this.#generateNext = generateNext;
+ }
+
+ /**
+ * Returns an object conforming to the requirements of the iterator
+ * protocol, yielding successive iterator values.
+ */
+ next() {
+ const baseIteratorNext = this.#baseIteratorNext;
+ const generateNext = this.#generateNext;
+ while (true) {
+ // This function sometimes needs to repeat its processing steps
+ // in the case that a generator function was provided.
+ //
+ // To avoid potentially large amounts of recursive calls, it is
+ // defined in a loop which will exit the first time a suitable
+ // response is acquired.
+ const baseIterator = this.#baseIterator;
+ const nextIterator = this.#nextIterator;
+ if (baseIterator === null) {
+ // The base iterator has already been exhausted.
+ return { value: undefined, done: true };
+ } else if (nextIterator === null) {
+ // This iterator is not currently yielding values from the
+ // provided generator function, either because it doesn’t
+ // exist or because its values have been exhausted.
+ //
+ // Get the next value in the base iterator and either provide
+ // it to the generator function or yield it, as appropriate.
+ const {
+ value,
+ done,
+ } = call(baseIteratorNext, baseIterator, []);
+ if (done) {
+ // The base iterator is exhausted.
+ //
+ // Free it up from memory and rerun these steps to signal
+ // that the iteration has completed.
+ this.#baseIterator = null;
+ this.#generateNext = null;
+ continue;
+ } else if (generateNext !== null) {
+ // The base iterator has yielded another value and there is
+ // a generator function.
+ //
+ // Initialize a new iterator of result values by calling
+ // the function with the current value, and then rerun
+ // these steps to actually yield a value from it.
+ this.#nextIterator = generateNext(value);
+ continue;
+ } else {
+ // The base iterator has yielded another value and there is
+ // no generator function.
+ //
+ // Just yield the value.
+ return { value, done: false };
+ }
+ } else {
+ // This iterator is currently yielding values from the
+ // provided generator function.
+ const {
+ value,
+ done,
+ } = call(generatorIteratorNext, nextIterator, []);
+ if (done) {
+ // The current iterator of values from the provided
+ // generator function is exhausted.
+ //
+ // Remove it, and rerun these steps to advance to the next
+ // value in the base iterator.
+ this.#nextIterator = null;
+ continue;
+ } else {
+ // The current iterator of values has yielded another
+ // value; reyield it.
+ return { value, done: false };
+ }
+ }
+ }
+ }
+ };
+
+ const {
+ next: iteratorNext,
+ } = Iterator.prototype;
+ const makePrototype = (stringTag) =>
+ objectCreate(
+ iteratorPrototype,
+ {
+ next: {
+ configurable: true,
+ enumerable: false,
+ value: function next() {
+ return call(iteratorNext, this, []);
+ },
+ writable: true,
+ },
+ [TO_STRING_TAG]: {
+ configurable: true,
+ enumerable: false,
+ value: stringTag,
+ writable: false,
+ },
+ },
+ );
+
+ /**
+ * Returns a new function capable of generating iterators using the
+ * provided iterator generation method, iterator advancement method,
+ * optional generator function, and optional string tag.
+ *
+ * ※ The first two arguments to this function are bound to generate
+ * the actual exported iterator function makers.
+ *
+ * ※ This function is not exposed.
+ */
+ const iteratorFunction = (
+ makeBaseIterator,
+ baseIteratorNext,
+ generateNext = null,
+ stringTag = "Iterator",
+ ) => {
+ const prototype = makePrototype(stringTag); // intentionally cached
+ return ($, thisArg = undefined) =>
+ new Iterator(
+ prototype,
+ call(makeBaseIterator, $, []),
+ baseIteratorNext,
+ generateNext === null ? null : bind(generateNext, thisArg, []),
+ );
+ };
+
+ return {
+ arrayIteratorFunction: defineOwnProperty(
+ bind(
+ iteratorFunction,
+ undefined,
+ [arrayIterator, arrayIteratorNext],
+ ),
+ "name",
+ { value: "arrayIteratorFunction" },
+ ),
+ generatorIteratorFunction: defineOwnProperty(
+ bind(
+ iteratorFunction,
+ undefined,
+ [
+ function () {
+ return this();
+ },
+ generatorIteratorNext,
+ ],
+ ),
+ "name",
+ { value: "generatorIteratorFunction" },
+ ),
+ mapIteratorFunction: defineOwnProperty(
+ bind(
+ iteratorFunction,
+ undefined,
+ [mapIterator, mapIteratorNext],
+ ),
+ "name",
+ { value: "mapIteratorFunction" },
+ ),
+ setIteratorFunction: defineOwnProperty(
+ bind(
+ iteratorFunction,
+ undefined,
+ [setIterator, setIteratorNext],
+ ),
+ "name",
+ { value: "setIteratorFunction" },
+ ),
+ stringIteratorFunction: defineOwnProperty(
+ bind(
+ iteratorFunction,
+ undefined,
+ [stringIterator, stringIteratorNext],
+ ),
+ "name",
+ { value: "stringIteratorFunction" },
+ ),
+ };
+})();