1 // ♓🌟 Piscēs ∷ string.js 
   2 // ==================================================================== 
   4 // Copyright © 2022 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/>. 
  10 import { bind
, call
, identity
, makeCallable 
} from "./function.js"; 
  20    * A RegExp·like object which only matches entire strings. 
  22    * Matchers are callable objects and will return true if they are 
  23    * called with a string that they match, and false otherwise. 
  24    * Matchers will always return false if called with nonstrings, 
  25    * although other methods like `exec` may still return true. 
  30   const { prototype: rePrototype 
} = RE
; 
  31   const { exec: reExec
, toString: reToString 
} = rePrototype
; 
  33     Object
.getOwnPropertyDescriptor(rePrototype
, "dotAll").get; 
  35     Object
.getOwnPropertyDescriptor(rePrototype
, "global").get; 
  37     Object
.getOwnPropertyDescriptor(rePrototype
, "hasIndices").get; 
  39     Object
.getOwnPropertyDescriptor(rePrototype
, "ignoreCase").get; 
  41     Object
.getOwnPropertyDescriptor(rePrototype
, "multiline").get; 
  43     Object
.getOwnPropertyDescriptor(rePrototype
, "source").get; 
  45     Object
.getOwnPropertyDescriptor(rePrototype
, "sticky").get; 
  47     Object
.getOwnPropertyDescriptor(rePrototype
, "unicode").get; 
  49   const Matcher 
