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