]> Lady’s Gitweb - Pisces/blob - function.js
Build function.js on top of object.js infra
[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 applyBaseFunction = ($, base, lengthDelta = 0) => {
132 if (base === UNDEFINED) {
133 // No base function was provided to apply.
134 return $;
135 } else {
136 // A base function was provided; apply it.
137 const { length, name, prototype } = base;
138 if (getPrototype($) === functionPrototype) {
139 setPrototype($, getPrototype(base));
140 } else {
141 /* do nothing */
142 }
143 return applyProperties($, {
144 length: +length + lengthDelta,
145 name,
146 prototype,
147 });
148 }
149 };
150 const applyProperties = ($, override) => {
151 if (override === UNDEFINED) {
152 // No properties were provided to apply.
153 return $;
154 } else {
155 // Properties were provided; apply them.
156 const { length, name, prototype } = override;
157 if (
158 prototype === UNDEFINED ||
159 !getOwnPropertyDescriptor($, "prototype")?.writable
160 ) {
161 // The provided function has no `.prototype`, its prototype is
162 // not writable, or no prototype value was provided.
163 //
164 // Do not modify the prototype property of the provided
165 // function.
166 /* do nothing */
167 } else {
168 // The provided function is a constructor and a prototype value
169 // was provided.
170 //
171 // Change the prototype property of the provided function to
172 // match.
173 defineOwnProperty(
174 $,
175 "prototype",
176 defineOwnDataProperty(
177 objectCreate(null),
178 "value",
179 prototype,
180 ),
181 );
182 }
183 return defineOwnProperties($, {
184 length: defineOwnDataProperty(
185 objectCreate(null),
186 "value",
187 toLength(length === UNDEFINED ? $.length : length),
188 ),
189 name: defineOwnDataProperty(
190 objectCreate(null),
191 "value",
192 toFunctionName(name === UNDEFINED ? $.name ?? "" : name),
193 ),
194 });
195 }
196 };
197
198 return {
199 bind: ($, boundThis, boundArgs) =>
200 callBind(
201 $,
202 boundThis,
203 ...defineOwnDataProperty(
204 objectCreate(argumentIterablePrototype),
205 "args",
206 boundArgs,
207 ),
208 ),
209 createArrowFunction: ($, propertyOverride = UNDEFINED) =>
210 applyProperties(
211 applyBaseFunction(
212 (...$s) => reflectApply($, UNDEFINED, $s),
213 $,
214 ),
215 propertyOverride,
216 ),
217 createCallableFunction: ($, propertyOverride = UNDEFINED) =>
218 applyProperties(
219 applyBaseFunction(
220 (...$s) => {
221 const iterator = defineOwnDataProperty(
222 objectCreate(argumentIterablePrototype),
223 "args",
224 $s,
225 )[ITERATOR]();
226 const { value: thisValue } = iterator.next();
227 return reflectApply($, thisValue, [...iterator]);
228 },
229 $,
230 1,
231 ),
232 propertyOverride,
233 ),
234 createIllegalConstructor: ($, propertyOverride = UNDEFINED) =>
235 defineOwnProperty(
236 applyProperties(
237 applyBaseFunction(
238 function () {
239 throw new TypeError("Illegal constructor");
240 },
241 $,
242 ),
243 propertyOverride,
244 ),
245 "prototype",
246 { writable: false },
247 ),
248 createProxyConstructor: (
249 handler,
250 $,
251 newTarget = UNDEFINED,
252 propertyOverride = UNDEFINED,
253 ) => {
254 const constructor = $ === UNDEFINED ? objectConstructor : $;
255 const target = newTarget === UNDEFINED ? constructor : newTarget;
256 const len = toLength(constructor.length);
257 if (!(type(handler) === "object")) {
258 // The provided handler is not an object; this is an error.
259 throw new TypeError(
260 `Piscēs: Proxy handler must be an object, but got: ${handler}.`,
261 );
262 } else if (!isConstructor(constructor)) {
263 // The provided constructor is not a constructor; this is an
264 // error.
265 throw new TypeError(
266 "Piscēs: Cannot create proxy constructor from nonconstructible value.",
267 );
268 } else if (!isConstructor(target)) {
269 // The provided new target is not a constructor; this is an
270 // error.
271 throw new TypeError(
272 "Piscēs: New target must be a constructor.",
273 );
274 } else {
275 // The arguments are acceptable.
276 return applyProperties(
277 defineOwnProperties(
278 setPrototype(
279 function C(...$s) {
280 if (new.target === UNDEFINED) {
281 // The constructor was not called with new; this is
282 // an error.
283 throw new TypeError(
284 `Piscēs: ${
285 C.name ?? "Proxy"
286 } must be called with new.`,
287 );
288 } else {
289 // The constructor was called with new; return the
290 // appropriate proxy.
291 const O = reflectConstruct(
292 constructor,
293 $s,
294 target,
295 );
296 return new proxyConstructor(O, handler);
297 }
298 },
299 proxyConstructor,
300 ),
301 {
302 length: defineOwnDataProperty(
303 objectCreate(null),
304 "value",
305 len,
306 ),
307 name: defineOwnDataProperty(
308 objectCreate(null),
309 "value",
310 `${toFunctionName(constructor.name ?? "")}Proxy`,
311 ),
312 prototype: setPropertyValues(objectCreate(null), {
313 configurable: false,
314 enumerable: false,
315 value: UNDEFINED,
316 writable: false,
317 }),
318 revocable: setPropertyValues(objectCreate(null), {
319 configurable: true,
320 enumerable: false,
321 value: defineOwnProperties(
322 (...$s) => {
323 const O = reflectConstruct(
324 constructor,
325 $s,
326 target,
327 );
328 return revocable(O, handler);
329 },
330 {
331 length: defineOwnDataProperty(
332 objectCreate(null),
333 "value",
334 len,
335 ),
336 name: defineOwnDataProperty(
337 objectCreate(null),
338 "value",
339 "revocable",
340 ),
341 },
342 ),
343 writable: true,
344 }),
345 },
346 ),
347 propertyOverride,
348 );
349 }
350 },
351 };
352 })();
353
354 /**
355 * Calls the provided function with the provided this value and
356 * arguments list.
357 *
358 * ☡ This is effectively an alias for `Reflect.apply`—the arguments
359 * must be passed as an arraylike.
360 */
361 export const call = createArrowFunction(Reflect.apply, {
362 name: "call",
363 });
364
365 /**
366 * Returns whether calling the provided function with no `this` value
367 * or arguments completes normally; that is, does not throw an error.
368 *
369 * ☡ This function will throw an error if the provided argument is not
370 * callable.
371 */
372 export const completesNormally = ($) => {
373 if (!isCallable($)) {
374 // The provided value is not callable; this is an error.
375 throw new TypeError(
376 `Piscēs: Cannot determine completion of noncallable value: ${$}`,
377 );
378 } else {
379 // The provided value is callable.
380 try {
381 // Attempt to call the function and return true if this succeeds.
382 $();
383 return true;
384 } catch {
385 // Calling the function did not succeed; return false.
386 return false;
387 }
388 }
389 };
390
391 /**
392 * Constructs the provided function with the provided arguments list
393 * and new target.
394 *
395 * ☡ This is effectively an alias for `Reflect.construct`—the
396 * arguments must be passed as an arraylike.
397 */
398 export const construct = createArrowFunction(Reflect.construct);
399
400 /**
401 * Returns the provided value.
402 *
403 * ※ This function can be called as a constructor. When used in an
404 * `extends` clause and called via `super`, it will set the value of
405 * `this` to the provided value, enabling it to be extended with
406 * private class features.
407 */
408 export const identity = function ($) {
409 return $;
410 };
411
412 /** Returns whether the provided value is callable. */
413 export const isCallable = ($) => typeof $ === "function";
414
415 /** Returns whether the provided value is a constructor. */
416 export const isConstructor = ($) =>
417 completesNormally(() =>
418 // Try constructing a new object with the provided value as its
419 // `new.target`. This will throw if the provided value is not a
420 // constructor.
421 construct(function () {}, [], $)
422 );
423
424 /**
425 * Returns whether the provided object inherits from the prototype of
426 * the provided function.
427 */
428 export const ordinaryHasInstance = createCallableFunction(
429 Function.prototype[Symbol.hasInstance],
430 { name: "ordinaryHasInstance" },
431 );
This page took 0.167015 seconds and 5 git commands to generate.