--- /dev/null
+// ♓🌟 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" },
+ ),
+ };
+})();
--- /dev/null
+// ♓🌟 Piscēs ∷ iterable.test.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 {
+ assertEquals,
+ assertStrictEquals,
+ assertThrows,
+ describe,
+ it,
+} from "./dev-deps.js";
+import {
+ arrayIteratorFunction,
+ generatorIteratorFunction,
+ mapIteratorFunction,
+ setIteratorFunction,
+ stringIteratorFunction,
+} from "./iterable.js";
+
+describe("arrayIteratorFunction", () => {
+ it("[[Call]] returns a function", () => {
+ assertStrictEquals(typeof arrayIteratorFunction(), "function");
+ });
+
+ it("[[Call]] returns a value which has a prototype of %FunctionPrototype%", () => {
+ assertStrictEquals(
+ Object.getPrototypeOf(arrayIteratorFunction()),
+ Function.prototype,
+ );
+ });
+
+ describe("()", () => {
+ it("[[Call]] returns a value which inherits from %IteratorPrototype%", () => {
+ const iteratorProto = Object.getPrototypeOf(
+ Object.getPrototypeOf([][Symbol.iterator]),
+ );
+ const iterator = arrayIteratorFunction();
+ assertStrictEquals(
+ iterator([]) instanceof Object.assign(
+ function () {},
+ { prototype: iteratorProto },
+ ),
+ true,
+ );
+ });
+
+ it("[[Call]] returns a value with the provided string tag", () => {
+ const iterator = arrayIteratorFunction(null, "My Iterator");
+ assertStrictEquals(
+ iterator([])[Symbol.toStringTag],
+ "My Iterator",
+ );
+ });
+
+ it("[[Call]] yields the values", () => {
+ const iterator = arrayIteratorFunction();
+ assertEquals(
+ [...iterator(["etaoin", "shrdlu"])],
+ ["etaoin", "shrdlu"],
+ );
+ });
+
+ it("[[Call]] maps the values", () => {
+ const iterator = arrayIteratorFunction(function* ($) {
+ yield $.toUpperCase();
+ });
+ assertEquals(
+ [...iterator(["etaoin", "shrdlu"])],
+ ["ETAOIN", "SHRDLU"],
+ );
+ });
+
+ it("[[Call]] can map to nothing", () => {
+ const iterator = arrayIteratorFunction(function* () {});
+ assertEquals(
+ [...iterator(["etaoin", "shrdlu"])],
+ [],
+ );
+ });
+
+ it("[[Call]] can map to multiple values", () => {
+ const iterator = arrayIteratorFunction(function* ($) {
+ yield* $;
+ });
+ assertEquals(
+ [...iterator(["etaoin", "shrdlu"])],
+ [..."etaoinshrdlu"],
+ );
+ });
+
+ it("[[Call]] throws if not provided with any arguments", () => {
+ const iterator = arrayIteratorFunction();
+ assertThrows(() => {
+ iterator();
+ });
+ });
+
+ it("[[Call]] throws if not provided an arraylike", () => {
+ const iterator = arrayIteratorFunction();
+ assertThrows(() => {
+ iterator(null);
+ });
+ });
+
+ describe("::next", () => {
+ it("[[Call]] throws if there are values and the mapper is not a generator function", () => {
+ const iterator = arrayIteratorFunction(function () {});
+ assertThrows(() => {
+ iterator(["etaoin"]).next();
+ });
+ });
+ });
+ });
+});
+
+describe("generatorIteratorFunction", () => {
+ it("[[Call]] returns a function", () => {
+ assertStrictEquals(typeof generatorIteratorFunction(), "function");
+ });
+
+ it("[[Call]] returns a value which has a prototype of %FunctionPrototype%", () => {
+ assertStrictEquals(
+ Object.getPrototypeOf(generatorIteratorFunction()),
+ Function.prototype,
+ );
+ });
+
+ describe("()", () => {
+ it("[[Call]] returns a value which inherits from %IteratorPrototype%", () => {
+ const iteratorProto = Object.getPrototypeOf(
+ Object.getPrototypeOf([][Symbol.iterator]),
+ );
+ const iterator = generatorIteratorFunction();
+ assertStrictEquals(
+ iterator(function* () {}) instanceof Object.assign(
+ function () {},
+ { prototype: iteratorProto },
+ ),
+ true,
+ );
+ });
+
+ it("[[Call]] returns a value with the provided string tag", () => {
+ const iterator = generatorIteratorFunction(null, "My Iterator");
+ assertStrictEquals(
+ iterator(function* () {})[Symbol.toStringTag],
+ "My Iterator",
+ );
+ });
+
+ it("[[Call]] yields the values", () => {
+ const generator = function* () {
+ yield* ["etaoin", "shrdlu"];
+ };
+ const iterator = generatorIteratorFunction();
+ assertEquals(
+ [...iterator(generator)],
+ ["etaoin", "shrdlu"],
+ );
+ });
+
+ it("[[Call]] maps the values", () => {
+ const generator = function* () {
+ yield* ["etaoin", "shrdlu"];
+ };
+ const iterator = generatorIteratorFunction(function* ($) {
+ yield $.toUpperCase();
+ });
+ assertEquals(
+ [...iterator(generator)],
+ ["ETAOIN", "SHRDLU"],
+ );
+ });
+
+ it("[[Call]] can map to nothing", () => {
+ const generator = function* () {
+ yield* ["etaoin", "shrdlu"];
+ };
+ const iterator = generatorIteratorFunction(function* () {});
+ assertEquals(
+ [...iterator(generator)],
+ [],
+ );
+ });
+
+ it("[[Call]] can map to multiple values", () => {
+ const generator = function* () {
+ yield* ["etaoin", "shrdlu"];
+ };
+ const iterator = generatorIteratorFunction(function* ($) {
+ yield* $;
+ });
+ assertEquals(
+ [...iterator(generator)],
+ [..."etaoinshrdlu"],
+ );
+ });
+
+ it("[[Call]] throws if not provided with any arguments", () => {
+ const iterator = generatorIteratorFunction();
+ assertThrows(() => {
+ iterator();
+ });
+ });
+
+ it("[[Call]] throws if not provided a function", () => {
+ const iterator = generatorIteratorFunction();
+ assertThrows(() => {
+ iterator([]);
+ });
+ });
+
+ describe("::next", () => {
+ it("[[Call]] throws if there are values and the mapper is not a generator function", () => {
+ const generator = function* () {
+ yield "etaoin";
+ };
+ const iterator = generatorIteratorFunction(function () {});
+ assertThrows(() => {
+ iterator(generator).next();
+ });
+ });
+
+ it("[[Call]] throws if not constructed with a generator function", () => {
+ const iterator = generatorIteratorFunction();
+ assertThrows(() => {
+ iterator(Array.prototype[Symbol.iterator].bind([])).next();
+ });
+ });
+ });
+ });
+});
+
+describe("mapIteratorFunction", () => {
+ it("[[Call]] returns a function", () => {
+ assertStrictEquals(typeof mapIteratorFunction(), "function");
+ });
+
+ it("[[Call]] returns a value which has a prototype of %FunctionPrototype%", () => {
+ assertStrictEquals(
+ Object.getPrototypeOf(mapIteratorFunction()),
+ Function.prototype,
+ );
+ });
+
+ describe("()", () => {
+ it("[[Call]] returns a value which inherits from %IteratorPrototype%", () => {
+ const iteratorProto = Object.getPrototypeOf(
+ Object.getPrototypeOf([][Symbol.iterator]),
+ );
+ const iterator = mapIteratorFunction();
+ assertStrictEquals(
+ iterator(new Map()) instanceof Object.assign(
+ function () {},
+ { prototype: iteratorProto },
+ ),
+ true,
+ );
+ });
+
+ it("[[Call]] returns a value with the provided string tag", () => {
+ const iterator = mapIteratorFunction(null, "My Iterator");
+ assertStrictEquals(
+ iterator(new Map())[Symbol.toStringTag],
+ "My Iterator",
+ );
+ });
+
+ it("[[Call]] yields the values", () => {
+ const iterator = mapIteratorFunction();
+ assertEquals(
+ [...iterator(new Map([["etaoin", "shrdlu"]]))],
+ [["etaoin", "shrdlu"]],
+ );
+ });
+
+ it("[[Call]] maps the values", () => {
+ const iterator = mapIteratorFunction(function* ([k, v]) {
+ yield [k.toUpperCase(), v.toUpperCase()];
+ });
+ assertEquals(
+ [...iterator(new Map([["etaoin", "shrdlu"]]))],
+ [["ETAOIN", "SHRDLU"]],
+ );
+ });
+
+ it("[[Call]] can map to nothing", () => {
+ const iterator = mapIteratorFunction(function* () {});
+ assertEquals(
+ [...iterator(new Map([["etaoin", "shrdlu"]]))],
+ [],
+ );
+ });
+
+ it("[[Call]] can map to multiple values", () => {
+ const iterator = mapIteratorFunction(function* ($) {
+ yield* $;
+ });
+ assertEquals(
+ [...iterator(new Map([["etaoin", "shrdlu"]]))],
+ ["etaoin", "shrdlu"],
+ );
+ });
+
+ it("[[Call]] throws if not provided with any arguments", () => {
+ const iterator = mapIteratorFunction();
+ assertThrows(() => {
+ iterator();
+ });
+ });
+
+ it("[[Call]] throws if not provided a map", () => {
+ const iterator = mapIteratorFunction();
+ assertThrows(() => {
+ iterator([]);
+ });
+ });
+
+ describe("::next", () => {
+ it("[[Call]] throws if there are values and the mapper is not a generator function", () => {
+ const iterator = mapIteratorFunction(function () {});
+ assertThrows(() => {
+ iterator(new Map([["etaoin", "shrdlu"]])).next();
+ });
+ });
+ });
+ });
+});
+
+describe("setIteratorFunction", () => {
+ it("[[Call]] returns a function", () => {
+ assertStrictEquals(typeof setIteratorFunction(), "function");
+ });
+
+ it("[[Call]] returns a value which has a prototype of %FunctionPrototype%", () => {
+ assertStrictEquals(
+ Object.getPrototypeOf(setIteratorFunction()),
+ Function.prototype,
+ );
+ });
+
+ describe("()", () => {
+ it("[[Call]] returns a value which inherits from %IteratorPrototype%", () => {
+ const iteratorProto = Object.getPrototypeOf(
+ Object.getPrototypeOf([][Symbol.iterator]),
+ );
+ const iterator = setIteratorFunction();
+ assertStrictEquals(
+ iterator(new Set()) instanceof Object.assign(
+ function () {},
+ { prototype: iteratorProto },
+ ),
+ true,
+ );
+ });
+
+ it("[[Call]] returns a value with the provided string tag", () => {
+ const iterator = setIteratorFunction(null, "My Iterator");
+ assertStrictEquals(
+ iterator(new Set())[Symbol.toStringTag],
+ "My Iterator",
+ );
+ });
+
+ it("[[Call]] yields the values", () => {
+ const iterator = setIteratorFunction();
+ assertEquals(
+ [...iterator(new Set(["etaoin", "shrdlu"]))],
+ ["etaoin", "shrdlu"],
+ );
+ });
+
+ it("[[Call]] maps the values", () => {
+ const iterator = setIteratorFunction(function* ($) {
+ yield $.toUpperCase();
+ });
+ assertEquals(
+ [...iterator(new Set(["etaoin", "shrdlu"]))],
+ ["ETAOIN", "SHRDLU"],
+ );
+ });
+
+ it("[[Call]] can map to nothing", () => {
+ const iterator = setIteratorFunction(function* () {});
+ assertEquals(
+ [...iterator(new Set(["etaoin", "shrdlu"]))],
+ [],
+ );
+ });
+
+ it("[[Call]] can map to multiple values", () => {
+ const iterator = setIteratorFunction(function* ($) {
+ yield* $;
+ });
+ assertEquals(
+ [...iterator(new Set(["etaoin", "shrdlu"]))],
+ [..."etaoinshrdlu"],
+ );
+ });
+
+ it("[[Call]] throws if not provided with any arguments", () => {
+ const iterator = setIteratorFunction();
+ assertThrows(() => {
+ iterator();
+ });
+ });
+
+ it("[[Call]] throws if not provided a set", () => {
+ const iterator = setIteratorFunction();
+ assertThrows(() => {
+ iterator([]);
+ });
+ });
+
+ describe("::next", () => {
+ it("[[Call]] throws if there are values and the mapper is not a generator function", () => {
+ const iterator = setIteratorFunction(function () {});
+ assertThrows(() => {
+ iterator(new Set(["etaoin"])).next();
+ });
+ });
+ });
+ });
+});
+
+describe("stringIteratorFunction", () => {
+ it("[[Call]] returns a function", () => {
+ assertStrictEquals(typeof stringIteratorFunction(), "function");
+ });
+
+ it("[[Call]] returns a value which has a prototype of %FunctionPrototype%", () => {
+ assertStrictEquals(
+ Object.getPrototypeOf(stringIteratorFunction()),
+ Function.prototype,
+ );
+ });
+
+ describe("()", () => {
+ it("[[Call]] returns a value which inherits from %IteratorPrototype%", () => {
+ const iteratorProto = Object.getPrototypeOf(
+ Object.getPrototypeOf([][Symbol.iterator]),
+ );
+ const iterator = stringIteratorFunction();
+ assertStrictEquals(
+ iterator("") instanceof Object.assign(
+ function () {},
+ { prototype: iteratorProto },
+ ),
+ true,
+ );
+ });
+
+ it("[[Call]] returns a value with the provided string tag", () => {
+ const iterator = stringIteratorFunction(null, "My Iterator");
+ assertStrictEquals(
+ iterator("")[Symbol.toStringTag],
+ "My Iterator",
+ );
+ });
+
+ it("[[Call]] yields the values", () => {
+ const iterator = stringIteratorFunction();
+ assertEquals(
+ [...iterator("etaoin👀")],
+ [..."etaoin👀"],
+ );
+ });
+
+ it("[[Call]] maps the values", () => {
+ const iterator = stringIteratorFunction(function* ($) {
+ yield $.toUpperCase();
+ });
+ assertEquals(
+ [...iterator("etaoin👀")],
+ [..."ETAOIN👀"],
+ );
+ });
+
+ it("[[Call]] can map to nothing", () => {
+ const iterator = stringIteratorFunction(function* () {});
+ assertEquals(
+ [...iterator("etaoin👀")],
+ [],
+ );
+ });
+
+ it("[[Call]] can map to multiple values", () => {
+ const iterator = stringIteratorFunction(function* ($) {
+ yield $;
+ yield $;
+ });
+ assertEquals(
+ [...iterator("etaoin👀")],
+ [..."eettaaooiinn👀👀"],
+ );
+ });
+
+ it("[[Call]] throws if not provided with any arguments", () => {
+ const iterator = stringIteratorFunction();
+ assertThrows(() => {
+ iterator();
+ });
+ });
+
+ it("[[Call]] throws if not provided something convertible to a string", () => {
+ const iterator = stringIteratorFunction();
+ assertThrows(() => {
+ iterator({
+ toString() {
+ throw null;
+ },
+ });
+ });
+ });
+
+ describe("::next", () => {
+ it("[[Call]] throws if there are values and the mapper is not a generator function", () => {
+ const iterator = stringIteratorFunction(function () {});
+ assertThrows(() => {
+ iterator("etaoin").next();
+ });
+ });
+ });
+ });
+});
// ♓🌟 Piscēs ∷ mod.js
// ====================================================================
//
-// Copyright © 2020–2022 Lady [@ Lady’s Computer].
+// Copyright © 2020–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
export * from "./binary.js";
export * from "./collection.js";
export * from "./function.js";
+export * from "./iterable.js";
export * from "./iri.js";
export * from "./numeric.js";
export * from "./object.js";
// file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
import { bind, call, identity, makeCallable } from "./function.js";
+import {
+ arrayIteratorFunction,
+ stringIteratorFunction,
+} from "./iterable.js";
import {
defineOwnProperties,
getOwnPropertyDescriptors,
objectCreate,
setPrototype,
} from "./object.js";
-import { ITERATOR, TO_STRING_TAG } from "./value.js";
+import { ITERATOR } from "./value.js";
const RE = RegExp;
const { prototype: rePrototype } = RE;
* representation of the provided value.
*
* Codepoints which are not valid Unicode scalar values are replaced
- * with U+FFFF.
+ * with U+FFFD.
*/
scalarValues,
*/
scalarValueString,
} = (() => {
- const { [ITERATOR]: arrayIterator } = arrayPrototype;
- const arrayIteratorPrototype = Object.getPrototypeOf(
- [][ITERATOR](),
+ const generateCodeUnits = function* (ucsCharacter) {
+ yield getCodeUnit(ucsCharacter, 0);
+ };
+ const generateCodepoints = function* (character) {
+ const { allowSurrogates } = this;
+ const codepoint = getCodepoint(character, 0);
+ yield allowSurrogates || codepoint <= 0xD7FF || codepoint >= 0xE000
+ ? codepoint
+ : 0xFFFD;
+ };
+
+ const codeUnitsIterator = arrayIteratorFunction(
+ generateCodeUnits,
+ "String Code Unit Iterator",
);
- const { next: arrayIteratorNext } = arrayIteratorPrototype;
- const iteratorPrototype = Object.getPrototypeOf(
- arrayIteratorPrototype,
+ const codepointsIterator = stringIteratorFunction(
+ bind(generateCodepoints, { allowSurrogates: true }, []),
+ "String Codepoint Iterator",
);
- const { [ITERATOR]: stringIterator } = stringPrototype;
- const stringIteratorPrototype = Object.getPrototypeOf(
- ""[ITERATOR](),
+ const scalarValuesIterator = stringIteratorFunction(
+ bind(generateCodepoints, { allowSurrogates: false }, []),
+ "String Scalar Value Iterator",
);
- const { next: stringIteratorNext } = stringIteratorPrototype;
-
- /**
- * An iterator object for iterating over code values (either code
- * units or codepoints) in a string.
- *
- * ※ This class is not exposed, although its methods are (through
- * the prototypes of string code value iterator objects).
- */
- const StringCodeValueIterator = class extends identity {
- #allowSurrogates;
- #baseIterator;
-
- /**
- * Constructs a new string code value iterator from the provided
- * base iterator.
- *
- * If the provided base iterator is an array iterator, this is a
- * code unit iterator. If the provided iterator is a string
- * iterator and surrogates are allowed, this is a codepoint
- * iterator. If the provided iterator is a string iterator and
- * surrogates are not allowed, this is a scalar value iterator.
- */
- constructor(baseIterator, allowSurrogates = true) {
- super(objectCreate(stringCodeValueIteratorPrototype));
- this.#allowSurrogates = !!allowSurrogates;
- this.#baseIterator = baseIterator;
- }
-
- /** Provides the next code value in the iterator. */
- next() {
- const baseIterator = this.#baseIterator;
- switch (getPrototype(baseIterator)) {
- case arrayIteratorPrototype: {
- // The base iterator is iterating over U·C·S characters.
- const {
- value: ucsCharacter,
- done,
- } = call(arrayIteratorNext, baseIterator, []);
- return done
- ? { value: undefined, done: true }
- : { value: getCodeUnit(ucsCharacter, 0), done: false };
- }
- case stringIteratorPrototype: {
- // The base iterator is iterating over Unicode characters.
- const {
- value: character,
- done,
- } = call(stringIteratorNext, baseIterator, []);
- if (done) {
- // The base iterator has been exhausted.
- return { value: undefined, done: true };
- } else {
- // The base iterator provided a character; yield the
- // codepoint.
- const codepoint = getCodepoint(character, 0);
- return {
- value: this.#allowSurrogates || codepoint <= 0xD7FF ||
- codepoint >= 0xE000
- ? codepoint
- : 0xFFFD,
- done: false,
- };
- }
- }
- default: {
- // Should not be possible!
- throw new TypeError(
- "Piscēs: Unrecognized base iterator type in %StringCodeValueIterator%.",
- );
- }
- }
- }
- };
-
const {
- next: stringCodeValueIteratorNext,
- } = StringCodeValueIterator.prototype;
- const stringCodeValueIteratorPrototype = objectCreate(
- iteratorPrototype,
- {
- next: {
- configurable: true,
- enumerable: false,
- value: stringCodeValueIteratorNext,
- writable: true,
- },
- [TO_STRING_TAG]: {
- configurable: true,
- enumerable: false,
- value: "String Code Value Iterator",
- writable: false,
- },
- },
- );
+ next: scalarValuesNext,
+ } = getPrototype(scalarValuesIterator(""));
const scalarValueIterablePrototype = {
[ITERATOR]() {
return {
next: bind(
- stringCodeValueIteratorNext,
- new StringCodeValueIterator(
- call(stringIterator, this.source, []),
- false,
- ),
+ scalarValuesNext,
+ scalarValuesIterator(this.source),
[],
),
};
};
return {
- codeUnits: ($) =>
- new StringCodeValueIterator(call(arrayIterator, `${$}`, [])),
- codepoints: ($) =>
- new StringCodeValueIterator(
- call(stringIterator, `${$}`, []),
- true,
- ),
- scalarValues: ($) =>
- new StringCodeValueIterator(
- call(stringIterator, `${$}`, []),
- false,
- ),
+ codeUnits: ($) => codeUnitsIterator(`${$}`),
+ codepoints: ($) => codepointsIterator(`${$}`),
+ scalarValues: ($) => scalarValuesIterator(`${$}`),
scalarValueString: ($) =>
stringFromCodepoints(...objectCreate(
scalarValueIterablePrototype,
assertStrictEquals(typeof codeUnits("").next, "function");
});
- it("[[Call]] returns a string code value iterator", () => {
+ it("[[Call]] returns a string code unit iterator", () => {
assertStrictEquals(
codeUnits("")[Symbol.toStringTag],
- "String Code Value Iterator",
+ "String Code Unit Iterator",
);
});
assertStrictEquals(typeof codepoints("").next, "function");
});
- it("[[Call]] returns a string code value iterator", () => {
+ it("[[Call]] returns a string codepoint iterator", () => {
assertStrictEquals(
codepoints("")[Symbol.toStringTag],
- "String Code Value Iterator",
+ "String Codepoint Iterator",
);
});
assertStrictEquals(typeof scalarValues("").next, "function");
});
- it("[[Call]] returns a string code value iterator", () => {
+ it("[[Call]] returns a string scalar value iterator", () => {
assertStrictEquals(
scalarValues("")[Symbol.toStringTag],
- "String Code Value Iterator",
+ "String Scalar Value Iterator",
);
});