]> Lady’s Gitweb - Pisces/blob - iterable.js
Add iterator function builders; use in string.js
[Pisces] / iterable.js
1 // ♓🌟 Piscēs ∷ iterable.js
2 // ====================================================================
3 //
4 // Copyright © 2023 Lady [@ Lady’s Computer].
5 //
6 // This Source Code Form is subject to the terms of the Mozilla Public
7 // License, v. 2.0. If a copy of the MPL was not distributed with this
8 // file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
9
10 import { bind, call, identity } from "./function.js";
11 import {
12 defineOwnProperty,
13 getPrototype,
14 objectCreate,
15 } from "./object.js";
16 import { ITERATOR, TO_STRING_TAG } from "./value.js";
17
18 export const {
19 /**
20 * Returns an iterator function which iterates over the values in its
21 * provided arraylike object and yields them.
22 *
23 * If a first argument is provided, it must be a generator function.
24 * The yielded values are instead those yielded by calling that
25 * function with each value in turn.
26 *
27 * If a second argument is provided, it is used as the string tag for
28 * the resulting iterator.
29 *
30 * The resulting function also takes a second argument, which will be
31 * used as the `this` value when calling the provided generator
32 * function, if provided.
33 *
34 * ※ The returned function is an ordinary nonconstructible (arrow)
35 * function which, when called with an array, returns an iterator.
36 *
37 * ※ The prototypes of iterators returned by a single iterator
38 * function will all be the same.
39 */
40 arrayIteratorFunction,
41
42 /**
43 * Returns an iterator function which iterates over the values
44 * yielded by its provided generator function and yields them.
45 *
46 * If a first argument is provided, it must be a generator function.
47 * The yielded values are instead those yielded by calling that
48 * function with each value in turn.
49 *
50 * If a second argument is provided, it is used as the string tag for
51 * the resulting iterator.
52 *
53 * The resulting function also takes a second argument, which will be
54 * used as the `this` value when calling the provided generator
55 * function, if provided.
56 *
57 * ※ The returned function is an ordinary nonconstructible (arrow)
58 * function which, when called with a generator function, returns an
59 * iterator.
60 *
61 * ☡ There is no way to detect whether the function provided to the
62 * returned function is, in fact, a generator function. Consequently,
63 * if a non‐generator function is provided, it will not throw until
64 * the first attempt at reading a value.
65 *
66 * ※ The prototypes of iterators returned by a single iterator
67 * function will all be the same.
68 */
69 generatorIteratorFunction,
70
71 /**
72 * Returns an iterator function which iterates over the values in its
73 * provided map and yields them.
74 *
75 * If a first argument is provided, it must be a generator function.
76 * The yielded values are instead those yielded by calling that
77 * function with each value in turn.
78 *
79 * If a second argument is provided, it is used as the string tag for
80 * the resulting iterator.
81 *
82 * The resulting function also takes a second argument, which will be
83 * used as the `this` value when calling the provided generator
84 * function, if provided.
85 *
86 * ※ The returned function is an ordinary nonconstructible (arrow)
87 * function which, when called with a map, returns an iterator.
88 *
89 * ※ The prototypes of iterators returned by a single iterator
90 * function will all be the same.
91 */
92 mapIteratorFunction,
93
94 /**
95 * Returns an iterator function which iterates over the values in its
96 * provided set and yields them.
97 *
98 * If a first argument is provided, it must be a generator function.
99 * The yielded values are instead those yielded by calling that
100 * function with each value in turn.
101 *
102 * If a second argument is provided, it is used as the string tag for
103 * the resulting iterator.
104 *
105 * The resulting function also takes a second argument, which will be
106 * used as the `this` value when calling the provided generator
107 * function, if provided.
108 *
109 * ※ The returned function is an ordinary nonconstructible (arrow)
110 * function which, when called with a set, returns an iterator.
111 *
112 * ※ The prototypes of iterators returned by a single iterator
113 * function will all be the same.
114 */
115 setIteratorFunction,
116
117 /**
118 * Returns an iterator function which iterates over the characters in
119 * its provided string and yields them.
120 *
121 * If a first argument is provided, it must be a generator function.
122 * The yielded values are instead those yielded by calling that
123 * function with each value in turn.
124 *
125 * If a second argument is provided, it is used as the string tag for
126 * the resulting iterator.
127 *
128 * The resulting function also takes a second argument, which will be
129 * used as the `this` value when calling the provided generator
130 * function, if provided.
131 *
132 * ※ This iterator function iterates over characters; use
133 * `arrayIteratorFunction` to iterate over code units.
134 *
135 * ※ The returned function is an ordinary nonconstructible (arrow)
136 * function which, when called with a string, returns an iterator.
137 *
138 * ※ The prototypes of iterators returned by a single iterator
139 * function will all be the same.
140 */
141 stringIteratorFunction,
142 } = (() => {
143 const { [ITERATOR]: arrayIterator } = Array.prototype;
144 const arrayIteratorPrototype = getPrototype(
145 [][ITERATOR](),
146 );
147 const { next: arrayIteratorNext } = arrayIteratorPrototype;
148 const { [ITERATOR]: stringIterator } = String.prototype;
149 const stringIteratorPrototype = getPrototype(
150 ""[ITERATOR](),
151 );
152 const { next: stringIteratorNext } = stringIteratorPrototype;
153 const { [ITERATOR]: mapIterator } = Map.prototype;
154 const mapIteratorPrototype = getPrototype(
155 new Map()[ITERATOR](),
156 );
157 const { next: mapIteratorNext } = mapIteratorPrototype;
158 const { [ITERATOR]: setIterator } = Set.prototype;
159 const setIteratorPrototype = getPrototype(
160 new Set()[ITERATOR](),
161 );
162 const { next: setIteratorNext } = setIteratorPrototype;
163 const generatorIteratorPrototype = getPrototype(
164 function* () {}.prototype,
165 );
166 const { next: generatorIteratorNext } = generatorIteratorPrototype;
167 const iteratorPrototype = getPrototype(
168 generatorIteratorPrototype,
169 );
170
171 /**
172 * An iterator generated by an iterator function.
173 *
174 * This class provides the internal data structure of all the
175 * iterator functions as well as the `::next` behaviour they all use.
176 *
177 * ※ This class extends the identity function to allow for arbitrary
178 * construction of its superclass instance based on the provided
179 * prototype.
180 *
181 * ※ This class is not exposed.
182 */
183 const Iterator = class extends identity {
184 #baseIterator;
185 #baseIteratorNext;
186 #generateNext;
187 #nextIterator = null;
188
189 /**
190 * Constructs a new iterator with the provided prototype which
191 * wraps the provided base iterator (advance·able using the
192 * provided next method) and yields the result of calling the
193 * provided generator function with each value (or just yields each
194 * value if no generator function is provided).
195 *
196 * ☡ It is not possible to type·check the provided next method or
197 * generator function to ensure that they actually are correct and
198 * appropriately callable; if they aren’t, an error will be thrown
199 * when attempting to yield the first value.
200 */
201 constructor(
202 prototype,
203 baseIterator,
204 baseIteratorNext,
205 generateNext = null,
206 ) {
207 super(objectCreate(prototype));
208 this.#baseIterator = baseIterator;
209 this.#baseIteratorNext = baseIteratorNext;
210 this.#generateNext = generateNext;
211 }
212
213 /**
214 * Returns an object conforming to the requirements of the iterator
215 * protocol, yielding successive iterator values.
216 */
217 next() {
218 const baseIteratorNext = this.#baseIteratorNext;
219 const generateNext = this.#generateNext;
220 while (true) {
221 // This function sometimes needs to repeat its processing steps
222 // in the case that a generator function was provided.
223 //
224 // To avoid potentially large amounts of recursive calls, it is
225 // defined in a loop which will exit the first time a suitable
226 // response is acquired.
227 const baseIterator = this.#baseIterator;
228 const nextIterator = this.#nextIterator;
229 if (baseIterator === null) {
230 // The base iterator has already been exhausted.
231 return { value: undefined, done: true };
232 } else if (nextIterator === null) {
233 // This iterator is not currently yielding values from the
234 // provided generator function, either because it doesn’t
235 // exist or because its values have been exhausted.
236 //
237 // Get the next value in the base iterator and either provide
238 // it to the generator function or yield it, as appropriate.
239 const {
240 value,
241 done,
242 } = call(baseIteratorNext, baseIterator, []);
243 if (done) {
244 // The base iterator is exhausted.
245 //
246 // Free it up from memory and rerun these steps to signal
247 // that the iteration has completed.
248 this.#baseIterator = null;
249 this.#generateNext = null;
250 continue;
251 } else if (generateNext !== null) {
252 // The base iterator has yielded another value and there is
253 // a generator function.
254 //
255 // Initialize a new iterator of result values by calling
256 // the function with the current value, and then rerun
257 // these steps to actually yield a value from it.
258 this.#nextIterator = generateNext(value);
259 continue;
260 } else {
261 // The base iterator has yielded another value and there is
262 // no generator function.
263 //
264 // Just yield the value.
265 return { value, done: false };
266 }
267 } else {
268 // This iterator is currently yielding values from the
269 // provided generator function.
270 const {
271 value,
272 done,
273 } = call(generatorIteratorNext, nextIterator, []);
274 if (done) {
275 // The current iterator of values from the provided
276 // generator function is exhausted.
277 //
278 // Remove it, and rerun these steps to advance to the next
279 // value in the base iterator.
280 this.#nextIterator = null;
281 continue;
282 } else {
283 // The current iterator of values has yielded another
284 // value; reyield it.
285 return { value, done: false };
286 }
287 }
288 }
289 }
290 };
291
292 const {
293 next: iteratorNext,
294 } = Iterator.prototype;
295 const makePrototype = (stringTag) =>
296 objectCreate(
297 iteratorPrototype,
298 {
299 next: {
300 configurable: true,
301 enumerable: false,
302 value: function next() {
303 return call(iteratorNext, this, []);
304 },
305 writable: true,
306 },
307 [TO_STRING_TAG]: {
308 configurable: true,
309 enumerable: false,
310 value: stringTag,
311 writable: false,
312 },
313 },
314 );
315
316 /**
317 * Returns a new function capable of generating iterators using the
318 * provided iterator generation method, iterator advancement method,
319 * optional generator function, and optional string tag.
320 *
321 * ※ The first two arguments to this function are bound to generate
322 * the actual exported iterator function makers.
323 *
324 * ※ This function is not exposed.
325 */
326 const iteratorFunction = (
327 makeBaseIterator,
328 baseIteratorNext,
329 generateNext = null,
330 stringTag = "Iterator",
331 ) => {
332 const prototype = makePrototype(stringTag); // intentionally cached
333 return ($, thisArg = undefined) =>
334 new Iterator(
335 prototype,
336 call(makeBaseIterator, $, []),
337 baseIteratorNext,
338 generateNext === null ? null : bind(generateNext, thisArg, []),
339 );
340 };
341
342 return {
343 arrayIteratorFunction: defineOwnProperty(
344 bind(
345 iteratorFunction,
346 undefined,
347 [arrayIterator, arrayIteratorNext],
348 ),
349 "name",
350 { value: "arrayIteratorFunction" },
351 ),
352 generatorIteratorFunction: defineOwnProperty(
353 bind(
354 iteratorFunction,
355 undefined,
356 [
357 function () {
358 return this();
359 },
360 generatorIteratorNext,
361 ],
362 ),
363 "name",
364 { value: "generatorIteratorFunction" },
365 ),
366 mapIteratorFunction: defineOwnProperty(
367 bind(
368 iteratorFunction,
369 undefined,
370 [mapIterator, mapIteratorNext],
371 ),
372 "name",
373 { value: "mapIteratorFunction" },
374 ),
375 setIteratorFunction: defineOwnProperty(
376 bind(
377 iteratorFunction,
378 undefined,
379 [setIterator, setIteratorNext],
380 ),
381 "name",
382 { value: "setIteratorFunction" },
383 ),
384 stringIteratorFunction: defineOwnProperty(
385 bind(
386 iteratorFunction,
387 undefined,
388 [stringIterator, stringIteratorNext],
389 ),
390 "name",
391 { value: "stringIteratorFunction" },
392 ),
393 };
394 })();
This page took 0.080163 seconds and 5 git commands to generate.