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