1 // ♓🌟 Piscēs ∷ string.js
2 // ====================================================================
4 // Copyright © 2022–2023 Lady [@ Lady’s Computer].
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/>.
13 createCallableFunction
,
15 } from "./function.js";
17 arrayIteratorFunction
,
18 stringIteratorFunction
,
19 } from "./iterable.js";
22 getOwnPropertyDescriptors
,
27 import { ITERATOR
} from "./value.js";
30 const { prototype: rePrototype
} = RE
;
31 const { prototype: arrayPrototype
} = Array
;
32 const { prototype: stringPrototype
} = String
;
34 const { exec
: reExec
} = rePrototype
;
38 * A `RegExp`like object which only matches entire strings, and may
39 * have additional constraints specified.
41 * Matchers are callable objects and will return true if they are
42 * called with a string that they match, and false otherwise.
43 * Matchers will always return false if called with nonstrings,
44 * although other methods like `::exec` coerce their arguments and
45 * may still return true.
49 const { toString
: reToString
} = rePrototype
;
51 Object
.getOwnPropertyDescriptor(rePrototype
, "dotAll").get;
53 Object
.getOwnPropertyDescriptor(rePrototype
, "flags").get;
55 Object
.getOwnPropertyDescriptor(rePrototype
, "global").get;
57 Object
.getOwnPropertyDescriptor(rePrototype
, "hasIndices").get;
59 Object
.getOwnPropertyDescriptor(rePrototype
, "ignoreCase").get;
61 Object
.getOwnPropertyDescriptor(rePrototype
, "multiline").get;
63 Object
.getOwnPropertyDescriptor(rePrototype
, "source").get;
65 Object
.getOwnPropertyDescriptor(rePrototype
, "sticky").get;
67 Object
.getOwnPropertyDescriptor(rePrototype
, "unicode").get;
69 const Matcher
= class extends identity
{
74 * Constructs a new `Matcher` from the provided source.
76 * If the provided source is a regular expression, then it must
77 * have the unicode flag set. Otherwise, it is interpreted as the
78 * string source of a regular expression with the unicode flag set.
80 * Other flags are taken from the provided regular expression
81 * object, if any are present.
83 * A name for the matcher may be provided as the second argument.
85 * A callable constraint on acceptable inputs may be provided as a
86 * third argument. If provided, it will be called with three
87 * arguments whenever a match appears successful: first, the string
88 * being matched, second, the match result, and third, the
89 * `Matcher` object itself. If the return value of this call is
90 * falsey, then the match will be considered a failure.
92 * ☡ If the provided source regular expression uses nongreedy
93 * quantifiers, it may not match the whole string even if a match
94 * with the whole string is possible. Surround the regular
95 * expression with `^(?:` and `)$` if you don’t want nongreedy
96 * regular expressions to fail when shorter matches are possible.
98 constructor(source
, name
= undefined, constraint
= null) {
101 if (typeof $ !== "string") {
102 // The provided value is not a string.
105 // The provided value is a string. Set the `.lastIndex` of
106 // the regular expression to 0 and see if the first attempt
107 // at a match matches the whole string and passes the
108 // provided constraint (if present).
109 regExp
.lastIndex
= 0;
110 const result
= call(reExec
, regExp
, [$]);
111 return result
?.[0] === $ &&
112 (constraint
=== null || constraint($, result
, this));
116 const regExp
= this.#regExp
= (() => {
118 call(reExec
, source
, [""]); // throws if source not a RegExp
120 return new RE(`${source}`, "u");
122 const unicode
= call(getUnicode
, source
, []);
124 // The provided regular expression does not have a unicode
127 `Piscēs: Cannot create Matcher from non‐Unicode RegExp: ${source}`,
130 // The provided regular expression has a unicode flag.
131 return new RE(source
);
134 if (constraint
!== null && typeof constraint
!== "function") {
136 "Piscēs: Cannot construct Matcher: Constraint is not callable.",
139 this.#constraint
= constraint
;
140 return defineOwnProperties(
141 setPrototype(this, matcherPrototype
),
152 : `Matcher(${call(reToString, regExp, [])})`,
159 /** Gets whether the dot‐all flag is present on this `Matcher`. */
161 return call(getDotAll
, this.#regExp
, []);
165 * Executes this `Matcher` on the provided value and returns the
166 * result if there is a match, or null otherwise.
168 * Matchers only match if they can match the entire value on the
171 * ☡ The match result returned by this method will be the same as
172 * that passed to the constraint function—and may have been
173 * modified by said function prior to being returned.
176 const regExp
= this.#regExp
;
177 const constraint
= this.#constraint
;
178 const string
= `${$}`;
179 regExp
.lastIndex
= 0;
180 const result
= call(reExec
, regExp
, [string
]);
182 result
?.[0] === string
&&
183 (constraint
=== null || constraint(string
, result
, this))
185 // The entire string was matched and the constraint, if
186 // present, returned a truthy value.
189 // The entire string was not matched or the constraint returned
196 * Gets the flags present on this `Matcher`.
198 * ※ This needs to be defined because the internal `RegExp` object
199 * may have flags which are not yet recognized by ♓🌟 Piscēs.
202 return call(getFlags
, this.#regExp
, []);
205 /** Gets whether the global flag is present on this `Matcher`. */
207 return call(getGlobal
, this.#regExp
, []);
211 * Gets whether the has‐indices flag is present on this `Matcher`.
214 return call(getHasIndices
, this.#regExp
, []);
218 * Gets whether the ignore‐case flag is present on this `Matcher`.
221 return call(getIgnoreCase
, this.#regExp
, []);
225 * Gets whether the multiline flag is present on this `Matcher`.
228 return call(getMultiline
, this.#regExp
, []);
231 /** Gets the regular expression source for this `Matcher`. */
233 return call(getSource
, this.#regExp
, []);
236 /** Gets whether the sticky flag is present on this `Matcher`. */
238 return call(getSticky
, this.#regExp
, []);
242 * Gets whether the unicode flag is present on this `Matcher`.
244 * ※ This will always be true.
247 return call(getUnicode
, this.#regExp
, []);
251 const matcherConstructor
= defineOwnProperties(
252 class extends RegExp
{
253 constructor(...args
) {
254 return new Matcher(...args
);
258 name
: { value
: "Matcher" },
259 length
: { value
: 1 },
262 const matcherPrototype
= defineOwnProperties(
263 matcherConstructor
.prototype,
264 getOwnPropertyDescriptors(Matcher
.prototype),
265 { constructor: { value
: matcherConstructor
} },
268 return { Matcher
: matcherConstructor
};
273 * Returns the result of converting the provided value to A·S·C·I·I
279 * Returns the result of converting the provided value to A·S·C·I·I
285 toLowerCase
: stringToLowercase
,
286 toUpperCase
: stringToUppercase
,
289 asciiLowercase
: ($) =>
293 createCallableFunction(stringToLowercase
),
295 asciiUppercase
: ($) =>
299 createCallableFunction(stringToUppercase
),
306 * Returns an iterator over the code units in the string
307 * representation of the provided value.
312 * Returns an iterator over the codepoints in the string
313 * representation of the provided value.
318 * Returns an iterator over the scalar values in the string
319 * representation of the provided value.
321 * Codepoints which are not valid Unicode scalar values are replaced
327 * Returns the result of converting the provided value to a string of
328 * scalar values by replacing (unpaired) surrogate values with
333 const generateCodeUnits
= function* (ucsCharacter
) {
334 yield getCodeUnit(ucsCharacter
, 0);
336 const generateCodepoints
= function* (character
) {
337 const { allowSurrogates
} = this;
338 const codepoint
= getCodepoint(character
, 0);
339 yield allowSurrogates
|| codepoint
<= 0xD7FF || codepoint
>= 0xE000
344 const codeUnitsIterator
= arrayIteratorFunction(
346 "String Code Unit Iterator",
348 const codepointsIterator
= stringIteratorFunction(
349 bind(generateCodepoints
, { allowSurrogates
: true }, []),
350 "String Codepoint Iterator",
352 const scalarValuesIterator
= stringIteratorFunction(
353 bind(generateCodepoints
, { allowSurrogates
: false }, []),
354 "String Scalar Value Iterator",
357 next
: scalarValuesNext
,
358 } = getPrototype(scalarValuesIterator(""));
359 const scalarValueIterablePrototype
= {
364 scalarValuesIterator(this.source
),
372 codeUnits
: ($) => codeUnitsIterator(`${$}`),
373 codepoints
: ($) => codepointsIterator(`${$}`),
374 scalarValues
: ($) => scalarValuesIterator(`${$}`),
375 scalarValueString
: ($) =>
376 stringFromCodepoints(...objectCreate(
377 scalarValueIterablePrototype
,
378 { source
: { value
: `${$}` } },
384 * Returns an iterator over the codepoints in the string representation
385 * of the provided value according to the algorithm of
386 * `String::[Symbol.iterator]`.
388 export const characters
= createCallableFunction(
389 stringPrototype
[ITERATOR
],
394 * Returns the character at the provided position in the string
395 * representation of the provided value according to the algorithm of
396 * `String::codePointAt`.
398 export const getCharacter
= ($, pos
) => {
399 const codepoint
= getCodepoint($, pos
);
400 return codepoint
== null
402 : stringFromCodepoints(codepoint
);
406 * Returns the code unit at the provided position in the string
407 * representation of the provided value according to the algorithm of
410 export const getCodeUnit
= createCallableFunction(
411 stringPrototype
.charCodeAt
,
416 * Returns the codepoint at the provided position in the string
417 * representation of the provided value according to the algorithm of
418 * `String::codePointAt`.
420 export const getCodepoint
= createCallableFunction(
421 stringPrototype
.codePointAt
,
426 * Returns the index of the first occurrence of the search string in
427 * the string representation of the provided value according to the
428 * algorithm of `String::indexOf`.
430 export const getFirstSubstringIndex
= createCallableFunction(
431 stringPrototype
.indexOf
,
432 "getFirstSubstringIndex",
436 * Returns the index of the last occurrence of the search string in the
437 * string representation of the provided value according to the
438 * algorithm of `String::lastIndexOf`.
440 export const getLastSubstringIndex
= createCallableFunction(
441 stringPrototype
.lastIndexOf
,
442 "getLastSubstringIndex",
446 * Returns the result of joining the provided iterable.
448 * If no separator is provided, it defaults to ",".
450 * If a value is nullish, it will be stringified as the empty string.
452 export const join
= (() => {
453 const { join
: arrayJoin
} = arrayPrototype
;
454 const join
= ($, separator
= ",") =>
455 call(arrayJoin
, [...$], [`${separator}`]);
461 * Returns a string created from the raw value of the tagged template
464 * ※ This is an alias for `String.raw`.
469 * Returns a string created from the provided code units.
471 * ※ This is an alias for `String.fromCharCode`.
473 fromCharCode
: stringFromCodeUnits
,
476 * Returns a string created from the provided codepoints.
478 * ※ This is an alias for `String.fromCodePoint`.
480 fromCodePoint
: stringFromCodepoints
,
484 * Returns the result of splitting the provided value on A·S·C·I·I
487 export const splitOnASCIIWhitespace
= ($) =>
488 stringSplit(stripAndCollapseASCIIWhitespace($), " ");
491 * Returns the result of splitting the provided value on commas,
492 * trimming A·S·C·I·I whitespace from the resulting tokens.
494 export const splitOnCommas
= ($) =>
496 stripLeadingAndTrailingASCIIWhitespace(
499 /[\n\r\t\f ]*,[\n\r\t\f ]*/gu,
507 * Returns the result of catenating the string representations of the
508 * provided values, returning a new string according to the algorithm
509 * of `String::concat`.
511 export const stringCatenate
= createCallableFunction(
512 stringPrototype
.concat
,
517 * Returns whether the string representation of the provided value ends
518 * with the provided search string according to the algorithm of
519 * `String::endsWith`.
521 export const stringEndsWith
= createCallableFunction(
522 stringPrototype
.endsWith
,
527 * Returns whether the string representation of the provided value
528 * contains the provided search string according to the algorithm of
529 * `String::includes`.
531 export const stringIncludes
= createCallableFunction(
532 stringPrototype
.includes
,
537 * Returns the result of matching the string representation of the
538 * provided value with the provided matcher according to the algorithm
539 * of `String::match`.
541 export const stringMatch
= createCallableFunction(
542 stringPrototype
.match
,
547 * Returns the result of matching the string representation of the
548 * provided value with the provided matcher according to the algorithm
549 * of `String::matchAll`.
551 export const stringMatchAll
= createCallableFunction(
552 stringPrototype
.matchAll
,
557 * Returns the normalized form of the string representation of the
558 * provided value according to the algorithm of `String::matchAll`.
560 export const stringNormalize
= createCallableFunction(
561 stringPrototype
.normalize
,
566 * Returns the result of padding the end of the string representation
567 * of the provided value padded until it is the desired length
568 * according to the algorithm of `String::padEnd`.
570 export const stringPadEnd
= createCallableFunction(
571 stringPrototype
.padEnd
,
576 * Returns the result of padding the start of the string representation
577 * of the provided value padded until it is the desired length
578 * according to the algorithm of `String::padStart`.
580 export const stringPadStart
= createCallableFunction(
581 stringPrototype
.padStart
,
586 * Returns the result of repeating the string representation of the
587 * provided value the provided number of times according to the
588 * algorithm of `String::repeat`.
590 export const stringRepeat
= createCallableFunction(
591 stringPrototype
.repeat
,
596 * Returns the result of replacing the string representation of the
597 * provided value with the provided replacement, using the provided
598 * matcher and according to the algorithm of `String::replace`.
600 export const stringReplace
= createCallableFunction(
601 stringPrototype
.replace
,
606 * Returns the result of replacing the string representation of the
607 * provided value with the provided replacement, using the provided
608 * matcher and according to the algorithm of `String::replaceAll`.
610 export const stringReplaceAll
= createCallableFunction(
611 stringPrototype
.replaceAll
,
616 * Returns the result of searching the string representation of the
617 * provided value using the provided matcher and according to the
618 * algorithm of `String::search`.
620 export const stringSearch
= createCallableFunction(
621 stringPrototype
.search
,
626 * Returns a slice of the string representation of the provided value
627 * according to the algorithm of `String::slice`.
629 export const stringSlice
= createCallableFunction(
630 stringPrototype
.slice
,
635 * Returns the result of splitting of the string representation of the
636 * provided value on the provided separator according to the algorithm
637 * of `String::split`.
639 export const stringSplit
= createCallableFunction(
640 stringPrototype
.split
,
645 * Returns whether the string representation of the provided value
646 * starts with the provided search string according to the algorithm of
647 * `String::startsWith`.
649 export const stringStartsWith
= createCallableFunction(
650 stringPrototype
.startsWith
,
655 * Returns the `[[StringData]]` of the provided value.
657 * ☡ This function will throw if the provided object does not have a
658 * `[[StringData]]` internal slot.
660 export const stringValue
= createCallableFunction(
661 stringPrototype
.valueOf
,
666 * Returns the result of stripping leading and trailing A·S·C·I·I
667 * whitespace from the provided value and collapsing other A·S·C·I·I
668 * whitespace in the string representation of the provided value.
670 export const stripAndCollapseASCIIWhitespace
= ($) =>
671 stripLeadingAndTrailingASCIIWhitespace(
680 * Returns the result of stripping leading and trailing A·S·C·I·I
681 * whitespace from the string representation of the provided value.
683 export const stripLeadingAndTrailingASCIIWhitespace
= ($) =>
684 call(reExec
, /^[\n\r\t\f ]*([^]*?)[\n\r\t\f ]*$/u, [$])[1];
687 * Returns a substring of the string representation of the provided
688 * value according to the algorithm of `String::substring`.
690 export const substring
= createCallableFunction(
691 stringPrototype
.substring
,
695 * Returns the result of converting the provided value to a string.
697 * ☡ This method throws for symbols and other objects without a string
700 export const toString
= ($) => `${$}`;