]> Lady’s Gitweb - Pisces/blob - function.js
Cast to primitive when creating function names
[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, 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 * ※ This is effectively an alias for `Function::call.bind`.
28 */
29 createCallableFunction,
30
31 /**
32 * Returns a constructor which throws whenever it is called but has
33 * the same `.name` and `.prototype` as the provided value.
34 *
35 * If a second argument is provided, the returned constructor will
36 * use that as its prototype; otherwise, it will use the prototype of
37 * the provided value.
38 */
39 createIllegalConstructor,
40
41 /**
42 * Returns a string function name generated from the provided value
43 * and optional prefix.
44 */
45 toFunctionName,
46 } = (() => {
47 // ☡ Because these functions are used to initialize module constants,
48 // they can’t depend on imports from elsewhere.
49 const {
50 bind: functionBind,
51 call: functionCall,
52 } = Function.prototype;
53 const callBind = Reflect.apply(functionBind, functionCall, [
54 functionBind,
55 ]);
56 const {
57 create: objectCreate,
58 defineProperties: defineOwnProperties,
59 getOwnPropertyDescriptor,
60 getPrototypeOf: getPrototype,
61 setPrototypeOf: setPrototype,
62 } = Object;
63 const { [ITERATOR]: arrayIterator } = Array.prototype;
64 const {
65 next: arrayIteratorNext,
66 } = getPrototype([][ITERATOR]());
67 const argumentIterablePrototype = {
68 [ITERATOR]() {
69 return {
70 next: callBind(
71 arrayIteratorNext,
72 call(arrayIterator, this.args, []),
73 ),
74 };
75 },
76 };
77 const getSymbolDescription = getOwnPropertyDescriptor(
78 Symbol.prototype,
79 "description",
80 ).get;
81
82 return {
83 bind: ($, boundThis, boundArgs) =>
84 callBind(
85 $,
86 boundThis,
87 ...objectCreate(
88 argumentIterablePrototype,
89 { args: { value: boundArgs } },
90 ),
91 ),
92 createCallableFunction: ($, name = undefined) =>
93 defineOwnProperties(callBind(functionCall, $), {
94 length: { value: $.length + 1 },
95 name: {
96 value: toFunctionName(
97 name === undefined ? $.name ?? "" : name,
98 ),
99 },
100 }),
101 createIllegalConstructor: ($, proto = undefined) => {
102 const constructor = function () {
103 throw new TypeError("Illegal constructor");
104 };
105 if ($ == null && proto === undefined) {
106 // The provided argument is nullish and no explicit prototype
107 // was provided.
108 //
109 // Do not modify the prototype of the generated constructor.
110 /* do nothing */
111 } else {
112 // The provided argument is not nullish or an explicit
113 // prototype was provided.
114 //
115 // Set the prototype of the generated constructor to match.
116 setPrototype(
117 constructor,
118 proto === undefined ? getPrototype($) : proto,
119 );
120 }
121 return defineOwnProperties(constructor, {
122 name: { value: $?.name ?? "" },
123 prototype: { value: $?.prototype ?? {}, writable: false },
124 });
125 },
126 toFunctionName: ($, prefix = undefined) => {
127 const key = toPrimitive($, "string");
128 const name = (() => {
129 if (typeof key === "symbol") {
130 // The provided value is a symbol; format its description.
131 const description = call(getSymbolDescription, key, []);
132 return description === undefined ? "" : `[${description}]`;
133 } else {
134 // The provided value not a symbol; convert it to a string
135 // property key.
136 return `${key}`;
137 }
138 })();
139 return prefix !== undefined ? `${prefix} ${name}` : name;
140 },
141 };
142 })();
143
144 export const {
145 /**
146 * Calls the provided function with the provided this value and
147 * arguments list.
148 *
149 * ☡ This is effectively an alias for `Reflect.apply`—the arguments
150 * must be passed as an arraylike.
151 */
152 call,
153
154 /**
155 * Constructs the provided function with the provided arguments list
156 * and new target.
157 *
158 * ☡ This is effectively an alias for `Reflect.construct`—the
159 * arguments must be passed as an arraylike.
160 */
161 construct,
162
163 /** Returns whether the provided value is a constructor. */
164 isConstructor,
165 } = (() => {
166 const { apply, construct } = Reflect;
167 return {
168 call: (target, thisArgument, argumentsList) =>
169 apply(target, thisArgument, argumentsList),
170 construct: (target, argumentsList, ...args) =>
171 args.length > 0
172 ? construct(target, argumentsList, args[0])
173 : construct(target, argumentsList),
174 isConstructor: ($) =>
175 completesNormally(() =>
176 // Try constructing a new object with the provided value as its
177 // `new.target`. This will throw if the provided value is not a
178 // constructor.
179 construct(function () {}, [], $)
180 ),
181 };
182 })();
183
184 /**
185 * Returns whether calling the provided function with no `this` value
186 * or arguments completes normally; that is, does not throw an error.
187 *
188 * ☡ This function will throw an error if the provided argument is not
189 * callable.
190 */
191 export const completesNormally = ($) => {
192 if (!isCallable($)) {
193 // The provided value is not callable; this is an error.
194 throw new TypeError(
195 `Piscēs: Cannot determine completion of noncallable value: ${$}`,
196 );
197 } else {
198 // The provided value is callable.
199 try {
200 // Attempt to call the function and return true if this succeeds.
201 $();
202 return true;
203 } catch {
204 // Calling the function did not succeed; return false.
205 return false;
206 }
207 }
208 };
209
210 /**
211 * Returns the provided value.
212 *
213 * ※ This function can be called as a constructor. When used in an
214 * `extends` clause and called via `super`, it will set the value of
215 * `this` to the provided value, enabling it to be extended with
216 * private class features.
217 */
218 export const identity = function ($) {
219 return $;
220 };
221
222 /** Returns whether the provided value is callable. */
223 export const isCallable = ($) => typeof $ === "function";
224
225 /**
226 * Returns whether the provided object inherits from the prototype of
227 * the provided function.
228 */
229 export const ordinaryHasInstance = createCallableFunction(
230 Function.prototype[Symbol.hasInstance],
231 "ordinaryHasInstance",
232 );
This page took 0.060988 seconds and 5 git commands to generate.