]> Lady’s Gitweb - Pisces/blob - function.js
Make minor improvements to function.js
[Pisces] / function.js
1 // SPDX-FileCopyrightText: 2022, 2023, 2025 Lady <https://www.ladys.computer/about/#lady>
2 // SPDX-License-Identifier: MPL-2.0
3 /**
4 * ⁌ ♓🧩 Piscēs ∷ function.js
5 *
6 * Copyright © 2022–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 {
14 ITERATOR,
15 toFunctionName,
16 toLength,
17 type,
18 UNDEFINED,
19 } from "./value.js";
20 import {
21 defineOwnDataProperty,
22 defineOwnProperties,
23 defineOwnProperty,
24 getOwnPropertyDescriptor,
25 getPrototype,
26 hasOwnProperty,
27 objectCreate,
28 setPropertyValues,
29 setPrototype,
30 } from "./object.js";
31
32 const PISCĒS = "♓🧩 Piscēs";
33
34 export const {
35 /**
36 * Creates a bound function from the provided function using the
37 * provided this value and arguments list.
38 *
39 * ☡ As with `call´ and `construct´, the arguments must be passed as
40 * an array.
41 */
42 bind,
43
44 /**
45 * Returns a new arrow function function which wraps the provided
46 * function and passes its arguments thru.
47 *
48 * The `length´, `name´, and prototype of the provided function will
49 * be preserved in the new one. A second argument may be used to
50 * override `length´ and `name´.
51 */
52 createArrowFunction,
53
54 /**
55 * Returns a new arrow function which wraps the provided function,
56 * using its first argument as the this value when calling the
57 * provided function and passing the remainder thru.
58 *
59 * The `length´, `name´, and prototype of the provided function will
60 * be preserved in the new one. A second argument may be used to
61 * override `length´ and `name´.
62 *
63 * ※ This is effectively an alias for `Function::call.bind´.
64 */
65 createCallableFunction,
66
67 /**
68 * Returns a constructor which throws whenever it is called but has
69 * the same `.length´, `.name´, and `.prototype´ as the provided
70 * value.
71 *
72 * The `length´, `name´, `prototype´, and prototype of the provided
73 * function will be preserved in the new one. A second argument may
74 * be used to override `length´, `name´, and `prototype´.
75 */
76 createIllegalConstructor,
77
78 /**
79 * Returns a constructor which produces a new constructor that wraps
80 * the provided constructor, but returns a proxy of the result using
81 * the provided handler.
82 *
83 * The resulting constructor inherits from, and has the same basic
84 * shape as, `Proxy´.
85 *
86 * If a base constructor is not provided, `Object´ will be used.
87 *
88 * If a third argument is provided, it is used as the target for the
89 * provided constructor when it is constructed. This can be used to
90 * prevent leakage of the provided constructor to superclasses
91 * through `new.target´.
92 *
93 * The `length´ of the provided function will be preserved in the new
94 * one. A fourth argument may be used to override `length´ and
95 * `name´.
96 *
97 * ※ `.prototype´ will be present, but undefined, on the resulting
98 * constructor. This differs from the behaviour of `Proxy´, for which
99 * `.prototype´ is not present at all. It is not presently possible
100 * to create a constructor with no `.prototype´ property in
101 * Ecmascript code.
102 */
103 createProxyConstructor,
104 } = (() => {
105 const { prototype: functionPrototype } = Function;
106 const {
107 bind: functionBind,
108 call: functionCall,
109 } = functionPrototype;
110 const objectConstructor = Object;
111 const proxyConstructor = Proxy;
112 const {
113 apply: reflectApply,
114 construct: reflectConstruct,
115 } = Reflect;
116 const callBind = reflectApply(functionBind, functionCall, [
117 functionBind,
118 ]);
119 const { revocable } = Proxy;
120 const { [ITERATOR]: arrayIterator } = Array.prototype;
121 const {
122 next: arrayIteratorNext,
123 } = getPrototype([][ITERATOR]());
124 const argumentIterablePrototype = {
125 [ITERATOR]() {
126 return {
127 [ITERATOR]() {
128 return this;
129 },
130 next: callBind(
131 arrayIteratorNext,
132 reflectApply(arrayIterator, this.args, []),
133 ),
134 };
135 },
136 };
137 const { get: wmGet, set: wmSet } = WeakMap.prototype;
138 const wsConstructor = WeakSet;
139 const { add: wsAdd, has: wsHas } = WeakSet.prototype;
140 const proxyConstructorValuesMap = new WeakMap();
141 const registerConstructedProxy = (constructor, proxy) => {
142 const values = (() => {
143 const existing = reflectApply(wmGet, proxyConstructorValuesMap, [
144 constructor,
145 ]);
146 if (existing) {
147 // There is an existing values set for this constructor.
148 //
149 // It is returned.
150 return existing;
151 } else {
152 // There is no existing values set for this constructor so one
153 // must be created.
154 const result = new wsConstructor();
155 reflectApply(wmSet, proxyConstructorValuesMap, [
156 constructor,
157 result,
158 ]);
159 return result;
160 }
161 })();
162 reflectApply(wsAdd, values, [proxy]);
163 return proxy;
164 };
165 const applyBaseFunction = ($, base, lengthDelta = 0) => {
166 if (base === UNDEFINED) {
167 // No base function was provided to apply.
168 return $;
169 } else {
170 // A base function was provided.
171 //
172 // Apply it.
173 const overrides = objectCreate(null);
174 overrides.length = +base.length + lengthDelta;
175 overrides.name = base.name;
176 if (getPrototype($) === functionPrototype) {
177 // The provided function has the function prototype.
178 //
179 // Change it to match the base function.
180 setPrototype($, getPrototype(base));
181 } else {
182 // The provided function already does not have the function
183 // prototype, so its prototype is not changed.
184 /* do nothing */
185 }
186 if (hasOwnProperty($, "prototype") && "prototype" in base) {
187 // The provided function has a `.prototype´ property, and one
188 // was provided in the base function as well.
189 try {
190 // If this does not throw, the provided function is a
191 // constructor. Its `.prototype´ property is set alongside
192 // the others.
193 reflectConstruct(function () {}, [], $);
194 overrides.prototype = base.prototype;
195 } catch {
196 // The provided function is not a constructor.
197 /* do nothing */
198 }
199 } else {
200 // The provided function does not have a `.prototype´ property,
201 // or the base function did not have one.
202 /* do nothing */
203 }
204 return applyProperties($, overrides);
205 }
206 };
207 const applyProperties = ($, override) => {
208 if (override === UNDEFINED) {
209 // No properties were provided to apply.
210 return $;
211 } else {
212 // Properties were provided.
213 //
214 // Apply them.
215 const { length, name } = override;
216 if (
217 !("prototype" in override)
218 || !getOwnPropertyDescriptor($, "prototype")?.writable
219 ) {
220 // The provided function has no `.prototype´, its prototype is
221 // not writable, or no prototype value was provided.
222 //
223 // Do not modify the prototype property of the provided
224 // function.
225 /* do nothing */
226 } else {
227 // The provided function has a writable `.prototype´ and a
228 // prototype value was provided.
229 //
230 // Change the prototype property of the provided function to
231 // match.
232 defineOwnProperty(
233 $,
234 "prototype",
235 defineOwnDataProperty(
236 objectCreate(null),
237 "value",
238 override.prototype,
239 ),
240 );
241 }
242 return defineOwnProperties($, {
243 length: defineOwnDataProperty(
244 objectCreate(null),
245 "value",
246 toLength(length === UNDEFINED ? $.length : length),
247 ),
248 name: defineOwnDataProperty(
249 objectCreate(null),
250 "value",
251 toFunctionName(name === UNDEFINED ? $.name ?? "" : name),
252 ),
253 });
254 }
255 };
256
257 return {
258 bind: ($, boundThis, boundArgs) =>
259 callBind(
260 $,
261 boundThis,
262 ...defineOwnDataProperty(
263 objectCreate(argumentIterablePrototype),
264 "args",
265 boundArgs,
266 ),
267 ),
268 createArrowFunction: ($, propertyOverride = UNDEFINED) =>
269 applyProperties(
270 applyBaseFunction(
271 (...$s) => reflectApply($, UNDEFINED, $s),
272 $,
273 ),
274 propertyOverride,
275 ),
276 createCallableFunction: ($, propertyOverride = UNDEFINED) =>
277 applyProperties(
278 applyBaseFunction(
279 (...$s) => {
280 const iterator = defineOwnDataProperty(
281 objectCreate(argumentIterablePrototype),
282 "args",
283 $s,
284 )[ITERATOR]();
285 const { value: thisValue } = iterator.next();
286 return reflectApply($, thisValue, [...iterator]);
287 },
288 $,
289 1,
290 ),
291 propertyOverride,
292 ),
293 createIllegalConstructor: ($, propertyOverride = UNDEFINED) =>
294 defineOwnProperty(
295 applyProperties(
296 applyBaseFunction(
297 function () {
298 throw new TypeError("Illegal constructor");
299 },
300 $,
301 ),
302 propertyOverride,
303 ),
304 "prototype",
305 { writable: false },
306 ),
307 createProxyConstructor: (
308 handler,
309 $,
310 newTarget = UNDEFINED,
311 propertyOverride = UNDEFINED,
312 ) => {
313 const constructor = $ === UNDEFINED
314 ? function ($) {
315 return new objectConstructor($);
316 }
317 : $;
318 const target = newTarget === UNDEFINED ? constructor : newTarget;
319 const len = toLength(constructor.length);
320 if (!(type(handler) === "object")) {
321 // The provided handler is not an object; this is an error.
322 throw new TypeError(
323 `${PISCĒS}: Proxy handler must be an object, but got: ${handler}.`,
324 );
325 } else if (!isConstructor(constructor)) {
326 // The provided constructor is not a constructor; this is an
327 // error.
328 throw new TypeError(
329 `${PISCĒS}: Cannot create proxy constructor from nonconstructible value.`,
330 );
331 } else if (!isConstructor(target)) {
332 // The provided new target is not a constructor; this is an
333 // error.
334 throw new TypeError(
335 `${PISCĒS}: New target must be a constructor.`,
336 );
337 } else {
338 // The arguments are acceptable.
339 const C = applyProperties(
340 defineOwnProperties(
341 setPrototype(
342 function (...$s) {
343 if (new.target === UNDEFINED) {
344 // The constructor was not called with new; this is
345 // an error.
346 throw new TypeError(
347 `${PISCĒS}: ${
348 C.name ?? "Proxy"
349 } must be called with new.`,
350 );
351 } else {
352 // The constructor was called with new.
353 //
354 // Return the appropriate proxy.
355 const O = reflectConstruct(
356 constructor,
357 $s,
358 target,
359 );
360 const proxy = new proxyConstructor(O, handler);
361 return registerConstructedProxy(C, proxy);
362 }
363 },
364 proxyConstructor,
365 ),
366 {
367 length: defineOwnDataProperty(
368 objectCreate(null),
369 "value",
370 len,
371 ),
372 name: defineOwnDataProperty(
373 objectCreate(null),
374 "value",
375 `${toFunctionName(constructor.name ?? "")}Proxy`,
376 ),
377 prototype: setPropertyValues(objectCreate(null), {
378 configurable: false,
379 enumerable: false,
380 value: UNDEFINED,
381 writable: false,
382 }),
383 },
384 ),
385 propertyOverride,
386 );
387 const { name } = C;
388 return defineOwnProperties(C, {
389 revocable: setPropertyValues(objectCreate(null), {
390 configurable: true,
391 enumerable: false,
392 value: defineOwnProperties(
393 (...$s) => {
394 const O = reflectConstruct(
395 constructor,
396 $s,
397 target,
398 );
399 const proxy = revocable(O, handler);
400 return registerConstructedProxy(C, proxy);
401 },
402 {
403 length: defineOwnDataProperty(
404 objectCreate(null),
405 "value",
406 len,
407 ),
408 name: defineOwnDataProperty(
409 objectCreate(null),
410 "value",
411 "revocable",
412 ),
413 },
414 ),
415 writable: true,
416 }),
417 [`is${name}`]: setPropertyValues(objectCreate(null), {
418 configurable: true,
419 enumerable: false,
420 value: defineOwnProperty(
421 ($) => {
422 const values = reflectApply(
423 wmGet,
424 proxyConstructorValuesMap,
425 [C],
426 );
427 if (values === UNDEFINED) {
428 // No values have been registered for the current
429 // constructor.
430 return false;
431 } else {
432 // One or more values has been registered for the
433 // current constructor.
434 //
435 // Return whether the provided argument is one.
436 return reflectApply(wsHas, values, [$]);
437 }
438 },
439 "name",
440 defineOwnDataProperty(
441 objectCreate(null),
442 "value",
443 `is${name}`,
444 ),
445 ),
446 writable: true,
447 }),
448 });
449 }
450 },
451 };
452 })();
453
454 /**
455 * Calls the provided function with the provided this value and
456 * arguments list.
457 *
458 * ☡ This is effectively an alias for `Reflect.apply´—the arguments
459 * must be passed as an arraylike.
460 */
461 export const call = createArrowFunction(Reflect.apply, {
462 name: "call",
463 });
464
465 /**
466 * Returns whether calling the provided function with no this value
467 * or arguments completes normally; that is, does not throw an error.
468 *
469 * ☡ This function will throw an error if the provided argument is not
470 * callable.
471 */
472 export const completesNormally = ($) => {
473 if (!isCallable($)) {
474 // The provided value is not callable; this is an error.
475 throw new TypeError(
476 `${PISCĒS}: Cannot determine completion of noncallable value: ${$}.`,
477 );
478 } else {
479 // The provided value is callable.
480 try {
481 // This will throw if calling the function throws.
482 //
483 // Otherwise, return true.
484 $();
485 return true;
486 } catch {
487 // Calling the function did not succeed.
488 //
489 // Return false.
490 return false;
491 }
492 }
493 };
494
495 /**
496 * Constructs the provided function with the provided arguments list
497 * and new target.
498 *
499 * ☡ This is effectively an alias for `Reflect.construct´—the
500 * arguments must be passed as an arraylike.
501 */
502 export const construct = createArrowFunction(Reflect.construct);
503
504 /**
505 * Returns the provided value.
506 *
507 * ※ This function can be called as a constructor. When used in an
508 * `extends´ clause and called via `super´, it will set the value of
509 * `this´ to the provided value, enabling it to be extended with
510 * private class features.
511 */
512 export const identity = function ($) {
513 return $;
514 };
515
516 /** Returns whether the provided value is callable. */
517 export const isCallable = ($) => typeof $ === "function";
518
519 /** Returns whether the provided value is a constructor. */
520 export const isConstructor = ($) =>
521 completesNormally(() =>
522 // Try constructing a new object using the provided value as
523 // `new.target´.
524 //
525 // ☡ This will throw if the provided value is not a constructor.
526 construct(function () {}, [], $)
527 );
528
529 /**
530 * Calls the provided callback with the provided argument if the
531 * provided argument is not nullish; otherwise, returns the provided
532 * argument unmodified.
533 */
534 export const maybe = ($, callback) => $ == null ? $ : callback($);
535
536 /**
537 * Returns whether the provided object inherits from the prototype of
538 * the provided function.
539 */
540 export const ordinaryHasInstance = createCallableFunction(
541 Function.prototype[Symbol.hasInstance],
542 { name: "ordinaryHasInstance" },
543 );
This page took 0.660757 seconds and 5 git commands to generate.