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/>. 
  14   createCallableFunction
, 
  16 } from "./function.js"; 
  18   arrayIteratorFunction
, 
  19   stringIteratorFunction
, 
  20 } from "./iterable.js"; 
  23   getOwnPropertyDescriptors
, 
  28 const { prototype: rePrototype 
} = RE
; 
  29 const { prototype: arrayPrototype 
} = Array
; 
  30 const { prototype: stringPrototype 
} = String
; 
  32 const { exec: reExec 
} = rePrototype
; 
  36    * A `RegExp`like object which only matches entire strings, and may 
  37    * have additional constraints specified. 
  39    * Matchers are callable objects and will return true if they are 
  40    * called with a string that they match, and false otherwise. 
  41    * Matchers will always return false if called with nonstrings, 
  42    * although other methods like `::exec` coerce their arguments and 
  43    * may still return true. 
  47   const { toString: reToString 
} = rePrototype
; 
  49     Object
.getOwnPropertyDescriptor(rePrototype
, "dotAll").get; 
  51     Object
.getOwnPropertyDescriptor(rePrototype
, "flags").get; 
  53     Object
.getOwnPropertyDescriptor(rePrototype
, "global").get; 
  55     Object
.getOwnPropertyDescriptor(rePrototype
, "hasIndices").get; 
  57     Object
.getOwnPropertyDescriptor(rePrototype
, "ignoreCase").get; 
  59     Object
.getOwnPropertyDescriptor(rePrototype
, "multiline").get; 
  61     Object
.getOwnPropertyDescriptor(rePrototype
, "source").get; 
  63     Object
.getOwnPropertyDescriptor(rePrototype
, "sticky").get; 
  65     Object
.getOwnPropertyDescriptor(rePrototype
, "unicode").get; 
  67   const Matcher 
