]> Lady’s Gitweb - Pisces/blob - function.js
Redo create⸺Function A·P·I in 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, toLength, toPrimitive } 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 string function name generated from the provided value
58 * and optional prefix.
59 */
60 toFunctionName,
61 } = (() => {
62 // ☡ Because these functions are used to initialize module constants,
63 // they can’t depend on imports from elsewhere.
64 const {
65 bind: functionBind,
66 call: functionCall,
67 } = Function.prototype;
68 const callBind = Reflect.apply(functionBind, functionCall, [
69 functionBind,
70 ]);
71 const {
72 create: objectCreate,
73 defineProperty: defineOwnProperty,
74 defineProperties: defineOwnProperties,
75 getOwnPropertyDescriptor,
76 getPrototypeOf: getPrototype,
77 setPrototypeOf: setPrototype,
78 } = Object;
79 const { [ITERATOR]: arrayIterator } = Array.prototype;
80 const {
81 next: arrayIteratorNext,
82 } = getPrototype([][ITERATOR]());
83 const argumentIterablePrototype = {
84 [ITERATOR]() {
85 return {
86 [ITERATOR]() {
87 return this;
88 },
89 next: callBind(
90 arrayIteratorNext,
91 call(arrayIterator, this.args, []),
92 ),
93 };
94 },
95 };
96 const getSymbolDescription = getOwnPropertyDescriptor(
97 Symbol.prototype,
98 "description",
99 ).get;
100 const applyBaseFunction = ($, base, lengthDelta = 0) => {
101 if (base === undefined) {
102 // No base function was provided to apply.
103 return $;
104 } else {
105 // A base function was provided; apply it.
106 const { length, name, prototype } = base;
107 setPrototype($, getPrototype(base));
108 return applyProperties($, {
109 length: +length + lengthDelta,
110 name,
111 prototype,
112 });
113 }
114 };
115 const applyProperties = ($, override) => {
116 if (override === undefined) {
117 // No properties were provided to apply.
118 return $;
119 } else {
120 // Properties were provided; apply them.
121 const { length, name, prototype } = override;
122 if (!isConstructor($) || prototype === undefined) {
123 // The provided function is not a constructor or no prototype
124 // value was provided.
125 //
126 // Do not modify the prototype property of the provided
127 // function.
128 /* do nothing */
129 } else {
130 // The provided function is a constructor and a prototype value
131 // was provided.
132 //
133 // Change the prototype property of the provided function to
134 // match.
135 defineOwnProperty($, "prototype", { value: prototype });
136 }
137 return defineOwnProperties($, {
138 length: {
139 value: toLength(length === undefined ? $.length : length),
140 },
141 name: {
142 value: toFunctionName(
143 name === undefined ? $.name ?? "" : name,
144 ),
145 },
146 });
147 }
148 };
149
150 return {
151 bind: ($, boundThis, boundArgs) =>
152 callBind(
153 $,
154 boundThis,
155 ...objectCreate(
156 argumentIterablePrototype,
157 { args: { value: boundArgs } },
158 ),
159 ),
160 createArrowFunction: ($, propertyOverride = undefined) =>
161 applyProperties(
162 applyBaseFunction(
163 (...$s) =>
164 $(...objectCreate(
165 argumentIterablePrototype,
166 { args: { value: $s } },
167 )),
168 $,
169 ),
170 propertyOverride,
171 ),
172 createCallableFunction: ($, propertyOverride = undefined) =>
173 applyProperties(
174 applyBaseFunction(
175 (...$s) => {
176 const iterator = objectCreate(
177 argumentIterablePrototype,
178 { args: { value: $s } },
179 )[ITERATOR]();
180 const { value: thisValue } = iterator.next();
181 return call($, thisValue, [...iterator]);
182 },
183 $,
184 1,
185 ),
186 propertyOverride,
187 ),
188 createIllegalConstructor: ($, propertyOverride = undefined) => {
189 const constructor = applyProperties(
190 applyBaseFunction(
191 function () {
192 throw new TypeError("Illegal constructor");
193 },
194 $,
195 ),
196 propertyOverride,
197 );
198 return defineOwnProperty(constructor, "prototype", {
199 writable: false,
200 });
201 },
202 toFunctionName: ($, prefix = undefined) => {
203 const key = toPrimitive($, "string");
204 const name = (() => {
205 if (typeof key === "symbol") {
206 // The provided value is a symbol; format its description.
207 const description = call(getSymbolDescription, key, []);
208 return description === undefined ? "" : `[${description}]`;
209 } else {
210 // The provided value not a symbol; convert it to a string
211 // property key.
212 return `${key}`;
213 }
214 })();
215 return prefix !== undefined ? `${prefix} ${name}` : name;
216 },
217 };
218 })();
219
220 export const {
221 /**
222 * Calls the provided function with the provided this value and
223 * arguments list.
224 *
225 * ☡ This is effectively an alias for `Reflect.apply`—the arguments
226 * must be passed as an arraylike.
227 */
228 call,
229
230 /**
231 * Constructs the provided function with the provided arguments list
232 * and new target.
233 *
234 * ☡ This is effectively an alias for `Reflect.construct`—the
235 * arguments must be passed as an arraylike.
236 */
237 construct,
238
239 /** Returns whether the provided value is a constructor. */
240 isConstructor,
241 } = (() => {
242 const { apply, construct } = Reflect;
243 return {
244 call: (target, thisArgument, argumentsList) =>
245 apply(target, thisArgument, argumentsList),
246 construct: (target, argumentsList, ...args) =>
247 args.length > 0
248 ? construct(target, argumentsList, args[0])
249 : construct(target, argumentsList),
250 isConstructor: ($) =>
251 completesNormally(() =>
252 // Try constructing a new object with the provided value as its
253 // `new.target`. This will throw if the provided value is not a
254 // constructor.
255 construct(function () {}, [], $)
256 ),
257 };
258 })();
259
260 /**
261 * Returns whether calling the provided function with no `this` value
262 * or arguments completes normally; that is, does not throw an error.
263 *
264 * ☡ This function will throw an error if the provided argument is not
265 * callable.
266 */
267 export const completesNormally = ($) => {
268 if (!isCallable($)) {
269 // The provided value is not callable; this is an error.
270 throw new TypeError(
271 `Piscēs: Cannot determine completion of noncallable value: ${$}`,
272 );
273 } else {
274 // The provided value is callable.
275 try {
276 // Attempt to call the function and return true if this succeeds.
277 $();
278 return true;
279 } catch {
280 // Calling the function did not succeed; return false.
281 return false;
282 }
283 }
284 };
285
286 /**
287 * Returns the provided value.
288 *
289 * ※ This function can be called as a constructor. When used in an
290 * `extends` clause and called via `super`, it will set the value of
291 * `this` to the provided value, enabling it to be extended with
292 * private class features.
293 */
294 export const identity = function ($) {
295 return $;
296 };
297
298 /** Returns whether the provided value is callable. */
299 export const isCallable = ($) => typeof $ === "function";
300
301 /**
302 * Returns whether the provided object inherits from the prototype of
303 * the provided function.
304 */
305 export const ordinaryHasInstance = createCallableFunction(
306 Function.prototype[Symbol.hasInstance],
307 { name: "ordinaryHasInstance" },
308 );
This page took 0.17244 seconds and 5 git commands to generate.