= class extends identity 
{ 
  53      * Constructs a new Matcher from the provided source. 
  55      * If the provided source is a regular expression, then it must 
  56      * have the unicode flag set. Otherwise, it is interpreted as the 
  57      * string source of a regular expression with the unicode flag set. 
  59      * Other flags are taken from the provided regular expression 
  60      * object, if any are present. 
  62      * A name for the matcher may be provided as the second argument. 
  64      * ☡ If the provided source regular expression uses nongreedy 
  65      * quantifiers, it may not match the whole string even if a match 
  66      * with the whole string is possible. Surround the regular 
  67      * expression with `^(?:` and `)$` if you don’t want nongreedy 
  68      * regular expressions to fail when shorter matches are possible. 
  70     constructor(source
, name 
= undefined) { 
  73           if (typeof $ !== "string") { 
  74             // The provided value is not a string. 
  77             // The provided value is a string. Set the `lastIndex` of 
  78             // the regular expression to 0 and see if the first attempt 
  79             // at a match matches the whole string. 
  81             return call(reExec
, regExp
, [$])?.[0] === $; 
  85       const regExp 
= this.#regExp 
= (() => { 
  87           call(reExec
, source
, [""]); // throws if source not a RegExp 
  89           return new RE(`${source}`, "u"); 
  91         const unicode 
= call(getUnicode
, source
, []); 
  93           // The provided regular expression does not have a unicode 
  96             `Piscēs: Cannot create Matcher from non‐Unicode RegExp: ${source}`, 
  99           // The provided regular expression has a unicode flag. 
 100           return new RE(source
); 
 103       return defineOwnProperties( 
 104         setPrototype(this, matcherPrototype
), 
 115               : `Matcher(${call(reToString, regExp, [])})`, 
 121     /** Gets whether the dotAll flag is present on this Matcher. */ 
 123       return call(getDotAll
, this.#regExp
, []); 
 127      * Executes this Matcher on the provided value and returns the 
 128      * result if there is a match, or null otherwise. 
 130      * Matchers only match if they can match the entire value on the 
 134       const regExp 
= this.#regExp
; 
 135       const string 
= `${$}`; 
 136       regExp
.lastIndex 
= 0; 
 137       const result 
= call(reExec
, regExp
, [string
]); 
 138       if (result
?.[0] === string
) { 
 139         // The entire string was matched. 
 142         // The entire string was not matched. 
 147     /** Gets whether the global flag is present on this Matcher. */ 
 149       return call(getGlobal
, this.#regExp
, []); 
 152     /** Gets whether the hasIndices flag is present on this Matcher. */ 
 154       return call(getHasIndices
, this.#regExp
, []); 
 157     /** Gets whether the ignoreCase flag is present on this Matcher. */ 
 159       return call(getIgnoreCase
, this.#regExp
, []); 
 162     /** Gets whether the multiline flag is present on this Matcher. */ 
 164       return call(getMultiline
, this.#regExp
, []); 
 167     /** Gets the regular expression source for this Matcher. */ 
 169       return call(getSource
, this.#regExp
, []); 
 172     /** Gets whether the sticky flag is present on this Matcher. */ 
 174       return call(getSticky
, this.#regExp
, []); 
 178      * Gets whether the unicode flag is present on this Matcher. 
 180      * ※ This will always be true. 
 183       return call(getUnicode
, this.#regExp
, []); 
 186   const matcherPrototype 
= setPrototype( 
 196    * Returns the result of converting the provided value to A·S·C·I·I 
 202    * Returns the result of converting the provided value to A·S·C·I·I 
 208     toLowerCase: stringToLowercase
, 
 209     toUpperCase: stringToUppercase
, 
 210   } = String
.prototype; 
 212     asciiLowercase: ($) => 
 216         makeCallable(stringToLowercase
), 
 218     asciiUppercase: ($) => 
 222         makeCallable(stringToUppercase
), 
 229    * Returns an iterator over the code units in the string 
 230    * representation of the provided value. 
 235    * Returns an iterator over the codepoints in the string 
 236    * representation of the provided value. 
 241    * Returns an iterator over the scalar values in the string 
 242    * representation of the provided value. 
 244    * Codepoints which are not valid Unicode scalar values are replaced 
 250    * Returns the result of converting the provided value to a string of 
 251    * scalar values by replacing (unpaired) surrogate values with 
 257     iterator: iteratorSymbol
, 
 258     toStringTag: toStringTagSymbol
, 
 260   const { [iteratorSymbol
]: arrayIterator 
} = Array
.prototype; 
 261   const arrayIteratorPrototype 
= Object
.getPrototypeOf( 
 262     [][iteratorSymbol
](), 
 264   const { next: arrayIteratorNext 
} = arrayIteratorPrototype
; 
 265   const iteratorPrototype 
= Object
.getPrototypeOf( 
 266     arrayIteratorPrototype
, 
 268   const { [iteratorSymbol
]: stringIterator 
} = String
.prototype; 
 269   const stringIteratorPrototype 
= Object
.getPrototypeOf( 
 270     ""[iteratorSymbol
](), 
 272   const { next: stringIteratorNext 
} = stringIteratorPrototype
; 
 275    * An iterator object for iterating over code values (either code 
 276    * units or codepoints) in a string. 
 278    * ※ This class is not exposed, although its methods are (through 
 279    * the prototypes of string code value iterator objects). 
 281   const StringCodeValueIterator 
= class extends identity 
{ 
 286      * Constructs a new string code value iterator from the provided 
 289      * If the provided base iterator is an array iterator, this is a 
 290      * code unit iterator.  If the provided iterator is a string 
 291      * iterator and surrogates are allowed, this is a codepoint 
 292      * iterator. If the provided iterator is a string iterator and 
 293      * surrogates are not allowed, this is a scalar value iterator. 
 295     constructor(baseIterator
, allowSurrogates 
= true) { 
 296       super(objectCreate(stringCodeValueIteratorPrototype
)); 
 297       this.#allowSurrogates 
= !!allowSurrogates
; 
 298       this.#baseIterator 
= baseIterator
; 
 301     /** Provides the next code value in the iterator. */ 
 303       const baseIterator 
= this.#baseIterator
; 
 304       switch (getPrototype(baseIterator
)) { 
 305         case arrayIteratorPrototype: { 
 306           // The base iterator is iterating over U·C·S characters. 
 310           } = call(arrayIteratorNext
, baseIterator
, []); 
 312             ? { value: undefined, done: true } 
 313             : { value: getCodeUnit(ucsCharacter
, 0), done: false }; 
 315         case stringIteratorPrototype: { 
 316           // The base iterator is iterating over Unicode characters. 
 320           } = call(stringIteratorNext
, baseIterator
, []); 
 322             // The base iterator has been exhausted. 
 323             return { value: undefined, done: true }; 
 325             // The base iterator provided a character; yield the 
 327             const codepoint 
= getCodepoint(character
, 0); 
 329               value: this.#allowSurrogates 
|| codepoint 
<= 0xD7FF || 
 338           // Should not be possible! 
 340             "Piscēs: Unrecognized base iterator type in %StringCodeValueIterator%.", 
 348     next: stringCodeValueIteratorNext
, 
 349   } = StringCodeValueIterator
.prototype; 
 350   const stringCodeValueIteratorPrototype 
= objectCreate( 
 356         value: stringCodeValueIteratorNext
, 
 359       [toStringTagSymbol
]: { 
 362         value: "String Code Value Iterator", 
 367   const scalarValueIterablePrototype 
= { 
 371           stringCodeValueIteratorNext
, 
 372           new StringCodeValueIterator( 
 373             call(stringIterator
, this.source
, []), 
 384       new StringCodeValueIterator(call(arrayIterator
, `${$}`, [])), 
 386       new StringCodeValueIterator( 
 387         call(stringIterator
, `${$}`, []), 
 391       new StringCodeValueIterator( 
 392         call(stringIterator
, `${$}`, []), 
 395     scalarValueString: ($) => 
 396       stringFromCodepoints(...objectCreate( 
 397         scalarValueIterablePrototype
, 
 398         { source: { value: `${$}` } }, 
 404  * Returns an iterator over the codepoints in the string representation 
 405  * of the provided value according to the algorithm of 
 406  * String::[Symbol.iterator]. 
 408 export const characters 
= makeCallable( 
 409   String
.prototype[Symbol
.iterator
], 
 413  * Returns the character at the provided position in the string 
 414  * representation of the provided value according to the algorithm of 
 415  * String::codePointAt. 
 417 export const getCharacter 
= ($, pos
) => { 
 418   const codepoint 
= getCodepoint($, pos
); 
 419   return codepoint 
== null 
 421     : stringFromCodepoints(codepoint
); 
 425  * Returns the code unit at the provided position in the string 
 426  * representation of the provided value according to the algorithm of 
 429 export const getCodeUnit 
= makeCallable(String
.prototype.charCodeAt
); 
 432  * Returns the codepoint at the provided position in the string 
 433  * representation of the provided value according to the algorithm of 
 434  * String::codePointAt. 
 436 export const getCodepoint 
= makeCallable(String
.prototype.codePointAt
); 
 439  * Returns the index of the first occurrence of the search string in 
 440  * the string representation of the provided value according to the 
 441  * algorithm of String::indexOf. 
 443 export const getFirstSubstringIndex 
= makeCallable( 
 444   String
.prototype.indexOf
, 
 448  * Returns the index of the last occurrence of the search string in the 
 449  * string representation of the provided value according to the 
 450  * algorithm of String::lastIndexOf. 
 452 export const getLastSubstringIndex 
= makeCallable( 
 453   String
.prototype.lastIndexOf
, 
 457  * Returns the result of joining the provided iterable. 
 459  * If no separator is provided, it defaults to ",". 
 461  * If a value is nullish, it will be stringified as the empty string. 
 463 export const join 
= (() => { 
 464   const { join: arrayJoin 
} = Array
.prototype; 
 465   const join 
= ($, separator 
= ",") => 
 466     call(arrayJoin
, [...$], [`${separator}`]); 
 472    * Returns a string created from the raw value of the tagged template 
 475    * ※ This is an alias for String.raw. 
 480    * Returns a string created from the provided code units. 
 482    * ※ This is an alias for String.fromCharCode. 
 484   fromCharCode: stringFromCodeUnits
, 
 487    * Returns a string created from the provided codepoints. 
 489    * ※ This is an alias for String.fromCodePoint. 
 491   fromCodePoint: stringFromCodepoints
, 
 495  * Returns the result of splitting the provided value on A·S·C·I·I 
 498 export const splitOnASCIIWhitespace 
= ($) => 
 499   stringSplit(stripAndCollapseASCIIWhitespace($), " "); 
 502  * Returns the result of splitting the provided value on commas, 
 503  * trimming A·S·C·I·I whitespace from the resulting tokens. 
 505 export const splitOnCommas 
= ($) => 
 507     stripLeadingAndTrailingASCIIWhitespace( 
 510         /[\n\r\t\f ]*,[\n\r\t\f ]*/gu
, 
 518  * Returns the result of catenating the string representations of the 
 519  * provided values, returning a new string according to the algorithm 
 522 export const stringCatenate 
= makeCallable(String
.prototype.concat
); 
 525  * Returns whether the string representation of the provided value ends 
 526  * with the provided search string according to the algorithm of 
 529 export const stringEndsWith 
= makeCallable(String
.prototype.endsWith
); 
 532  * Returns whether the string representation of the provided value 
 533  * contains the provided search string according to the algorithm of 
 536 export const stringIncludes 
= makeCallable(String
.prototype.includes
); 
 539  * Returns the result of matching the string representation of the 
 540  * provided value with the provided matcher according to the algorithm 
 543 export const stringMatch 
= makeCallable(String
.prototype.match
); 
 546  * Returns the result of matching the string representation of the 
 547  * provided value with the provided matcher according to the algorithm 
 548  * of String::matchAll. 
 550 export const stringMatchAll 
= makeCallable(String
.prototype.matchAll
); 
 553  * Returns the normalized form of the string representation of the 
 554  * provided value according to the algorithm of String::matchAll. 
 556 export const stringNormalize 
= makeCallable( 
 557   String
.prototype.normalize
, 
 561  * Returns the result of padding the end of the string representation 
 562  * of the provided value padded until it is the desired length 
 563  * according to the algorithm of String::padEnd. 
 565 export const stringPadEnd 
= makeCallable(String
.prototype.padEnd
); 
 568  * Returns the result of padding the start of the string representation 
 569  * of the provided value padded until it is the desired length 
 570  * according to the algorithm of String::padStart. 
 572 export const stringPadStart 
= makeCallable(String
.prototype.padStart
); 
 575  * Returns the result of repeating the string representation of the 
 576  * provided value the provided number of times according to the 
 577  * algorithm of String::repeat. 
 579 export const stringRepeat 
= makeCallable(String
.prototype.repeat
); 
 582  * Returns the result of replacing the string representation of the 
 583  * provided value with the provided replacement, using the provided 
 584  * matcher and according to the algorithm of String::replace. 
 586 export const stringReplace 
= makeCallable(String
.prototype.replace
); 
 589  * Returns the result of replacing the string representation of the 
 590  * provided value with the provided replacement, using the provided 
 591  * matcher and according to the algorithm of String::replaceAll. 
 593 export const stringReplaceAll 
= makeCallable( 
 594   String
.prototype.replaceAll
, 
 598  * Returns the result of searching the string representation of the 
 599  * provided value using the provided matcher and according to the 
 600  * algorithm of String::search. 
 602 export const stringSearch 
= makeCallable(String
.prototype.search
); 
 605  * Returns a slice of the string representation of the provided value 
 606  * according to the algorithm of String::slice. 
 608 export const stringSlice 
= makeCallable(String
.prototype.slice
); 
 611  * Returns the result of splitting of the string representation of the 
 612  * provided value on the provided separator according to the algorithm 
 615 export const stringSplit 
= makeCallable(String
.prototype.split
); 
 618  * Returns whether the string representation of the provided value 
 619  * starts with the provided search string according to the algorithm of 
 620  * String::startsWith. 
 622 export const stringStartsWith 
= makeCallable( 
 623   String
.prototype.startsWith
, 
 627  * Returns the `[[StringData]]` of the provided value. 
 629  * ☡ This function will throw if the provided object does not have a 
 630  * `[[StringData]]` internal slot. 
 632 export const stringValue 
= makeCallable(String
.prototype.valueOf
); 
 635  * Returns the result of stripping leading and trailing A·S·C·I·I 
 636  * whitespace from the provided value and collapsing other A·S·C·I·I 
 637  * whitespace in the string representation of the provided value. 
 639 export const stripAndCollapseASCIIWhitespace 
= ($) => 
 640   stripLeadingAndTrailingASCIIWhitespace( 
 649  * Returns the result of stripping leading and trailing A·S·C·I·I 
 650  * whitespace from the string representation of the provided value. 
 652 export const stripLeadingAndTrailingASCIIWhitespace 
= (() => { 
 653   const { exec: reExec 
} = RegExp
.prototype; 
 655     call(reExec
, /^[\n\r\t\f ]*([^]*?)[\n\r\t\f ]*$/u
, [$])[1]; 
 659  * Returns a substring of the string representation of the provided 
 660  * value according to the algorithm of String::substring. 
 662 export const substring 
= makeCallable(String
.prototype.substring
); 
 665  * Returns the result of converting the provided value to a string. 
 667  * ☡ This method throws for symbols and other objects without a string 
 670 export const toString 
= ($) => `${$}`;