]> Lady’s Gitweb - Pisces/blob - value.js
Split some object code into value and unit test
[Pisces] / value.js
1 // ♓🌟 Piscēs ∷ value.js
2 // ====================================================================
3 //
4 // Copyright © 2022 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 { call } from "./function.js";
11
12 /** The null primitive. */
13 export const NULL = null;
14
15 /** The undefined primitive. */
16 export const UNDEFINED = undefined;
17
18 export const {
19 /**
20 * Returns the primitive value of the provided object per its
21 * `toString` and `valueOf` methods.
22 *
23 * If the provided hint is "string", then `toString` takes
24 * precedence; otherwise, `valueOf` does.
25 *
26 * Throws an error if both of these methods are not callable or do
27 * not return a primitive.
28 */
29 ordinaryToPrimitive,
30
31 /**
32 * Returns the provided value converted to a primitive, or throws if
33 * no such conversion is possible.
34 *
35 * The provided preferred type, if specified, should be "string",
36 * "number", or "default". If the provided input has a
37 * `[Symbol.toPrimitive]` method, this function will throw rather
38 * than calling that method with a preferred type other than one of
39 * the above.
40 */
41 toPrimitive,
42 } = (() => {
43 const { toPrimitive: toPrimitiveSymbol } = Symbol;
44
45 return {
46 ordinaryToPrimitive: (O, hint) => {
47 const methodNames = hint == "string"
48 ? ["toString", "valueOf"]
49 : ["valueOf", "toString"];
50 for (let index = 0; index < methodNames.length; ++index) {
51 const method = O[methodNames[index]];
52 if (typeof method === "function") {
53 // Method is callable.
54 const result = call(method, O, []);
55 if (type(result) !== "object") {
56 // Method returns a primitive.
57 return result;
58 } else {
59 // Method returns an object.
60 continue;
61 }
62 } else {
63 // Method is not callable.
64 continue;
65 }
66 }
67 throw new TypeError(
68 "Piscēs: Unable to convert object to primitive",
69 );
70 },
71 toPrimitive: ($, preferredType = "default") => {
72 const hint = `${preferredType}`;
73 if (
74 "default" !== hint && "string" !== hint &&
75 "number" !== hint
76 ) {
77 // An invalid preferred type was specified.
78 throw new TypeError(
79 `Piscēs: Invalid preferred type: ${preferredType}.`,
80 );
81 } else if (type($) === "object") {
82 // The provided value is an object.
83 const exoticToPrim = $[toPrimitiveSymbol] ?? undefined;
84 if (exoticToPrim !== undefined) {
85 // The provided value has an exotic primitive conversion
86 // method.
87 if (typeof exoticToPrim !== "function") {
88 // The method is not callable.
89 throw new TypeError(
90 "Piscēs: `[Symbol.toPrimitive]` was neither nullish nor callable.",
91 );
92 } else {
93 // The method is callable.
94 return call(exoticToPrim, $, [hint]);
95 }
96 } else {
97 // Use the ordinary primitive conversion function.
98 return ordinaryToPrimitive($, hint);
99 }
100 } else {
101 // The provided value is already a primitive.
102 return $;
103 }
104 },
105 };
106 })();
107
108 /**
109 * Returns whether the provided values are the same value.
110 *
111 * ※ This differs from `===` in the cases of nan and zero.
112 */
113 export const sameValue = Object.is;
114
115 export const {
116 /**
117 * Returns whether the provided values are either the same value or
118 * both zero (either positive or negative).
119 *
120 * ※ This differs from `===` in the case of nan.
121 */
122 sameValueZero,
123 } = (() => {
124 const { isNaN: isNan } = Number;
125 return {
126 sameValueZero: ($1, $2) => {
127 const type1 = type($1);
128 const type2 = type($2);
129 if (type1 !== type2) {
130 // The provided values are not of the same type.
131 return false;
132 } else if (type1 === "number") {
133 // The provided values are numbers; check if they are nan and
134 // use strict equality otherwise.
135 return isNan($1) && isNan($2) || $1 === $2;
136 } else {
137 // The provided values are not numbers; use strict equality.
138 return $1 === $2;
139 }
140 },
141 };
142 })();
143
144 /**
145 * Returns a lowercase string identifying the type of the provided
146 * value.
147 *
148 * This differs from the value of the `typeof` operator only in the
149 * cases of objects and null.
150 */
151 export const type = ($) => {
152 if ($ === null) {
153 // The provided value is null.
154 return "null";
155 } else {
156 // The provided value is not null.
157 const type·of = typeof $;
158 return type·of === "function" ? "object" : type·of;
159 }
160 };
This page took 0.186818 seconds and 5 git commands to generate.