]> Lady’s Gitweb - Pisces/blob - value.js
Move isIntegerIndexString into value.js
[Pisces] / value.js
1 // ♓🌟 Piscēs ∷ value.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 export const {
11 /** The welknown `@@asyncIterator` symbol. */
12 asyncIterator: ASYNC_ITERATOR,
13
14 /** The welknown `@@hasInstance` symbol. */
15 hasInstance: HAS_INSTANCE,
16
17 /** The welknown `@@isConcatSpreadable` symbol. */
18 isConcatSpreadable: IS_CONCAT_SPREADABLE,
19
20 /** The welknown `@@iterator` symbol. */
21 iterator: ITERATOR,
22
23 /** The welknown `@@match` symbol. */
24 match: MATCH,
25
26 /** The welknown `@@matchAll` symbol. */
27 matchAll: MATCH_ALL,
28
29 /** The welknown `@@replace` symbol. */
30 replace: REPLACE,
31
32 /** The welknown `@@species` symbol. */
33 species: SPECIES,
34
35 /** The welknown `@@split` symbol. */
36 split: SPLIT,
37
38 /** The welknown `@@toPrimitive` symbol. */
39 toPrimitive: TO_PRIMITIVE,
40
41 /** The welknown `@@toStringTag` symbol. */
42 toStringTag: TO_STRING_TAG,
43
44 /** The welknown `@@unscopables` symbol. */
45 unscopables: UNSCOPABLES,
46 } = Symbol;
47
48 /** The null primitive. */
49 export const NULL = null;
50
51 /** The undefined primitive. */
52 export const UNDEFINED = undefined;
53
54 /**
55 * Returns −0 if the provided argument is "-0"; returns a number
56 * representing the index if the provided argument is a canonical
57 * numeric index string; otherwise, returns undefined.
58 *
59 * There is no clamping of the numeric index, but note that numbers
60 * above 2^53 − 1 are not safe nor valid integer indices.
61 */
62 export const canonicalNumericIndexString = ($) => {
63 if (typeof $ !== "string") {
64 return undefined;
65 } else if ($ === "-0") {
66 return -0;
67 } else {
68 const n = +$;
69 return $ === `${n}` ? n : undefined;
70 }
71 };
72
73 /**
74 * Returns the length of the provided arraylike value.
75 *
76 * This can produce larger lengths than can actually be stored in
77 * arrays, because no such restrictions exist on arraylike methods.
78 *
79 * ☡ This function throws if the provided value is not arraylike.
80 */
81 export const lengthOfArraylike = ({ length }) => toLength(length);
82
83 export const {
84 /**
85 * Returns the primitive value of the provided object per its
86 * `.toString` and `.valueOf` methods.
87 *
88 * If the provided hint is "string", then `.toString` takes
89 * precedence; otherwise, `.valueOf` does.
90 *
91 * Throws an error if both of these methods are not callable or do
92 * not return a primitive.
93 */
94 ordinaryToPrimitive,
95
96 /**
97 * Returns the provided value converted to a primitive, or throws if
98 * no such conversion is possible.
99 *
100 * The provided preferred type, if specified, should be "string",
101 * "number", or "default". If the provided input has a
102 * `.[Symbol.toPrimitive]` method, this function will throw rather
103 * than calling that method with a preferred type other than one of
104 * the above.
105 */
106 toPrimitive,
107 } = (() => {
108 const { apply: call } = Reflect;
109
110 return {
111 ordinaryToPrimitive: (O, hint) => {
112 const methodNames = hint == "string"
113 ? ["toString", "valueOf"]
114 : ["valueOf", "toString"];
115 for (let index = 0; index < methodNames.length; ++index) {
116 const method = O[methodNames[index]];
117 if (typeof method === "function") {
118 // Method is callable.
119 const result = call(method, O, []);
120 if (type(result) !== "object") {
121 // Method returns a primitive.
122 return result;
123 } else {
124 // Method returns an object.
125 continue;
126 }
127 } else {
128 // Method is not callable.
129 continue;
130 }
131 }
132 throw new TypeError(
133 "Piscēs: Unable to convert object to primitive",
134 );
135 },
136 toPrimitive: ($, preferredType = "default") => {
137 const hint = `${preferredType}`;
138 if (
139 "default" !== hint && "string" !== hint &&
140 "number" !== hint
141 ) {
142 // An invalid preferred type was specified.
143 throw new TypeError(
144 `Piscēs: Invalid preferred type: ${preferredType}.`,
145 );
146 } else if (type($) === "object") {
147 // The provided value is an object.
148 const exoticToPrim = $[TO_PRIMITIVE] ?? undefined;
149 if (exoticToPrim !== undefined) {
150 // The provided value has an exotic primitive conversion
151 // method.
152 if (typeof exoticToPrim !== "function") {
153 // The method is not callable.
154 throw new TypeError(
155 "Piscēs: `.[Symbol.toPrimitive]` was neither nullish nor callable.",
156 );
157 } else {
158 // The method is callable.
159 return call(exoticToPrim, $, [hint]);
160 }
161 } else {
162 // Use the ordinary primitive conversion function.
163 return ordinaryToPrimitive($, hint);
164 }
165 } else {
166 // The provided value is already a primitive.
167 return $;
168 }
169 },
170 };
171 })();
172
173 export const {
174 /** Returns whether the provided value is an integer index string. */
175 isIntegerIndexString,
176
177 /**
178 * Returns whether the provided values are the same value.
179 *
180 * ※ This differs from `===` in the cases of nan and zero.
181 */
182 sameValue,
183
184 /**
185 * Returns whether the provided values are either the same value or
186 * both zero (either positive or negative).
187 *
188 * ※ This differs from `===` in the case of nan.
189 */
190 sameValueZero,
191
192 /**
193 * Returns the result of converting the provided value to an index,
194 * or throws an error if it is out of range.
195 */
196 toIndex,
197
198 /**
199 * Returns the result of converting the provided value to a length.
200 */
201 toLength,
202 } = (() => {
203 const { floor, max, min } = Math;
204 const {
205 MAX_SAFE_INTEGER: MAXIMUM_SAFE_INTEGRAL_NUMBER,
206 isInteger: isIntegralNumber,
207 isNaN: isNan,
208 } = Number;
209 const { is } = Object;
210 return {
211 isIntegerIndexString: ($) => {
212 const value = canonicalNumericIndexString($);
213 if (value !== undefined && isIntegralNumber(value)) {
214 // The provided value is an integral canonical numeric index
215 // string.
216 return sameValue(value, 0) ||
217 value > 0 && value <= MAXIMUM_SAFE_INTEGRAL_NUMBER &&
218 value === toLength(value);
219 } else {
220 // The provided value is not an integral canonical numeric
221 // index string.
222 return false;
223 }
224 },
225 sameValue: (a, b) => is(a, b),
226 sameValueZero: ($1, $2) => {
227 const type1 = type($1);
228 const type2 = type($2);
229 if (type1 !== type2) {
230 // The provided values are not of the same type.
231 return false;
232 } else if (type1 === "number") {
233 // The provided values are numbers; check if they are nan and
234 // use strict equality otherwise.
235 return isNan($1) && isNan($2) || $1 === $2;
236 } else {
237 // The provided values are not numbers; use strict equality.
238 return $1 === $2;
239 }
240 },
241 toIndex: ($) => {
242 const integer = floor($);
243 if (isNan(integer) || integer == 0) {
244 // The value is zero·like.
245 return 0;
246 } else {
247 // The value is not zero·like.
248 const clamped = toLength(integer);
249 if (clamped !== integer) {
250 // Clamping the value changes it.
251 throw new RangeError(`Piscēs: Index out of range: ${$}.`);
252 } else {
253 // The value is within appropriate bounds.
254 return integer;
255 }
256 }
257 },
258 toLength: ($) => {
259 const len = floor($);
260 return isNan(len) || len == 0
261 ? 0
262 : max(min(len, MAXIMUM_SAFE_INTEGRAL_NUMBER), 0);
263 },
264 };
265 })();
266
267 /**
268 * Returns a lowercase string identifying the type of the provided
269 * value.
270 *
271 * This differs from the value of the `typeof` operator only in the
272 * cases of objects and null.
273 */
274 export const type = ($) => {
275 if ($ === null) {
276 // The provided value is null.
277 return "null";
278 } else {
279 // The provided value is not null.
280 const type·of = typeof $;
281 return type·of === "function" ? "object" : type·of;
282 }
283 };
This page took 0.082294 seconds and 5 git commands to generate.