]> Lady’s Gitweb - Pisces/blob - function.js
Add createProxyConstructor to function.js
[Pisces] / function.js
1 // ♓🌟 Piscēs ∷ function.js
2 // ====================================================================
3 //
4 // Copyright © 2022‐2023 Lady [@ Lady’s Computer].
5 //
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/>.
9
10 import { ITERATOR, toFunctionName, toLength, type } from "./value.js";
11
12 export const {
13 /**
14 * Creates a bound function from the provided function using the
15 * provided this value and arguments list.
16 *
17 * ☡ As with `call` and `construct`, the arguments must be passed as
18 * an array.
19 */
20 bind,
21
22 /**
23 * Returns a new function which calls the provided function with its
24 * first argument as the `this` value and the remaining arguments
25 * passed through.
26 *
27 * The `length`, `name`, and prototype of the provided function will
28 * be preserved in the new one. A second argument may be used to
29 * override `length` and `name`.
30 */
31 createArrowFunction,
32
33 /**
34 * Returns a new function which calls the provided function with its
35 * first argument as the `this` value and the remaining arguments
36 * passed through.
37 *
38 * The `length`, `name`, and prototype of the provided function will
39 * be preserved in the new one. A second argument may be used to
40 * override `length` and `name`.
41 *
42 * ※ This is effectively an alias for `Function::call.bind`.
43 */
44 createCallableFunction,
45
46 /**
47 * Returns a constructor which throws whenever it is called but has
48 * the same `.name` and `.prototype` as the provided value.
49 *
50 * The `length`, `name`, `prototype`, and prototype of the provided
51 * function will be preserved in the new one. A second argument may
52 * be used to override `length`, `name`, and `prototype`.
53 */
54 createIllegalConstructor,
55
56 /**
57 * Returns a constructor which produces a new constructor which wraps
58 * the provided constructor, but returns a proxy of the result using
59 * the provided handler.
60 *
61 * The resulting constructor inherits from, and has the same basic
62 * shape as, `Proxy`.
63 *
64 * If a base constructor is not provided, `Object` will be used.
65 *
66 * If a third argument is provided, it is used as the target for the
67 * provided constructor when it is constructed. This can be used to
68 * prevent leakage of the provided constructor to superclasses
69 * through `new.target`.
70 *
71 * The `length` of the provided function will be preserved in the new
72 * one. A fourth argument may be used to override `length` and
73 * `name`.
74 *
75 * ※ `.prototype` will be present, but undefined, on the resulting
76 * constructor. This differs from the behaviour of `Proxy`, for which
77 * `.prototype` is not present at all. It is not presently possible
78 * to create a constructor with no `.prototype` property in
79 * Ecmascript code.
80 */
81 createProxyConstructor,
82 } = (() => {
83 const { prototype: functionPrototype } = Function;
84 const {
85 bind: functionBind,
86 call: functionCall,
87 } = functionPrototype;
88 const objectConstructor = Object;
89 const proxyConstructor = Proxy;
90 const {
91 create: objectCreate,
92 defineProperty: defineOwnProperty,
93 defineProperties: defineOwnProperties,
94 getOwnPropertyDescriptor,
95 getPrototypeOf: getPrototype,
96 setPrototypeOf: setPrototype,
97 } = Object;
98 const {
99 apply: reflectApply,
100 construct: reflectConstruct,
101 } = Reflect;
102 const callBind = reflectApply(functionBind, functionCall, [
103 functionBind,
104 ]);
105 const { revocable } = Proxy;
106 const { [ITERATOR]: arrayIterator } = Array.prototype;
107 const {
108 next: arrayIteratorNext,
109 } = getPrototype([][ITERATOR]());
110 const argumentIterablePrototype = {
111 [ITERATOR]() {
112 return {
113 [ITERATOR]() {
114 return this;
115 },
116 next: callBind(
117 arrayIteratorNext,
118 call(arrayIterator, this.args, []),
119 ),
120 };
121 },
122 };
123 const applyBaseFunction = ($, base, lengthDelta = 0) => {
124 if (base === undefined) {
125 // No base function was provided to apply.
126 return $;
127 } else {
128 // A base function was provided; apply it.
129 const { length, name, prototype } = base;
130 if (getPrototype($) === functionPrototype) {
131 setPrototype($, getPrototype(base));
132 } else {
133 /* do nothing */
134 }
135 return applyProperties($, {
136 length: +length + lengthDelta,
137 name,
138 prototype,
139 });
140 }
141 };
142 const applyProperties = ($, override) => {
143 if (override === undefined) {
144 // No properties were provided to apply.
145 return $;
146 } else {
147 // Properties were provided; apply them.
148 const { length, name, prototype } = override;
149 if (
150 !getOwnPropertyDescriptor($, "prototype")?.writable ||
151 prototype === undefined
152 ) {
153 // The provided function has no `.prototype`, its prototype is
154 // not writable, or no prototype value was provided.
155 //
156 // Do not modify the prototype property of the provided
157 // function.
158 /* do nothing */
159 } else {
160 // The provided function is a constructor and a prototype value
161 // was provided.
162 //
163 // Change the prototype property of the provided function to
164 // match.
165 defineOwnProperty($, "prototype", { value: prototype });
166 }
167 return defineOwnProperties($, {
168 length: {
169 value: toLength(length === undefined ? $.length : length),
170 },
171 name: {
172 value: toFunctionName(
173 name === undefined ? $.name ?? "" : name,
174 ),
175 },
176 });
177 }
178 };
179
180 return {
181 bind: ($, boundThis, boundArgs) =>
182 callBind(
183 $,
184 boundThis,
185 ...objectCreate(
186 argumentIterablePrototype,
187 { args: { value: boundArgs } },
188 ),
189 ),
190 createArrowFunction: ($, propertyOverride = undefined) =>
191 applyProperties(
192 applyBaseFunction(
193 (...$s) => reflectApply($, undefined, $s),
194 $,
195 ),
196 propertyOverride,
197 ),
198 createCallableFunction: ($, propertyOverride = undefined) =>
199 applyProperties(
200 applyBaseFunction(
201 (...$s) => {
202 const iterator = objectCreate(
203 argumentIterablePrototype,
204 { args: { value: $s } },
205 )[ITERATOR]();
206 const { value: thisValue } = iterator.next();
207 return reflectApply($, thisValue, [...iterator]);
208 },
209 $,
210 1,
211 ),
212 propertyOverride,
213 ),
214 createIllegalConstructor: ($, propertyOverride = undefined) =>
215 defineOwnProperty(
216 applyProperties(
217 applyBaseFunction(
218 function () {
219 throw new TypeError("Illegal constructor");
220 },
221 $,
222 ),
223 propertyOverride,
224 ),
225 "prototype",
226 { writable: false },
227 ),
228 createProxyConstructor: (
229 handler,
230 $,
231 newTarget = undefined,
232 propertyOverride = undefined,
233 ) => {
234 const constructor = $ === undefined ? objectConstructor : $;
235 const target = newTarget === undefined ? constructor : newTarget;
236 const len = toLength(constructor.length);
237 if (!(type(handler) === "object")) {
238 throw new TypeError(
239 `Piscēs: Proxy handler must be an object, but got: ${handler}.`,
240 );
241 } else if (!isConstructor(constructor)) {
242 throw new TypeError(
243 "Piscēs: Cannot create proxy constructor from nonconstructible value.",
244 );
245 } else if (!isConstructor(target)) {
246 throw new TypeError(
247 "Piscēs: New target must be a constructor.",
248 );
249 } else {
250 return applyProperties(
251 defineOwnProperties(
252 setPrototype(
253 function C(...$s) {
254 if (new.target === undefined) {
255 throw new TypeError(
256 `Piscēs: ${
257 C.name ?? "Proxy"
258 } must be called with new.`,
259 );
260 } else {
261 const O = reflectConstruct(
262 constructor,
263 $s,
264 target,
265 );
266 return new proxyConstructor(O, handler);
267 }
268 },
269 proxyConstructor,
270 ),
271 {
272 length: { value: len },
273 name: {
274 value: `${
275 toFunctionName(constructor.name ?? "")
276 }Proxy`,
277 },
278 prototype: {
279 configurable: false,
280 enumerable: false,
281 value: undefined,
282 writable: false,
283 },
284 revocable: {
285 configurable: true,
286 enumerable: false,
287 value: defineOwnProperties(
288 (...$s) => {
289 const O = reflectConstruct(
290 constructor,
291 $s,
292 target,
293 );
294 return revocable(O, handler);
295 },
296 {
297 length: { value: len },
298 name: { value: "revocable" },
299 },
300 ),
301 writable: true,
302 },
303 },
304 ),
305 propertyOverride,
306 );
307 }
308 },
309 };
310 })();
311
312 /**
313 * Calls the provided function with the provided this value and
314 * arguments list.
315 *
316 * ☡ This is effectively an alias for `Reflect.apply`—the arguments
317 * must be passed as an arraylike.
318 */
319 export const call = createArrowFunction(Reflect.apply, {
320 name: "call",
321 });
322
323 /**
324 * Returns whether calling the provided function with no `this` value
325 * or arguments completes normally; that is, does not throw an error.
326 *
327 * ☡ This function will throw an error if the provided argument is not
328 * callable.
329 */
330 export const completesNormally = ($) => {
331 if (!isCallable($)) {
332 // The provided value is not callable; this is an error.
333 throw new TypeError(
334 `Piscēs: Cannot determine completion of noncallable value: ${$}`,
335 );
336 } else {
337 // The provided value is callable.
338 try {
339 // Attempt to call the function and return true if this succeeds.
340 $();
341 return true;
342 } catch {
343 // Calling the function did not succeed; return false.
344 return false;
345 }
346 }
347 };
348
349 /**
350 * Constructs the provided function with the provided arguments list
351 * and new target.
352 *
353 * ☡ This is effectively an alias for `Reflect.construct`—the
354 * arguments must be passed as an arraylike.
355 */
356 export const construct = createArrowFunction(Reflect.construct);
357
358 /**
359 * Returns the provided value.
360 *
361 * ※ This function can be called as a constructor. When used in an
362 * `extends` clause and called via `super`, it will set the value of
363 * `this` to the provided value, enabling it to be extended with
364 * private class features.
365 */
366 export const identity = function ($) {
367 return $;
368 };
369
370 /** Returns whether the provided value is callable. */
371 export const isCallable = ($) => typeof $ === "function";
372
373 /** Returns whether the provided value is a constructor. */
374 export const isConstructor = ($) =>
375 completesNormally(() =>
376 // Try constructing a new object with the provided value as its
377 // `new.target`. This will throw if the provided value is not a
378 // constructor.
379 construct(function () {}, [], $)
380 );
381
382 /**
383 * Returns whether the provided object inherits from the prototype of
384 * the provided function.
385 */
386 export const ordinaryHasInstance = createCallableFunction(
387 Function.prototype[Symbol.hasInstance],
388 { name: "ordinaryHasInstance" },
389 );
This page took 0.130636 seconds and 5 git commands to generate.