X-Git-Url: https://git.ladys.computer/Pisces/blobdiff_plain/d741139aff475642fe08978d39722b298cf96802..bf3fe705a9d5f717b3c1794a12726e926ece7ecc:/iterable.js?ds=inline diff --git a/iterable.js b/iterable.js new file mode 100644 index 0000000..d89e35f --- /dev/null +++ b/iterable.js @@ -0,0 +1,394 @@ +// ♓🌟 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 . + +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" }, + ), + }; +})();