X-Git-Url: https://git.ladys.computer/Pisces/blobdiff_plain/e59e8454940d56a20e22c8eb5154ca542e478a0a..refs/heads/current:/function.js diff --git a/function.js b/function.js index 10a3f89..2f242fe 100644 --- a/function.js +++ b/function.js @@ -7,7 +7,23 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at . -import { ITERATOR, toFunctionName, toLength, type } from "./value.js"; +import { + ITERATOR, + toFunctionName, + toLength, + type, + UNDEFINED, +} from "./value.js"; +import { + defineOwnDataProperty, + defineOwnProperties, + defineOwnProperty, + getOwnPropertyDescriptor, + getPrototype, + objectCreate, + setPropertyValues, + setPrototype, +} from "./object.js"; export const { /** @@ -87,14 +103,6 @@ export const { } = functionPrototype; const objectConstructor = Object; const proxyConstructor = Proxy; - const { - create: objectCreate, - defineProperty: defineOwnProperty, - defineProperties: defineOwnProperties, - getOwnPropertyDescriptor, - getPrototypeOf: getPrototype, - setPrototypeOf: setPrototype, - } = Object; const { apply: reflectApply, construct: reflectConstruct, @@ -120,8 +128,31 @@ export const { }; }, }; + const { get: wmGet, set: wmSet } = WeakMap.prototype; + const wsConstructor = WeakSet; + const { add: wsAdd, has: wsHas } = WeakSet.prototype; + const proxyConstructorValuesMap = new WeakMap(); + const registerConstructedProxy = (constructor, proxy) => { + const values = (() => { + const existing = reflectApply(wmGet, proxyConstructorValuesMap, [ + constructor, + ]); + if (existing) { + return existing; + } else { + const result = new wsConstructor(); + reflectApply(wmSet, proxyConstructorValuesMap, [ + constructor, + result, + ]); + return result; + } + })(); + reflectApply(wsAdd, values, [proxy]); + return proxy; + }; const applyBaseFunction = ($, base, lengthDelta = 0) => { - if (base === undefined) { + if (base === UNDEFINED) { // No base function was provided to apply. return $; } else { @@ -140,15 +171,15 @@ export const { } }; const applyProperties = ($, override) => { - if (override === undefined) { + if (override === UNDEFINED) { // No properties were provided to apply. return $; } else { // Properties were provided; apply them. const { length, name, prototype } = override; if ( - !getOwnPropertyDescriptor($, "prototype")?.writable || - prototype === undefined + prototype === UNDEFINED || + !getOwnPropertyDescriptor($, "prototype")?.writable ) { // The provided function has no `.prototype`, its prototype is // not writable, or no prototype value was provided. @@ -162,17 +193,27 @@ export const { // // Change the prototype property of the provided function to // match. - defineOwnProperty($, "prototype", { value: prototype }); + defineOwnProperty( + $, + "prototype", + defineOwnDataProperty( + objectCreate(null), + "value", + prototype, + ), + ); } return defineOwnProperties($, { - length: { - value: toLength(length === undefined ? $.length : length), - }, - name: { - value: toFunctionName( - name === undefined ? $.name ?? "" : name, - ), - }, + length: defineOwnDataProperty( + objectCreate(null), + "value", + toLength(length === UNDEFINED ? $.length : length), + ), + name: defineOwnDataProperty( + objectCreate(null), + "value", + toFunctionName(name === UNDEFINED ? $.name ?? "" : name), + ), }); } }; @@ -182,26 +223,28 @@ export const { callBind( $, boundThis, - ...objectCreate( - argumentIterablePrototype, - { args: { value: boundArgs } }, + ...defineOwnDataProperty( + objectCreate(argumentIterablePrototype), + "args", + boundArgs, ), ), - createArrowFunction: ($, propertyOverride = undefined) => + createArrowFunction: ($, propertyOverride = UNDEFINED) => applyProperties( applyBaseFunction( - (...$s) => reflectApply($, undefined, $s), + (...$s) => reflectApply($, UNDEFINED, $s), $, ), propertyOverride, ), - createCallableFunction: ($, propertyOverride = undefined) => + createCallableFunction: ($, propertyOverride = UNDEFINED) => applyProperties( applyBaseFunction( (...$s) => { - const iterator = objectCreate( - argumentIterablePrototype, - { args: { value: $s } }, + const iterator = defineOwnDataProperty( + objectCreate(argumentIterablePrototype), + "args", + $s, )[ITERATOR](); const { value: thisValue } = iterator.next(); return reflectApply($, thisValue, [...iterator]); @@ -211,7 +254,7 @@ export const { ), propertyOverride, ), - createIllegalConstructor: ($, propertyOverride = undefined) => + createIllegalConstructor: ($, propertyOverride = UNDEFINED) => defineOwnProperty( applyProperties( applyBaseFunction( @@ -228,82 +271,143 @@ export const { createProxyConstructor: ( handler, $, - newTarget = undefined, - propertyOverride = undefined, + newTarget = UNDEFINED, + propertyOverride = UNDEFINED, ) => { - const constructor = $ === undefined ? objectConstructor : $; - const target = newTarget === undefined ? constructor : newTarget; + const constructor = $ === UNDEFINED + ? function ($) { + return new objectConstructor($); + } + : $; + const target = newTarget === UNDEFINED ? constructor : newTarget; const len = toLength(constructor.length); if (!(type(handler) === "object")) { + // The provided handler is not an object; this is an error. throw new TypeError( `Piscēs: Proxy handler must be an object, but got: ${handler}.`, ); } else if (!isConstructor(constructor)) { + // The provided constructor is not a constructor; this is an + // error. throw new TypeError( "Piscēs: Cannot create proxy constructor from nonconstructible value.", ); } else if (!isConstructor(target)) { + // The provided new target is not a constructor; this is an + // error. throw new TypeError( "Piscēs: New target must be a constructor.", ); } else { - return applyProperties( + // The arguments are acceptable. + const C = applyProperties( defineOwnProperties( setPrototype( - function C(...$s) { - if (new.target === undefined) { + function (...$s) { + if (new.target === UNDEFINED) { + // The constructor was not called with new; this is an + // error. throw new TypeError( `Piscēs: ${ C.name ?? "Proxy" } must be called with new.`, ); } else { + // The constructor was called with new; return the + // appropriate proxy. const O = reflectConstruct( constructor, $s, target, ); - return new proxyConstructor(O, handler); + const proxy = new proxyConstructor(O, handler); + return registerConstructedProxy(C, proxy); } }, proxyConstructor, ), { - length: { value: len }, - name: { - value: `${ - toFunctionName(constructor.name ?? "") - }Proxy`, - }, - prototype: { + length: defineOwnDataProperty( + objectCreate(null), + "value", + len, + ), + name: defineOwnDataProperty( + objectCreate(null), + "value", + `${toFunctionName(constructor.name ?? "")}Proxy`, + ), + prototype: setPropertyValues(objectCreate(null), { configurable: false, enumerable: false, - value: undefined, + value: UNDEFINED, writable: false, - }, - revocable: { - configurable: true, - enumerable: false, - value: defineOwnProperties( - (...$s) => { - const O = reflectConstruct( - constructor, - $s, - target, - ); - return revocable(O, handler); - }, - { - length: { value: len }, - name: { value: "revocable" }, - }, - ), - writable: true, - }, + }), }, ), propertyOverride, ); + const { name } = C; + return defineOwnProperties(C, { + revocable: setPropertyValues(objectCreate(null), { + configurable: true, + enumerable: false, + value: defineOwnProperties( + (...$s) => { + const O = reflectConstruct( + constructor, + $s, + target, + ); + const proxy = revocable(O, handler); + return registerConstructedProxy(C, proxy); + }, + { + length: defineOwnDataProperty( + objectCreate(null), + "value", + len, + ), + name: defineOwnDataProperty( + objectCreate(null), + "value", + "revocable", + ), + }, + ), + writable: true, + }), + [`is${name}`]: setPropertyValues(objectCreate(null), { + configurable: true, + enumerable: false, + value: defineOwnProperty( + ($) => { + const values = reflectApply( + wmGet, + proxyConstructorValuesMap, + [C], + ); + if (values === UNDEFINED) { + // No values have been registered for the current + // constructor. + return false; + } else { + // One or more values has been registered for the + // current constructor; return whether the provided + // argument is one. + return reflectApply(wsHas, values, [$]); + } + }, + "name", + defineOwnDataProperty( + objectCreate(null), + "value", + `is${name}`, + ), + ), + writable: true, + }), + }); } }, }; @@ -379,6 +483,13 @@ export const isConstructor = ($) => construct(function () {}, [], $) ); +/** + * Calls the provided callback with the provided argument if the + * provided argument is not nullish; otherwise, returns the provided + * argument unmodified. + */ +export const maybe = ($, callback) => $ == null ? $ : callback($); + /** * Returns whether the provided object inherits from the prototype of * the provided function.