]> Lady’s Gitweb - Pisces/blob - string.js
Add string functions and unit tests
[Pisces] / string.js
1 // ♓🌟 Piscēs ∷ string.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 { bind, call, identity, makeCallable } from "./function.js";
11 import { getPrototype, objectCreate } from "./object.js";
12
13 export const {
14 /**
15 * Returns the result of converting the provided value to A·S·C·I·I
16 * lowercase.
17 */
18 asciiLowercase,
19
20 /**
21 * Returns the result of converting the provided value to A·S·C·I·I
22 * uppercase.
23 */
24 asciiUppercase,
25 } = (() => {
26 const {
27 toLowerCase: stringToLowercase,
28 toUpperCase: stringToUppercase,
29 } = String.prototype;
30 return {
31 asciiLowercase: ($) =>
32 stringReplaceAll(
33 `${$}`,
34 /[A-Z]/gu,
35 makeCallable(stringToLowercase),
36 ),
37 asciiUppercase: ($) =>
38 stringReplaceAll(
39 `${$}`,
40 /[a-z]/gu,
41 makeCallable(stringToUppercase),
42 ),
43 };
44 })();
45
46 export const {
47 /**
48 * Returns an iterator over the code units in the string
49 * representation of the provided value.
50 */
51 codeUnits,
52
53 /**
54 * Returns an iterator over the codepoints in the string
55 * representation of the provided value.
56 */
57 codepoints,
58
59 /**
60 * Returns an iterator over the scalar values in the string
61 * representation of the provided value.
62 *
63 * Codepoints which are not valid Unicode scalar values are replaced
64 * with U+FFFF.
65 */
66 scalarValues,
67
68 /**
69 * Returns the result of converting the provided value to a string of
70 * scalar values by replacing (unpaired) surrogate values with
71 * U+FFFD.
72 */
73 scalarValueString,
74 } = (() => {
75 const {
76 iterator: iteratorSymbol,
77 toStringTag: toStringTagSymbol,
78 } = Symbol;
79 const { [iteratorSymbol]: arrayIterator } = Array.prototype;
80 const arrayIteratorPrototype = Object.getPrototypeOf(
81 [][iteratorSymbol](),
82 );
83 const { next: arrayIteratorNext } = arrayIteratorPrototype;
84 const iteratorPrototype = Object.getPrototypeOf(
85 arrayIteratorPrototype,
86 );
87 const { [iteratorSymbol]: stringIterator } = String.prototype;
88 const stringIteratorPrototype = Object.getPrototypeOf(
89 ""[iteratorSymbol](),
90 );
91 const { next: stringIteratorNext } = stringIteratorPrototype;
92
93 /**
94 * An iterator object for iterating over code values (either code
95 * units or codepoints) in a string.
96 *
97 * ※ This constructor is not exposed.
98 */
99 const StringCodeValueIterator = class extends identity {
100 #allowSurrogates;
101 #baseIterator;
102
103 /**
104 * Constructs a new string code value iterator from the provided
105 * base iterator.
106 *
107 * If the provided base iterator is an array iterator, this is a
108 * code unit iterator. If the provided iterator is a string
109 * iterator and surrogates are allowed, this is a codepoint
110 * iterator. If the provided iterator is a string iterator and
111 * surrogates are not allowed, this is a scalar value iterator.
112 */
113 constructor(baseIterator, allowSurrogates = true) {
114 super(objectCreate(stringCodeValueIteratorPrototype));
115 this.#allowSurrogates = !!allowSurrogates;
116 this.#baseIterator = baseIterator;
117 }
118
119 /** Provides the next code value in the iterator. */
120 next() {
121 const baseIterator = this.#baseIterator;
122 switch (getPrototype(baseIterator)) {
123 case arrayIteratorPrototype: {
124 // The base iterator is iterating over U·C·S characters.
125 const {
126 value: ucsCharacter,
127 done,
128 } = call(arrayIteratorNext, baseIterator, []);
129 return done
130 ? { value: undefined, done: true }
131 : { value: getCodeUnit(ucsCharacter, 0), done: false };
132 }
133 case stringIteratorPrototype: {
134 // The base iterator is iterating over Unicode characters.
135 const {
136 value: character,
137 done,
138 } = call(stringIteratorNext, baseIterator, []);
139 if (done) {
140 // The base iterator has been exhausted.
141 return { value: undefined, done: true };
142 } else {
143 // The base iterator provided a character; yield the
144 // codepoint.
145 const codepoint = getCodepoint(character, 0);
146 return {
147 value: this.#allowSurrogates || codepoint <= 0xD7FF ||
148 codepoint >= 0xE000
149 ? codepoint
150 : 0xFFFD,
151 done: false,
152 };
153 }
154 }
155 default: {
156 // Should not be possible!
157 throw new TypeError(
158 "Piscēs: Unrecognized base iterator type in %StringCodeValueIterator%.",
159 );
160 }
161 }
162 }
163 };
164
165 const {
166 next: stringCodeValueIteratorNext,
167 } = StringCodeValueIterator.prototype;
168 const stringCodeValueIteratorPrototype = objectCreate(
169 iteratorPrototype,
170 {
171 next: {
172 configurable: true,
173 enumerable: false,
174 value: stringCodeValueIteratorNext,
175 writable: true,
176 },
177 [toStringTagSymbol]: {
178 configurable: true,
179 enumerable: false,
180 value: "String Code Value Iterator",
181 writable: false,
182 },
183 },
184 );
185 const scalarValueIterablePrototype = {
186 [iteratorSymbol]() {
187 return {
188 next: bind(
189 stringCodeValueIteratorNext,
190 new StringCodeValueIterator(
191 call(stringIterator, this.source, []),
192 false,
193 ),
194 [],
195 ),
196 };
197 },
198 };
199
200 return {
201 codeUnits: ($) =>
202 new StringCodeValueIterator(call(arrayIterator, $, [])),
203 codepoints: ($) =>
204 new StringCodeValueIterator(
205 call(stringIterator, $, []),
206 true,
207 ),
208 scalarValues: ($) =>
209 new StringCodeValueIterator(
210 call(stringIterator, $, []),
211 false,
212 ),
213 scalarValueString: ($) =>
214 stringFromCodepoints(...objectCreate(
215 scalarValueIterablePrototype,
216 { source: { value: $ } },
217 )),
218 };
219 })();
220
221 /**
222 * Returns an iterator over the codepoints in the string representation
223 * of the provided value according to the algorithm of
224 * String::[Symbol.iterator].
225 */
226 export const characters = makeCallable(
227 String.prototype[Symbol.iterator],
228 );
229
230 /**
231 * Returns the character at the provided position in the string
232 * representation of the provided value according to the algorithm of
233 * String::codePointAt.
234 */
235 export const getCharacter = ($, pos) => {
236 const codepoint = getCodepoint($, pos);
237 return codepoint == null
238 ? undefined
239 : stringFromCodepoints(codepoint);
240 };
241
242 /**
243 * Returns the code unit at the provided position in the string
244 * representation of the provided value according to the algorithm of
245 * String::charAt.
246 */
247 export const getCodeUnit = makeCallable(String.prototype.charCodeAt);
248
249 /**
250 * Returns the codepoint at the provided position in the string
251 * representation of the provided value according to the algorithm of
252 * String::codePointAt.
253 */
254 export const getCodepoint = makeCallable(String.prototype.codePointAt);
255
256 /**
257 * Returns the index of the first occurrence of the search string in
258 * the string representation of the provided value according to the
259 * algorithm of String::indexOf.
260 */
261 export const getFirstSubstringIndex = makeCallable(
262 String.prototype.indexOf,
263 );
264
265 /**
266 * Returns the index of the last occurrence of the search string in the
267 * string representation of the provided value according to the
268 * algorithm of String::lastIndexOf.
269 */
270 export const getLastSubstringIndex = makeCallable(
271 String.prototype.lastIndexOf,
272 );
273
274 /**
275 * Returns the result of joining the provided iterable.
276 *
277 * If no separator is provided, it defaults to ",".
278 *
279 * If a value is nullish, it will be stringified as the empty string.
280 */
281 export const join = (() => {
282 const { join: arrayJoin } = Array.prototype;
283 const join = ($, separator = ",") =>
284 call(arrayJoin, [...$], [`${separator}`]);
285 return join;
286 })();
287
288 export const {
289 /**
290 * Returns a string created from the raw value of the tagged template
291 * literal.
292 *
293 * ※ This is an alias for String.raw.
294 */
295 raw: rawString,
296
297 /**
298 * Returns a string created from the provided code units.
299 *
300 * ※ This is an alias for String.fromCharCode.
301 */
302 fromCharCode: stringFromCodeUnits,
303
304 /**
305 * Returns a string created from the provided codepoints.
306 *
307 * ※ This is an alias for String.fromCodePoint.
308 */
309 fromCodePoint: stringFromCodepoints,
310 } = String;
311
312 /**
313 * Returns the result of splitting the provided value on A·S·C·I·I
314 * whitespace.
315 */
316 export const splitOnASCIIWhitespace = ($) =>
317 stringSplit(stripAndCollapseASCIIWhitespace($), " ");
318
319 /**
320 * Returns the result of splitting the provided value on commas,
321 * trimming A·S·C·I·I whitespace from the resulting tokens.
322 */
323 export const splitOnCommas = ($) =>
324 stringSplit(
325 stripLeadingAndTrailingASCIIWhitespace(
326 stringReplaceAll(
327 `${$}`,
328 /[\n\r\t\f ]*,[\n\r\t\f ]*/gu,
329 ",",
330 ),
331 ),
332 ",",
333 );
334
335 /**
336 * Returns the result of catenating the string representations of the
337 * provided values, returning a new string according to the algorithm
338 * of String::concat.
339 */
340 export const stringCatenate = makeCallable(String.prototype.concat);
341
342 /**
343 * Returns whether the string representation of the provided value ends
344 * with the provided search string according to the algorithm of
345 * String::endsWith.
346 */
347 export const stringEndsWith = makeCallable(String.prototype.endsWith);
348
349 /**
350 * Returns whether the string representation of the provided value
351 * contains the provided search string according to the algorithm of
352 * String::includes.
353 */
354 export const stringIncludes = makeCallable(String.prototype.includes);
355
356 /**
357 * Returns the result of matching the string representation of the
358 * provided value with the provided matcher according to the algorithm
359 * of String::match.
360 */
361 export const stringMatch = makeCallable(String.prototype.match);
362
363 /**
364 * Returns the result of matching the string representation of the
365 * provided value with the provided matcher according to the algorithm
366 * of String::matchAll.
367 */
368 export const stringMatchAll = makeCallable(String.prototype.matchAll);
369
370 /**
371 * Returns the normalized form of the string representation of the
372 * provided value according to the algorithm of String::matchAll.
373 */
374 export const stringNormalize = makeCallable(
375 String.prototype.normalize,
376 );
377
378 /**
379 * Returns the result of padding the end of the string representation
380 * of the provided value padded until it is the desired length
381 * according to the algorithm of String::padEnd.
382 */
383 export const stringPadEnd = makeCallable(String.prototype.padEnd);
384
385 /**
386 * Returns the result of padding the start of the string representation
387 * of the provided value padded until it is the desired length
388 * according to the algorithm of String::padStart.
389 */
390 export const stringPadStart = makeCallable(String.prototype.padStart);
391
392 /**
393 * Returns the result of repeating the string representation of the
394 * provided value the provided number of times according to the
395 * algorithm of String::repeat.
396 */
397 export const stringRepeat = makeCallable(String.prototype.repeat);
398
399 /**
400 * Returns the result of replacing the string representation of the
401 * provided value with the provided replacement, using the provided
402 * matcher and according to the algorithm of String::replace.
403 */
404 export const stringReplace = makeCallable(String.prototype.replace);
405
406 /**
407 * Returns the result of replacing the string representation of the
408 * provided value with the provided replacement, using the provided
409 * matcher and according to the algorithm of String::replaceAll.
410 */
411 export const stringReplaceAll = makeCallable(
412 String.prototype.replaceAll,
413 );
414
415 /**
416 * Returns the result of searching the string representation of the
417 * provided value using the provided matcher and according to the
418 * algorithm of String::search.
419 */
420 export const stringSearch = makeCallable(String.prototype.search);
421
422 /**
423 * Returns a slice of the string representation of the provided value
424 * according to the algorithm of String::slice.
425 */
426 export const stringSlice = makeCallable(String.prototype.slice);
427
428 /**
429 * Returns the result of splitting of the string representation of the
430 * provided value on the provided separator according to the algorithm
431 * of String::split.
432 */
433 export const stringSplit = makeCallable(String.prototype.split);
434
435 /**
436 * Returns whether the string representation of the provided value
437 * starts with the provided search string according to the algorithm of
438 * String::startsWith.
439 */
440 export const stringStartsWith = makeCallable(
441 String.prototype.startsWith,
442 );
443
444 /**
445 * Returns the `[[StringData]]` of the provided value.
446 *
447 * ☡ This function will throw if the provided object does not have a
448 * `[[StringData]]` internal slot.
449 */
450 export const stringValue = makeCallable(String.prototype.valueOf);
451
452 /**
453 * Returns the result of stripping leading and trailing A·S·C·I·I
454 * whitespace from the provided value and collapsing other A·S·C·I·I
455 * whitespace in the string representation of the provided value.
456 */
457 export const stripAndCollapseASCIIWhitespace = ($) =>
458 stripLeadingAndTrailingASCIIWhitespace(
459 stringReplaceAll(
460 `${$}`,
461 /[\n\r\t\f ]+/gu,
462 " ",
463 ),
464 );
465
466 /**
467 * Returns the result of stripping leading and trailing A·S·C·I·I
468 * whitespace from the string representation of the provided value.
469 */
470 export const stripLeadingAndTrailingASCIIWhitespace = (() => {
471 const { exec: reExec } = RegExp.prototype;
472 return ($) =>
473 call(reExec, /^[\n\r\t\f ]*([^]*?)[\n\r\t\f ]*$/u, [$])[1];
474 })();
475
476 /**
477 * Returns a substring of the string representation of the provided
478 * value according to the algorithm of String::substring.
479 */
480 export const substring = makeCallable(String.prototype.substring);
This page took 0.083351 seconds and 5 git commands to generate.