= class extends identity 
{ 
  72      * Constructs a new `Matcher` from the provided source. 
  74      * If the provided source is a regular expression, then it must 
  75      * have the unicode flag set. Otherwise, it is interpreted as the 
  76      * string source of a regular expression with the unicode flag set. 
  78      * Other flags are taken from the provided regular expression 
  79      * object, if any are present. 
  81      * A name for the matcher may be provided as the second argument. 
  83      * A callable constraint on acceptable inputs may be provided as a 
  84      * third argument. If provided, it will be called with three 
  85      * arguments whenever a match appears successful: first, the string 
  86      * being matched, second, the match result, and third, the 
  87      * `Matcher` object itself. If the return value of this call is 
  88      * falsey, then the match will be considered a failure. 
  90      * ☡ If the provided source regular expression uses nongreedy 
  91      * quantifiers, it may not match the whole string even if a match 
  92      * with the whole string is possible. Surround the regular 
  93      * expression with `^(?:` and `)$` if you don’t want nongreedy 
  94      * regular expressions to fail when shorter matches are possible. 
  96     constructor(source
, name 
= undefined, constraint 
= null) { 
  99           if (typeof $ !== "string") { 
 100             // The provided value is not a string. 
 103             // The provided value is a string. Set the `.lastIndex` of 
 104             // the regular expression to 0 and see if the first attempt 
 105             // at a match matches the whole string and passes the 
 106             // provided constraint (if present). 
 107             regExp
.lastIndex 
= 0; 
 108             const result 
= call(reExec
, regExp
, [$]); 
 109             return result
?.[0] === $ && 
 110               (constraint 
=== null || constraint($, result
, this)); 
 114       const regExp 
= this.#regExp 
= (() => { 
 116           call(reExec
, source
, [""]); // throws if source not a RegExp 
 118           return new RE(`${source}`, "u"); 
 120         const unicode 
= call(getUnicode
, source
, []); 
 122           // The provided regular expression does not have a unicode 
 125             `Piscēs: Cannot create Matcher from non‐Unicode RegExp: ${source}`, 
 128           // The provided regular expression has a unicode flag. 
 129           return new RE(source
); 
 132       if (constraint 
!== null && typeof constraint 
!== "function") { 
 134           "Piscēs: Cannot construct Matcher: Constraint is not callable.", 
 137         this.#constraint 
= constraint
; 
 138         return defineOwnProperties( 
 139           setPrototype(this, matcherPrototype
), 
 150                 : `Matcher(${call(reToString, regExp, [])})`, 
 157     /** Gets whether the dot‐all flag is present on this `Matcher`. */ 
 159       return call(getDotAll
, this.#regExp
, []); 
 163      * Executes this `Matcher` on the provided value and returns the 
 164      * result if there is a match, or null otherwise. 
 166      * Matchers only match if they can match the entire value on the 
 169      * ☡ The match result returned by this method will be the same as 
 170      * that passed to the constraint function—and may have been 
 171      * modified by said function prior to being returned. 
 174       const regExp 
= this.#regExp
; 
 175       const constraint 
= this.#constraint
; 
 176       const string 
= `${$}`; 
 177       regExp
.lastIndex 
= 0; 
 178       const result 
= call(reExec
, regExp
, [string
]); 
 180         result
?.[0] === string 
&& 
 181         (constraint 
=== null || constraint(string
, result
, this)) 
 183         // The entire string was matched and the constraint, if 
 184         // present, returned a truthy value. 
 187         // The entire string was not matched or the constraint returned 
 194      * Gets the flags present on this `Matcher`. 
 196      * ※ This needs to be defined because the internal `RegExp` object 
 197      * may have flags which are not yet recognized by ♓🌟 Piscēs. 
 200       return call(getFlags
, this.#regExp
, []); 
 203     /** Gets whether the global flag is present on this `Matcher`. */ 
 205       return call(getGlobal
, this.#regExp
, []); 
 209      * Gets whether the has‐indices flag is present on this `Matcher`. 
 212       return call(getHasIndices
, this.#regExp
, []); 
 216      * Gets whether the ignore‐case flag is present on this `Matcher`. 
 219       return call(getIgnoreCase
, this.#regExp
, []); 
 223      * Gets whether the multiline flag is present on this `Matcher`. 
 226       return call(getMultiline
, this.#regExp
, []); 
 229     /** Gets the regular expression source for this `Matcher`. */ 
 231       return call(getSource
, this.#regExp
, []); 
 234     /** Gets whether the sticky flag is present on this `Matcher`. */ 
 236       return call(getSticky
, this.#regExp
, []); 
 240      * Gets whether the unicode flag is present on this `Matcher`. 
 242      * ※ This will always be true. 
 245       return call(getUnicode
, this.#regExp
, []); 
 249   const matcherConstructor 
= defineOwnProperties( 
 250     class extends RegExp 
{ 
 251       constructor(...args
) { 
 252         return new Matcher(...args
); 
 256       name: { value: "Matcher" }, 
 257       length: { value: 1 }, 
 260   const matcherPrototype 
= defineOwnProperties( 
 261     matcherConstructor
.prototype, 
 262     getOwnPropertyDescriptors(Matcher
.prototype), 
 263     { constructor: { value: matcherConstructor 
} }, 
 266   return { Matcher: matcherConstructor 
}; 
 271    * Returns the result of converting the provided value to A·S·C·I·I 
 277    * Returns the result of converting the provided value to A·S·C·I·I 
 283     toLowerCase: stringToLowercase
, 
 284     toUpperCase: stringToUppercase
, 
 287     asciiLowercase: ($) => 
 291         createCallableFunction(stringToLowercase
), 
 293     asciiUppercase: ($) => 
 297         createCallableFunction(stringToUppercase
), 
 304    * Returns an iterator over the codepoints in the string representation 
 305    * of the provided value according to the algorithm of 
 306    * `String::[Symbol.iterator]`. 
 311    * Returns an iterator over the code units in the string 
 312    * representation of the provided value. 
 317    * Returns an iterator over the codepoints in the string 
 318    * representation of the provided value. 
 323    * Returns an iterator over the scalar values in the string 
 324    * representation of the provided value. 
 326    * Codepoints which are not valid Unicode scalar values are replaced 
 331   const generateCharacters 
= function* (character
) { 
 334   const generateCodeUnits 
= function* (ucsCharacter
) { 
 335     yield getCodeUnit(ucsCharacter
, 0); 
 337   const generateCodepoints 
= function* (character
) { 
 338     const { allowSurrogates 
} = this; 
 339     const codepoint 
= getCodepoint(character
, 0); 
 340     yield allowSurrogates 
|| codepoint 
<= 0xD7FF || codepoint 
>= 0xE000 
 345   const charactersIterator 
= stringIteratorFunction( 
 347     "String Character Iterator", 
 349   const codeUnitsIterator 
= arrayIteratorFunction( 
 351     "String Code Unit Iterator", 
 353   const codepointsIterator 
= stringIteratorFunction( 
 354     bind(generateCodepoints
, { allowSurrogates: true }, []), 
 355     "String Codepoint Iterator", 
 357   const scalarValuesIterator 
= stringIteratorFunction( 
 358     bind(generateCodepoints
, { allowSurrogates: false }, []), 
 359     "String Scalar Value Iterator", 
 363     characters: ($) => charactersIterator(`${$}`), 
 364     codeUnits: ($) => codeUnitsIterator(`${$}`), 
 365     codepoints: ($) => codepointsIterator(`${$}`), 
 366     scalarValues: ($) => scalarValuesIterator(`${$}`), 
 371  * Returns the character at the provided position in the string 
 372  * representation of the provided value according to the algorithm of 
 373  * `String::codePointAt`. 
 375 export const getCharacter 
= ($, pos
) => { 
 376   const codepoint 
= getCodepoint($, pos
); 
 377   return codepoint 
== null 
 379     : stringFromCodepoints(codepoint
); 
 383  * Returns the code unit at the provided position in the string 
 384  * representation of the provided value according to the algorithm of 
 385  * `String::charAt`, except that out‐of‐bounds values return undefined 
 392    * Returns the result of catenating the string representations of the 
 393    * provided values, returning a new string according to the algorithm 
 394    * of `String::concat`. 
 396    * ※ If no arguments are given, this function returns the empty 
 397    * string. This is different behaviour than if an explicit undefined 
 398    * first argument is given, in which case the resulting string will 
 399    * begin with `"undefined"`. 
 403   const { charCodeAt
, concat 
} = String
.prototype; 
 404   const { isNaN: isNan 
} = Number
; 
 407     getCodeUnit: ($, n
) => { 
 408       const codeUnit 
= call(charCodeAt
, $, [n
]); 
 409       return isNan(codeUnit
) ? undefined : codeUnit
; 
 411     stringCatenate: defineOwnProperties( 
 412       (...args
) => call(concat
, "", args
), 
 413       { name: { value: "stringCatenate" }, length: { value: 2 } }, 
 419  * Returns the codepoint at the provided position in the string 
 420  * representation of the provided value according to the algorithm of 
 421  * `String::codePointAt`. 
 423 export const getCodepoint 
= createCallableFunction( 
 424   stringPrototype
.codePointAt
, 
 425   { name: "getCodepoint" }, 
 429  * Returns the index of the first occurrence of the search string in 
 430  * the string representation of the provided value according to the 
 431  * algorithm of `String::indexOf`. 
 433 export const getFirstSubstringIndex 
= createCallableFunction( 
 434   stringPrototype
.indexOf
, 
 435   { name: "getFirstSubstringIndex" }, 
 439  * Returns the index of the last occurrence of the search string in the 
 440  * string representation of the provided value according to the 
 441  * algorithm of `String::lastIndexOf`. 
 443 export const getLastSubstringIndex 
= createCallableFunction( 
 444   stringPrototype
.lastIndexOf
, 
 445   { name: "getLastSubstringIndex" }, 
 449  * Returns the result of joining the provided iterable. 
 451  * If no separator is provided, it defaults to ",". 
 453  * If a value is nullish, it will be stringified as the empty string. 
 455 export const join 
= (() => { 
 456   const { join: arrayJoin 
} = arrayPrototype
; 
 457   const join 
= ($, separator
) => 
 461       [separator 
=== undefined ? "," : `${separator}`], 
 467  * Returns a string created from the raw value of the tagged template 
 470  * ※ This is effectively an alias for `String.raw`. 
 472 export const rawString 
= createArrowFunction(String
.raw
, { 
 478    * Returns a string created from the provided code units. 
 480    * ※ This is effectively an alias for `String.fromCharCode`, but 
 481    * with the same error behaviour as `String.fromCodePoint`. 
 483    * ☡ This function throws an error if provided with an argument which 
 484    * is not an integral number from 0 to FFFF₁₆ inclusive. 
 488   const { fromCharCode 
} = String
; 
 489   const { isInteger: isIntegralNumber 
} = Number
; 
 492     stringFromCodeUnits: defineOwnProperties( 
 494         for (let index 
= 0; index 
< codeUnits
.length
; ++index
) { 
 495           // Iterate over each provided code unit and throw if it is 
 497           const nextCU 
= +codeUnits
[index
]; 
 499             !isIntegralNumber(nextCU
) || nextCU 
< 0 || nextCU 
> 0xFFFF 
 501             // The code unit is not an integral number between 0 and 
 503             throw new RangeError( 
 504               `Piscēs: Code unit out of range: ${nextCU}.`, 
 507             // The code unit is acceptable. 
 511         return call(fromCharCode
, undefined, codeUnits
); 
 513       { name: { value: "stringFromCodeUnits" }, length: { value: 1 } }, 
 519  * Returns a string created from the provided codepoints. 
 521  * ※ This is effectively an alias for `String.fromCodePoint`. 
 523  * ☡ This function throws an error if provided with an argument which 
 524  * is not an integral number from 0 to 10FFFF₁₆ inclusive. 
 526 export const stringFromCodepoints 
= createArrowFunction( 
 527   String
.fromCodePoint
, 
 528   { name: "stringFromCodepoints" }, 
 532  * Returns the result of splitting the provided value on A·S·C·I·I 
 535 export const splitOnASCIIWhitespace 
= ($) => 
 536   stringSplit(stripAndCollapseASCIIWhitespace($), " "); 
 539  * Returns the result of splitting the provided value on commas, 
 540  * trimming A·S·C·I·I whitespace from the resulting tokens. 
 542 export const splitOnCommas 
= ($) => 
 544     stripLeadingAndTrailingASCIIWhitespace( 
 547         /[\n\r\t\f ]*,[\n\r\t\f ]*/gu
, 
 555  * Returns whether the string representation of the provided value ends 
 556  * with the provided search string according to the algorithm of 
 557  * `String::endsWith`. 
 559 export const stringEndsWith 
= createCallableFunction( 
 560   stringPrototype
.endsWith
, 
 561   { name: "stringEndsWith" }, 
 565  * Returns whether the string representation of the provided value 
 566  * contains the provided search string according to the algorithm of 
 567  * `String::includes`. 
 569 export const stringIncludes 
= createCallableFunction( 
 570   stringPrototype
.includes
, 
 571   { name: "stringIncludes" }, 
 575  * Returns the result of matching the string representation of the 
 576  * provided value with the provided matcher according to the algorithm 
 577  * of `String::match`. 
 579 export const stringMatch 
= createCallableFunction( 
 580   stringPrototype
.match
, 
 581   { name: "stringMatch" }, 
 585  * Returns the result of matching the string representation of the 
 586  * provided value with the provided matcher according to the algorithm 
 587  * of `String::matchAll`. 
 589 export const stringMatchAll 
= createCallableFunction( 
 590   stringPrototype
.matchAll
, 
 591   { name: "stringMatchAll" }, 
 595  * Returns the normalized form of the string representation of the 
 596  * provided value according to the algorithm of `String::normalize`. 
 598 export const stringNormalize 
= createCallableFunction( 
 599   stringPrototype
.normalize
, 
 600   { name: "stringNormalize" }, 
 604  * Returns the result of padding the end of the string representation 
 605  * of the provided value padded until it is the desired length 
 606  * according to the algorithm of `String::padEnd`. 
 608 export const stringPadEnd 
= createCallableFunction( 
 609   stringPrototype
.padEnd
, 
 610   { name: "stringPadEnd" }, 
 614  * Returns the result of padding the start of the string representation 
 615  * of the provided value padded until it is the desired length 
 616  * according to the algorithm of `String::padStart`. 
 618 export const stringPadStart 
= createCallableFunction( 
 619   stringPrototype
.padStart
, 
 620   { name: "stringPadStart" }, 
 624  * Returns the result of repeating the string representation of the 
 625  * provided value the provided number of times according to the 
 626  * algorithm of `String::repeat`. 
 628 export const stringRepeat 
= createCallableFunction( 
 629   stringPrototype
.repeat
, 
 630   { name: "stringRepeat" }, 
 634  * Returns the result of replacing the string representation of the 
 635  * provided value with the provided replacement, using the provided 
 636  * matcher and according to the algorithm of `String::replace`. 
 638 export const stringReplace 
= createCallableFunction( 
 639   stringPrototype
.replace
, 
 640   { name: "stringReplace" }, 
 644  * Returns the result of replacing the string representation of the 
 645  * provided value with the provided replacement, using the provided 
 646  * matcher and according to the algorithm of `String::replaceAll`. 
 648 export const stringReplaceAll 
= createCallableFunction( 
 649   stringPrototype
.replaceAll
, 
 650   { name: "stringReplaceAll" }, 
 654  * Returns the result of searching the string representation of the 
 655  * provided value using the provided matcher and according to the 
 656  * algorithm of `String::search`. 
 658 export const stringSearch 
= createCallableFunction( 
 659   stringPrototype
.search
, 
 660   { name: "stringSearch" }, 
 664  * Returns a slice of the string representation of the provided value 
 665  * according to the algorithm of `String::slice`. 
 667 export const stringSlice 
= createCallableFunction( 
 668   stringPrototype
.slice
, 
 669   { name: "stringSlice" }, 
 673  * Returns the result of splitting of the string representation of the 
 674  * provided value on the provided separator according to the algorithm 
 675  * of `String::split`. 
 677 export const stringSplit 
= createCallableFunction( 
 678   stringPrototype
.split
, 
 679   { name: "stringSplit" }, 
 683  * Returns whether the string representation of the provided value 
 684  * starts with the provided search string according to the algorithm of 
 685  * `String::startsWith`. 
 687 export const stringStartsWith 
= createCallableFunction( 
 688   stringPrototype
.startsWith
, 
 689   { name: "stringStartsWith" }, 
 693  * Returns the value of the provided string. 
 695  * ※ This is effectively an alias for the `String::valueOf`. 
 697  * ☡ This function throws if the provided argument is not a string and 
 698  * does not have a `[[StringData]]` slot. 
 700 export const stringValue 
= createCallableFunction( 
 701   stringPrototype
.valueOf
, 
 702   { name: "stringValue" }, 
 706  * Returns the result of stripping leading and trailing A·S·C·I·I 
 707  * whitespace from the provided value and collapsing other A·S·C·I·I 
 708  * whitespace in the string representation of the provided value. 
 710 export const stripAndCollapseASCIIWhitespace 
= ($) => 
 711   stripLeadingAndTrailingASCIIWhitespace( 
 720  * Returns the result of stripping leading and trailing A·S·C·I·I 
 721  * whitespace from the string representation of the provided value. 
 723 export const stripLeadingAndTrailingASCIIWhitespace 
= ($) => 
 724   call(reExec
, /^[\n\r\t\f ]*([^]*?)[\n\r\t\f ]*$/u
, [$])[1]; 
 727  * Returns a substring of the string representation of the provided 
 728  * value according to the algorithm of `String::substring`. 
 730 export const substring 
= createCallableFunction( 
 731   stringPrototype
.substring
, 
 735  * Returns the result of converting the provided value to a string of 
 736  * scalar values by replacing (unpaired) surrogate values with 
 739 export const toScalarValueString 
= createCallableFunction( 
 740   String
.prototype.toWellFormed
, 
 741   { name: "toScalarValueString" }, 
 745  * Returns the result of converting the provided value to a string. 
 747  * ☡ This method throws for symbols and other objects without a string 
 750 export const toString 
= ($) => `${$}`;