1 // ♓🌟 Piscēs ∷ function.js
2 // ====================================================================
4 // Copyright © 2022‐2023 Lady [@ Lady’s Computer].
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/>.
18 defineOwnDataProperty
,
21 getOwnPropertyDescriptor
,
30 * Creates a bound function from the provided function using the
31 * provided this value and arguments list.
33 * ☡ As with `call` and `construct`, the arguments must be passed as
39 * Returns a new function which calls the provided function with its
40 * first argument as the `this` value and the remaining arguments
43 * The `length`, `name`, and prototype of the provided function will
44 * be preserved in the new one. A second argument may be used to
45 * override `length` and `name`.
50 * Returns a new function which calls the provided function with its
51 * first argument as the `this` value and the remaining arguments
54 * The `length`, `name`, and prototype of the provided function will
55 * be preserved in the new one. A second argument may be used to
56 * override `length` and `name`.
58 * ※ This is effectively an alias for `Function::call.bind`.
60 createCallableFunction
,
63 * Returns a constructor which throws whenever it is called but has
64 * the same `.name` and `.prototype` as the provided value.
66 * The `length`, `name`, `prototype`, and prototype of the provided
67 * function will be preserved in the new one. A second argument may
68 * be used to override `length`, `name`, and `prototype`.
70 createIllegalConstructor
,
73 * Returns a constructor which produces a new constructor which wraps
74 * the provided constructor, but returns a proxy of the result using
75 * the provided handler.
77 * The resulting constructor inherits from, and has the same basic
80 * If a base constructor is not provided, `Object` will be used.
82 * If a third argument is provided, it is used as the target for the
83 * provided constructor when it is constructed. This can be used to
84 * prevent leakage of the provided constructor to superclasses
85 * through `new.target`.
87 * The `length` of the provided function will be preserved in the new
88 * one. A fourth argument may be used to override `length` and
91 * ※ `.prototype` will be present, but undefined, on the resulting
92 * constructor. This differs from the behaviour of `Proxy`, for which
93 * `.prototype` is not present at all. It is not presently possible
94 * to create a constructor with no `.prototype` property in
97 createProxyConstructor
,
99 const { prototype: functionPrototype
} = Function
;
103 } = functionPrototype
;
104 const objectConstructor
= Object
;
105 const proxyConstructor
= Proxy
;
108 construct
: reflectConstruct
,
110 const callBind
= reflectApply(functionBind
, functionCall
, [
113 const { revocable
} = Proxy
;
114 const { [ITERATOR
]: arrayIterator
} = Array
.prototype;
116 next
: arrayIteratorNext
,
117 } = getPrototype([][ITERATOR
]());
118 const argumentIterablePrototype
= {
126 call(arrayIterator
, this.args
, []),
131 const { get: wmGet
, set: wmSet
} = WeakMap
.prototype;
132 const wsConstructor
= WeakSet
;
133 const { add
: wsAdd
, has
: wsHas
} = WeakSet
.prototype;
134 const proxyConstructorValuesMap
= new WeakMap();
135 const registerConstructedProxy
= (constructor, proxy
) => {
136 const values
= (() => {
137 const existing
= reflectApply(wmGet
, proxyConstructorValuesMap
, [
143 const result
= new wsConstructor();
144 reflectApply(wmSet
, proxyConstructorValuesMap
, [
151 reflectApply(wsAdd
, values
, [proxy
]);
154 const applyBaseFunction
= ($, base
, lengthDelta
= 0) => {
155 if (base
=== UNDEFINED
) {
156 // No base function was provided to apply.
159 // A base function was provided; apply it.
160 const { length
, name
, prototype } = base
;
161 if (getPrototype($) === functionPrototype
) {
162 setPrototype($, getPrototype(base
));
166 return applyProperties($, {
167 length
: +length
+ lengthDelta
,
173 const applyProperties
= ($, override
) => {
174 if (override
=== UNDEFINED
) {
175 // No properties were provided to apply.
178 // Properties were provided; apply them.
179 const { length
, name
, prototype } = override
;
181 prototype === UNDEFINED
||
182 !getOwnPropertyDescriptor($, "prototype")?.writable
184 // The provided function has no `.prototype`, its prototype is
185 // not writable, or no prototype value was provided.
187 // Do not modify the prototype property of the provided
191 // The provided function is a constructor and a prototype value
194 // Change the prototype property of the provided function to
199 defineOwnDataProperty(
206 return defineOwnProperties($, {
207 length
: defineOwnDataProperty(
210 toLength(length
=== UNDEFINED
? $.length
: length
),
212 name
: defineOwnDataProperty(
215 toFunctionName(name
=== UNDEFINED
? $.name
?? "" : name
),
222 bind
: ($, boundThis
, boundArgs
) =>
226 ...defineOwnDataProperty(
227 objectCreate(argumentIterablePrototype
),
232 createArrowFunction
: ($, propertyOverride
= UNDEFINED
) =>
235 (...$s
) => reflectApply($, UNDEFINED
, $s
),
240 createCallableFunction
: ($, propertyOverride
= UNDEFINED
) =>
244 const iterator
= defineOwnDataProperty(
245 objectCreate(argumentIterablePrototype
),
249 const { value
: thisValue
} = iterator
.next();
250 return reflectApply($, thisValue
, [...iterator
]);
257 createIllegalConstructor
: ($, propertyOverride
= UNDEFINED
) =>
262 throw new TypeError("Illegal constructor");
271 createProxyConstructor
: (
274 newTarget
= UNDEFINED
,
275 propertyOverride
= UNDEFINED
,
277 const constructor = $ === UNDEFINED
279 return new objectConstructor($);
282 const target
= newTarget
=== UNDEFINED
? constructor : newTarget
;
283 const len
= toLength(constructor.length
);
284 if (!(type(handler
) === "object")) {
285 // The provided handler is not an object; this is an error.
287 `Piscēs: Proxy handler must be an object, but got: ${handler}.`,
289 } else if (!isConstructor(constructor)) {
290 // The provided constructor is not a constructor; this is an
293 "Piscēs: Cannot create proxy constructor from nonconstructible value.",
295 } else if (!isConstructor(target
)) {
296 // The provided new target is not a constructor; this is an
299 "Piscēs: New target must be a constructor.",
302 // The arguments are acceptable.
303 const C
= applyProperties(
307 if (new.target
=== UNDEFINED
) {
308 // The constructor was not called with new; this is an
313 } must be called with new.`,
316 // The constructor was called with new; return the
317 // appropriate proxy.
318 const O
= reflectConstruct(
323 const proxy
= new proxyConstructor(O
, handler
);
324 return registerConstructedProxy(C
, proxy
);
330 length
: defineOwnDataProperty(
335 name
: defineOwnDataProperty(
338 `${toFunctionName(constructor.name ?? "")}Proxy`,
340 prototype: setPropertyValues(objectCreate(null), {
351 return defineOwnProperties(C
, {
352 revocable
: setPropertyValues(objectCreate(null), {
355 value
: defineOwnProperties(
357 const O
= reflectConstruct(
362 const proxy
= revocable(O
, handler
);
363 return registerConstructedProxy(C
, proxy
);
366 length
: defineOwnDataProperty(
371 name
: defineOwnDataProperty(
380 [`is${name}`]: setPropertyValues(objectCreate(null), {
383 value
: defineOwnProperty(
385 const values
= reflectApply(
387 proxyConstructorValuesMap
,
390 if (values
=== UNDEFINED
) {
391 // No values have been registered for the current
395 // One or more values has been registered for the
396 // current constructor; return whether the provided
398 return reflectApply(wsHas
, values
, [$]);
402 defineOwnDataProperty(
417 * Calls the provided function with the provided this value and
420 * ☡ This is effectively an alias for `Reflect.apply`—the arguments
421 * must be passed as an arraylike.
423 export const call
= createArrowFunction(Reflect
.apply
, {
428 * Returns whether calling the provided function with no `this` value
429 * or arguments completes normally; that is, does not throw an error.
431 * ☡ This function will throw an error if the provided argument is not
434 export const completesNormally
= ($) => {
435 if (!isCallable($)) {
436 // The provided value is not callable; this is an error.
438 `Piscēs: Cannot determine completion of noncallable value: ${$}`,
441 // The provided value is callable.
443 // Attempt to call the function and return true if this succeeds.
447 // Calling the function did not succeed; return false.
454 * Constructs the provided function with the provided arguments list
457 * ☡ This is effectively an alias for `Reflect.construct`—the
458 * arguments must be passed as an arraylike.
460 export const construct
= createArrowFunction(Reflect
.construct
);
463 * Returns the provided value.
465 * ※ This function can be called as a constructor. When used in an
466 * `extends` clause and called via `super`, it will set the value of
467 * `this` to the provided value, enabling it to be extended with
468 * private class features.
470 export const identity = function ($) {
474 /** Returns whether the provided value is callable. */
475 export const isCallable
= ($) => typeof $ === "function";
477 /** Returns whether the provided value is a constructor. */
478 export const isConstructor
= ($) =>
479 completesNormally(() =>
480 // Try constructing a new object with the provided value as its
481 // `new.target`. This will throw if the provided value is not a
483 construct(function () {}, [], $)
487 * Calls the provided callback with the provided argument if the
488 * provided argument is not nullish; otherwise, returns the provided
489 * argument unmodified.
491 export const maybe
= ($, callback
) => $ == null ? $ : callback($);
494 * Returns whether the provided object inherits from the prototype of
495 * the provided function.
497 export const ordinaryHasInstance
= createCallableFunction(
498 Function
.prototype[Symbol
.hasInstance
],
499 { name
: "ordinaryHasInstance" },