1 // SPDX-FileCopyrightText: 2022, 2023, 2025 Lady <https://www.ladys.computer/about/#lady>
2 // SPDX-License-Identifier: MPL-2.0
4 * ⁌ ♓🧩 Piscēs ∷ function.js
6 * Copyright © 2022–2023, 2025 Lady [@ Ladys Computer].
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/>.
21 defineOwnDataProperty
,
24 getOwnPropertyDescriptor
,
32 const PISC
ĒS
= "♓🧩 Piscēs";
36 * Creates a bound function from the provided function using the
37 * provided this value and arguments list.
39 * ☡ As with `call´ and `construct´, the arguments must be passed as
45 * Returns a new arrow function function which wraps the provided
46 * function and passes its arguments thru.
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´.
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.
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´.
63 * ※ This is effectively an alias for `Function::call.bind´.
65 createCallableFunction
,
68 * Returns a constructor which throws whenever it is called but has
69 * the same `.length´, `.name´, and `.prototype´ as the provided
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´.
76 createIllegalConstructor
,
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.
83 * The resulting constructor inherits from, and has the same basic
86 * If a base constructor is not provided, `Object´ will be used.
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´.
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
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
103 createProxyConstructor
,
105 const { prototype: functionPrototype
} = Function
;
109 } = functionPrototype
;
110 const objectConstructor
= Object
;
111 const proxyConstructor
= Proxy
;
114 construct: reflectConstruct
,
116 const callBind
= reflectApply(functionBind
, functionCall
, [
119 const { revocable
} = Proxy
;
120 const { [ITERATOR
]: arrayIterator
} = Array
.prototype;
122 next: arrayIteratorNext
,
123 } = getPrototype([][ITERATOR
]());
124 const argumentIterablePrototype
= {
132 reflectApply(arrayIterator
, this.args
, []),
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
, [
147 // There is an existing values set for this constructor.
152 // There is no existing values set for this constructor so one
154 const result
= new wsConstructor();
155 reflectApply(wmSet
, proxyConstructorValuesMap
, [
162 reflectApply(wsAdd
, values
, [proxy
]);
165 const applyBaseFunction
= ($, base
, lengthDelta
= 0) => {
166 if (base
=== UNDEFINED
) {
167 // No base function was provided to apply.
170 // A base function was provided.
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.
179 // Change it to match the base function.
180 setPrototype($, getPrototype(base
));
182 // The provided function already does not have the function
183 // prototype, so its prototype is not changed.
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.
190 // If this does not throw, the provided function is a
191 // constructor. Its `.prototype´ property is set alongside
193 reflectConstruct(function () {}, [], $);
194 overrides
.prototype = base
.prototype;
196 // The provided function is not a constructor.
200 // The provided function does not have a `.prototype´ property,
201 // or the base function did not have one.
204 return applyProperties($, overrides
);
207 const applyProperties
= ($, override
) => {
208 if (override
=== UNDEFINED
) {
209 // No properties were provided to apply.
212 // Properties were provided.
215 const { length
, name
} = override
;
217 !("prototype" in override
)
218 || !getOwnPropertyDescriptor($, "prototype")?.writable
220 // The provided function has no `.prototype´, its prototype is
221 // not writable, or no prototype value was provided.
223 // Do not modify the prototype property of the provided
227 // The provided function has a writable `.prototype´ and a
228 // prototype value was provided.
230 // Change the prototype property of the provided function to
235 defineOwnDataProperty(
242 return defineOwnProperties($, {
243 length: defineOwnDataProperty(
246 toLength(length
=== UNDEFINED
? $.length : length
),
248 name: defineOwnDataProperty(
251 toFunctionName(name
=== UNDEFINED
? $.name
?? "" : name
),
258 bind: ($, boundThis
, boundArgs
) =>
262 ...defineOwnDataProperty(
263 objectCreate(argumentIterablePrototype
),
268 createArrowFunction: ($, propertyOverride
= UNDEFINED
) =>
271 (...$s
) => reflectApply($, UNDEFINED
, $s
),
276 createCallableFunction: ($, propertyOverride
= UNDEFINED
) =>
280 const iterator
= defineOwnDataProperty(
281 objectCreate(argumentIterablePrototype
),
285 const { value: thisValue
} = iterator
.next();
286 return reflectApply($, thisValue
, [...iterator
]);
293 createIllegalConstructor: ($, propertyOverride
= UNDEFINED
) =>
298 throw new TypeError("Illegal constructor");
307 createProxyConstructor: (
310 newTarget
= UNDEFINED
,
311 propertyOverride
= UNDEFINED
,
313 const constructor = $ === UNDEFINED
315 return new objectConstructor($);
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.
323 `${PISCĒS}: Proxy handler must be an object, but got: ${handler}.`,
325 } else if (!isConstructor(constructor)) {
326 // The provided constructor is not a constructor; this is an
329 `${PISCĒS}: Cannot create proxy constructor from nonconstructible value.`,
331 } else if (!isConstructor(target
)) {
332 // The provided new target is not a constructor; this is an
335 `${PISCĒS}: New target must be a constructor.`,
338 // The arguments are acceptable.
339 const C
= applyProperties(
343 if (new.target
=== UNDEFINED
) {
344 // The constructor was not called with new; this is
349 } must be called with new.`,
352 // The constructor was called with new.
354 // Return the appropriate proxy.
355 const O
= reflectConstruct(
360 const proxy
= new proxyConstructor(O
, handler
);
361 return registerConstructedProxy(C
, proxy
);
367 length: defineOwnDataProperty(
372 name: defineOwnDataProperty(
375 `${toFunctionName(constructor.name ?? "")}Proxy`,
377 prototype: setPropertyValues(objectCreate(null), {
388 return defineOwnProperties(C
, {
389 revocable: setPropertyValues(objectCreate(null), {
392 value: defineOwnProperties(
394 const O
= reflectConstruct(
399 const proxy
= revocable(O
, handler
);
400 return registerConstructedProxy(C
, proxy
);
403 length: defineOwnDataProperty(
408 name: defineOwnDataProperty(
417 [`is${name}`]: setPropertyValues(objectCreate(null), {
420 value: defineOwnProperty(
422 const values
= reflectApply(
424 proxyConstructorValuesMap
,
427 if (values
=== UNDEFINED
) {
428 // No values have been registered for the current
432 // One or more values has been registered for the
433 // current constructor.
435 // Return whether the provided argument is one.
436 return reflectApply(wsHas
, values
, [$]);
440 defineOwnDataProperty(
455 * Calls the provided function with the provided this value and
458 * ☡ This is effectively an alias for `Reflect.apply´—the arguments
459 * must be passed as an arraylike.
461 export const call
= createArrowFunction(Reflect
.apply
, {
466 * Returns whether calling the provided function with no this value
467 * or arguments completes normally; that is, does not throw an error.
469 * ☡ This function will throw an error if the provided argument is not
472 export const completesNormally
= ($) => {
473 if (!isCallable($)) {
474 // The provided value is not callable; this is an error.
476 `${PISCĒS}: Cannot determine completion of noncallable value: ${$}.`,
479 // The provided value is callable.
481 // This will throw if calling the function throws.
483 // Otherwise, return true.
487 // Calling the function did not succeed.
496 * Constructs the provided function with the provided arguments list
499 * ☡ This is effectively an alias for `Reflect.construct´—the
500 * arguments must be passed as an arraylike.
502 export const construct
= createArrowFunction(Reflect
.construct
);
505 * Returns the provided value.
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.
512 export const identity = function ($) {
516 /** Returns whether the provided value is callable. */
517 export const isCallable
= ($) => typeof $ === "function";
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
525 // ☡ This will throw if the provided value is not a constructor.
526 construct(function () {}, [], $)
530 * Calls the provided callback with the provided argument if the
531 * provided argument is not nullish; otherwise, returns the provided
532 * argument unmodified.
534 export const maybe
= ($, callback
) => $ == null ? $ : callback($);
537 * Returns whether the provided object inherits from the prototype of
538 * the provided function.
540 export const ordinaryHasInstance
= createCallableFunction(
541 Function
.prototype[Symbol
.hasInstance
],
542 { name: "ordinaryHasInstance" },