]> Lady’s Gitweb - Pisces/blob - function.js
Add methods for own entries and values to object.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 {
11 ITERATOR,
12 toFunctionName,
13 toLength,
14 type,
15 UNDEFINED,
16 } from "./value.js";
17 import {
18 defineOwnDataProperty,
19 defineOwnProperties,
20 defineOwnProperty,
21 getOwnPropertyDescriptor,
22 getPrototype,
23 objectCreate,
24 setPropertyValues,
25 setPrototype,
26 } from "./object.js";
27
28 export const {
29 /**
30 * Creates a bound function from the provided function using the
31 * provided this value and arguments list.
32 *
33 * ☡ As with `call` and `construct`, the arguments must be passed as
34 * an array.
35 */
36 bind,
37
38 /**
39 * Returns a new function which calls the provided function with its
40 * first argument as the `this` value and the remaining arguments
41 * passed through.
42 *
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`.
46 */
47 createArrowFunction,
48
49 /**
50 * Returns a new function which calls the provided function with its
51 * first argument as the `this` value and the remaining arguments
52 * passed through.
53 *
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`.
57 *
58 * ※ This is effectively an alias for `Function::call.bind`.
59 */
60 createCallableFunction,
61
62 /**
63 * Returns a constructor which throws whenever it is called but has
64 * the same `.name` and `.prototype` as the provided value.
65 *
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`.
69 */
70 createIllegalConstructor,
71
72 /**
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.
76 *
77 * The resulting constructor inherits from, and has the same basic
78 * shape as, `Proxy`.
79 *
80 * If a base constructor is not provided, `Object` will be used.
81 *
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`.
86 *
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
89 * `name`.
90 *
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
95 * Ecmascript code.
96 */
97 createProxyConstructor,
98 } = (() => {
99 const { prototype: functionPrototype } = Function;
100 const {
101 bind: functionBind,
102 call: functionCall,
103 } = functionPrototype;
104 const objectConstructor = Object;
105 const proxyConstructor = Proxy;
106 const {
107 apply: reflectApply,
108 construct: reflectConstruct,
109 } = Reflect;
110 const callBind = reflectApply(functionBind, functionCall, [
111 functionBind,
112 ]);
113 const { revocable } = Proxy;
114 const { [ITERATOR]: arrayIterator } = Array.prototype;
115 const {
116 next: arrayIteratorNext,
117 } = getPrototype([][ITERATOR]());
118 const argumentIterablePrototype = {
119 [ITERATOR]() {
120 return {
121 [ITERATOR]() {
122 return this;
123 },
124 next: callBind(
125 arrayIteratorNext,
126 call(arrayIterator, this.args, []),
127 ),
128 };
129 },
130 };
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, [
138 constructor,
139 ]);
140 if (existing) {
141 return existing;
142 } else {
143 const result = new wsConstructor();
144 reflectApply(wmSet, proxyConstructorValuesMap, [
145 constructor,
146 result,
147 ]);
148 return result;
149 }
150 })();
151 reflectApply(wsAdd, values, [proxy]);
152 return proxy;
153 };
154 const applyBaseFunction = ($, base, lengthDelta = 0) => {
155 if (base === UNDEFINED) {
156 // No base function was provided to apply.
157 return $;
158 } else {
159 // A base function was provided; apply it.
160 const { length, name, prototype } = base;
161 if (getPrototype($) === functionPrototype) {
162 setPrototype($, getPrototype(base));
163 } else {
164 /* do nothing */
165 }
166 return applyProperties($, {
167 length: +length + lengthDelta,
168 name,
169 prototype,
170 });
171 }
172 };
173 const applyProperties = ($, override) => {
174 if (override === UNDEFINED) {
175 // No properties were provided to apply.
176 return $;
177 } else {
178 // Properties were provided; apply them.
179 const { length, name, prototype } = override;
180 if (
181 prototype === UNDEFINED ||
182 !getOwnPropertyDescriptor($, "prototype")?.writable
183 ) {
184 // The provided function has no `.prototype`, its prototype is
185 // not writable, or no prototype value was provided.
186 //
187 // Do not modify the prototype property of the provided
188 // function.
189 /* do nothing */
190 } else {
191 // The provided function is a constructor and a prototype value
192 // was provided.
193 //
194 // Change the prototype property of the provided function to
195 // match.
196 defineOwnProperty(
197 $,
198 "prototype",
199 defineOwnDataProperty(
200 objectCreate(null),
201 "value",
202 prototype,
203 ),
204 );
205 }
206 return defineOwnProperties($, {
207 length: defineOwnDataProperty(
208 objectCreate(null),
209 "value",
210 toLength(length === UNDEFINED ? $.length : length),
211 ),
212 name: defineOwnDataProperty(
213 objectCreate(null),
214 "value",
215 toFunctionName(name === UNDEFINED ? $.name ?? "" : name),
216 ),
217 });
218 }
219 };
220
221 return {
222 bind: ($, boundThis, boundArgs) =>
223 callBind(
224 $,
225 boundThis,
226 ...defineOwnDataProperty(
227 objectCreate(argumentIterablePrototype),
228 "args",
229 boundArgs,
230 ),
231 ),
232 createArrowFunction: ($, propertyOverride = UNDEFINED) =>
233 applyProperties(
234 applyBaseFunction(
235 (...$s) => reflectApply($, UNDEFINED, $s),
236 $,
237 ),
238 propertyOverride,
239 ),
240 createCallableFunction: ($, propertyOverride = UNDEFINED) =>
241 applyProperties(
242 applyBaseFunction(
243 (...$s) => {
244 const iterator = defineOwnDataProperty(
245 objectCreate(argumentIterablePrototype),
246 "args",
247 $s,
248 )[ITERATOR]();
249 const { value: thisValue } = iterator.next();
250 return reflectApply($, thisValue, [...iterator]);
251 },
252 $,
253 1,
254 ),
255 propertyOverride,
256 ),
257 createIllegalConstructor: ($, propertyOverride = UNDEFINED) =>
258 defineOwnProperty(
259 applyProperties(
260 applyBaseFunction(
261 function () {
262 throw new TypeError("Illegal constructor");
263 },
264 $,
265 ),
266 propertyOverride,
267 ),
268 "prototype",
269 { writable: false },
270 ),
271 createProxyConstructor: (
272 handler,
273 $,
274 newTarget = UNDEFINED,
275 propertyOverride = UNDEFINED,
276 ) => {
277 const constructor = $ === UNDEFINED
278 ? function ($) {
279 return new objectConstructor($);
280 }
281 : $;
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.
286 throw new TypeError(
287 `Piscēs: Proxy handler must be an object, but got: ${handler}.`,
288 );
289 } else if (!isConstructor(constructor)) {
290 // The provided constructor is not a constructor; this is an
291 // error.
292 throw new TypeError(
293 "Piscēs: Cannot create proxy constructor from nonconstructible value.",
294 );
295 } else if (!isConstructor(target)) {
296 // The provided new target is not a constructor; this is an
297 // error.
298 throw new TypeError(
299 "Piscēs: New target must be a constructor.",
300 );
301 } else {
302 // The arguments are acceptable.
303 const C = applyProperties(
304 defineOwnProperties(
305 setPrototype(
306 function (...$s) {
307 if (new.target === UNDEFINED) {
308 // The constructor was not called with new; this is an
309 // error.
310 throw new TypeError(
311 `Piscēs: ${
312 C.name ?? "Proxy"
313 } must be called with new.`,
314 );
315 } else {
316 // The constructor was called with new; return the
317 // appropriate proxy.
318 const O = reflectConstruct(
319 constructor,
320 $s,
321 target,
322 );
323 const proxy = new proxyConstructor(O, handler);
324 return registerConstructedProxy(C, proxy);
325 }
326 },
327 proxyConstructor,
328 ),
329 {
330 length: defineOwnDataProperty(
331 objectCreate(null),
332 "value",
333 len,
334 ),
335 name: defineOwnDataProperty(
336 objectCreate(null),
337 "value",
338 `${toFunctionName(constructor.name ?? "")}Proxy`,
339 ),
340 prototype: setPropertyValues(objectCreate(null), {
341 configurable: false,
342 enumerable: false,
343 value: UNDEFINED,
344 writable: false,
345 }),
346 },
347 ),
348 propertyOverride,
349 );
350 const { name } = C;
351 return defineOwnProperties(C, {
352 revocable: setPropertyValues(objectCreate(null), {
353 configurable: true,
354 enumerable: false,
355 value: defineOwnProperties(
356 (...$s) => {
357 const O = reflectConstruct(
358 constructor,
359 $s,
360 target,
361 );
362 const proxy = revocable(O, handler);
363 return registerConstructedProxy(C, proxy);
364 },
365 {
366 length: defineOwnDataProperty(
367 objectCreate(null),
368 "value",
369 len,
370 ),
371 name: defineOwnDataProperty(
372 objectCreate(null),
373 "value",
374 "revocable",
375 ),
376 },
377 ),
378 writable: true,
379 }),
380 [`is${name}`]: setPropertyValues(objectCreate(null), {
381 configurable: true,
382 enumerable: false,
383 value: defineOwnProperty(
384 ($) => {
385 const values = reflectApply(
386 wmGet,
387 proxyConstructorValuesMap,
388 [C],
389 );
390 if (values === UNDEFINED) {
391 // No values have been registered for the current
392 // constructor.
393 return false;
394 } else {
395 // One or more values has been registered for the
396 // current constructor; return whether the provided
397 // argument is one.
398 return reflectApply(wsHas, values, [$]);
399 }
400 },
401 "name",
402 defineOwnDataProperty(
403 objectCreate(null),
404 "value",
405 `is${name}`,
406 ),
407 ),
408 writable: true,
409 }),
410 });
411 }
412 },
413 };
414 })();
415
416 /**
417 * Calls the provided function with the provided this value and
418 * arguments list.
419 *
420 * ☡ This is effectively an alias for `Reflect.apply`—the arguments
421 * must be passed as an arraylike.
422 */
423 export const call = createArrowFunction(Reflect.apply, {
424 name: "call",
425 });
426
427 /**
428 * Returns whether calling the provided function with no `this` value
429 * or arguments completes normally; that is, does not throw an error.
430 *
431 * ☡ This function will throw an error if the provided argument is not
432 * callable.
433 */
434 export const completesNormally = ($) => {
435 if (!isCallable($)) {
436 // The provided value is not callable; this is an error.
437 throw new TypeError(
438 `Piscēs: Cannot determine completion of noncallable value: ${$}`,
439 );
440 } else {
441 // The provided value is callable.
442 try {
443 // Attempt to call the function and return true if this succeeds.
444 $();
445 return true;
446 } catch {
447 // Calling the function did not succeed; return false.
448 return false;
449 }
450 }
451 };
452
453 /**
454 * Constructs the provided function with the provided arguments list
455 * and new target.
456 *
457 * ☡ This is effectively an alias for `Reflect.construct`—the
458 * arguments must be passed as an arraylike.
459 */
460 export const construct = createArrowFunction(Reflect.construct);
461
462 /**
463 * Returns the provided value.
464 *
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.
469 */
470 export const identity = function ($) {
471 return $;
472 };
473
474 /** Returns whether the provided value is callable. */
475 export const isCallable = ($) => typeof $ === "function";
476
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
482 // constructor.
483 construct(function () {}, [], $)
484 );
485
486 /**
487 * Calls the provided callback with the provided argument if the
488 * provided argument is not nullish; otherwise, returns the provided
489 * argument unmodified.
490 */
491 export const maybe = ($, callback) => $ == null ? $ : callback($);
492
493 /**
494 * Returns whether the provided object inherits from the prototype of
495 * the provided function.
496 */
497 export const ordinaryHasInstance = createCallableFunction(
498 Function.prototype[Symbol.hasInstance],
499 { name: "ordinaryHasInstance" },
500 );
This page took 0.156942 seconds and 5 git commands to generate.