]> Lady’s Gitweb - Pisces/blobdiff - string.js
Add methods for own entries and values to object.js
[Pisces] / string.js
index edf18810ee241a20cfede1c6826480ce81e34c20..e8126d14fa44be9155723de05b4720403ee327f9 100644 (file)
--- a/string.js
+++ b/string.js
@@ -10,6 +10,7 @@
 import {
   bind,
   call,
+  createArrowFunction,
   createCallableFunction,
   identity,
 } from "./function.js";
@@ -18,13 +19,14 @@ import {
   stringIteratorFunction,
 } from "./iterable.js";
 import {
+  defineOwnDataProperty,
   defineOwnProperties,
   getOwnPropertyDescriptors,
-  getPrototype,
   objectCreate,
+  setPropertyValues,
   setPrototype,
 } from "./object.js";
-import { ITERATOR } from "./value.js";
+import { sameValue, toLength, UNDEFINED } from "./value.js";
 
 const RE = RegExp;
 const { prototype: rePrototype } = RE;
@@ -95,7 +97,7 @@ export const {
      * expression with `^(?:` and `)$` if you don’t want nongreedy
      * regular expressions to fail when shorter matches are possible.
      */
-    constructor(source, name = undefined, constraint = null) {
+    constructor(source, name = UNDEFINED, constraint = null) {
       super(
         ($) => {
           if (typeof $ !== "string") {
@@ -140,17 +142,19 @@ export const {
         return defineOwnProperties(
           setPrototype(this, matcherPrototype),
           {
-            lastIndex: {
+            lastIndex: setPropertyValues(objectCreate(null), {
               configurable: false,
               enumerable: false,
               value: 0,
               writable: false,
-            },
-            name: {
-              value: name != null
+            }),
+            name: defineOwnDataProperty(
+              objectCreate(null),
+              "value",
+              name != null
                 ? `${name}`
                 : `Matcher(${call(reToString, regExp, [])})`,
-            },
+            ),
           },
         );
       }
@@ -248,21 +252,31 @@ export const {
     }
   };
 
-  const matcherConstructor = defineOwnProperties(
+  const matcherConstructor = Object.defineProperties(
     class extends RegExp {
       constructor(...args) {
         return new Matcher(...args);
       }
     },
     {
-      name: { value: "Matcher" },
-      length: { value: 1 },
+      name: defineOwnDataProperty(
+        Object.create(null),
+        "value",
+        "Matcher",
+      ),
+      length: defineOwnDataProperty(Object.create(null), "value", 1),
     },
   );
   const matcherPrototype = defineOwnProperties(
     matcherConstructor.prototype,
     getOwnPropertyDescriptors(Matcher.prototype),
-    { constructor: { value: matcherConstructor } },
+    {
+      constructor: defineOwnDataProperty(
+        Object.create(null),
+        "value",
+        matcherConstructor,
+      ),
+    },
   );
 
   return { Matcher: matcherConstructor };
@@ -301,7 +315,33 @@ export const {
   };
 })();
 
+/**
+ * Returns −0 if the provided argument is "-0"; returns a number
+ * representing the index if the provided argument is a canonical
+ * numeric index string; otherwise, returns undefined.
+ *
+ * There is no clamping of the numeric index, but note that numbers
+ * above 2^53 − 1 are not safe nor valid integer indices.
+ */
+export const canonicalNumericIndexString = ($) => {
+  if (typeof $ !== "string") {
+    return UNDEFINED;
+  } else if ($ === "-0") {
+    return -0;
+  } else {
+    const n = +$;
+    return $ === `${n}` ? n : UNDEFINED;
+  }
+};
+
 export const {
+  /**
+   * Returns an iterator over the codepoints in the string representation
+   * of the provided value according to the algorithm of
+   * `String::[Symbol.iterator]`.
+   */
+  characters,
+
   /**
    * Returns an iterator over the code units in the string
    * representation of the provided value.
@@ -322,14 +362,10 @@ export const {
    * with U+FFFD.
    */
   scalarValues,
-
-  /**
-   * Returns the result of converting the provided value to a string of
-   * scalar values by replacing (unpaired) surrogate values with
-   * U+FFFD.
-   */
-  scalarValueString,
 } = (() => {
+  const generateCharacters = function* (character) {
+    yield character;
+  };
   const generateCodeUnits = function* (ucsCharacter) {
     yield getCodeUnit(ucsCharacter, 0);
   };
@@ -341,6 +377,10 @@ export const {
       : 0xFFFD;
   };
 
+  const charactersIterator = stringIteratorFunction(
+    generateCharacters,
+    "String Character Iterator",
+  );
   const codeUnitsIterator = arrayIteratorFunction(
     generateCodeUnits,
     "String Code Unit Iterator",
@@ -353,43 +393,15 @@ export const {
     bind(generateCodepoints, { allowSurrogates: false }, []),
     "String Scalar Value Iterator",
   );
-  const {
-    next: scalarValuesNext,
-  } = getPrototype(scalarValuesIterator(""));
-  const scalarValueIterablePrototype = {
-    [ITERATOR]() {
-      return {
-        next: bind(
-          scalarValuesNext,
-          scalarValuesIterator(this.source),
-          [],
-        ),
-      };
-    },
-  };
 
   return {
+    characters: ($) => charactersIterator(`${$}`),
     codeUnits: ($) => codeUnitsIterator(`${$}`),
     codepoints: ($) => codepointsIterator(`${$}`),
     scalarValues: ($) => scalarValuesIterator(`${$}`),
-    scalarValueString: ($) =>
-      stringFromCodepoints(...objectCreate(
-        scalarValueIterablePrototype,
-        { source: { value: `${$}` } },
-      )),
   };
 })();
 
-/**
- * Returns an iterator over the codepoints in the string representation
- * of the provided value according to the algorithm of
- * `String::[Symbol.iterator]`.
- */
-export const characters = createCallableFunction(
-  stringPrototype[ITERATOR],
-  "characters",
-);
-
 /**
  * Returns the character at the provided position in the string
  * representation of the provided value according to the algorithm of
@@ -398,19 +410,83 @@ export const characters = createCallableFunction(
 export const getCharacter = ($, pos) => {
   const codepoint = getCodepoint($, pos);
   return codepoint == null
-    ? undefined
+    ? UNDEFINED
     : stringFromCodepoints(codepoint);
 };
 
-/**
- * Returns the code unit at the provided position in the string
- * representation of the provided value according to the algorithm of
- * `String::charAt`.
- */
-export const getCodeUnit = createCallableFunction(
-  stringPrototype.charCodeAt,
-  "getCodeUnit",
-);
+export const {
+  /**
+   * Returns the code unit at the provided position in the string
+   * representation of the provided value according to the algorithm of
+   * `String::charAt`, except that out‐of‐bounds values return undefined
+   * in place of nan.
+   */
+  getCodeUnit,
+
+  /**
+   * Returns a string created from the provided code units.
+   *
+   * ※ This is effectively an alias for `String.fromCharCode`, but
+   * with the same error behaviour as `String.fromCodePoint`.
+   *
+   * ☡ This function throws an error if provided with an argument which
+   * is not an integral number from 0 to FFFF₁₆ inclusive.
+   */
+  stringFromCodeUnits,
+
+  /**
+   * Returns the result of catenating the string representations of the
+   * provided values, returning a new string according to the algorithm
+   * of `String::concat`.
+   *
+   * ※ If no arguments are given, this function returns the empty
+   * string. This is different behaviour than if an explicit undefined
+   * first argument is given, in which case the resulting string will
+   * begin with `"undefined"`.
+   */
+  stringCatenate,
+} = (() => {
+  const { fromCharCode } = String;
+  const { charCodeAt, concat } = String.prototype;
+  const {
+    isInteger: isIntegralNumber,
+    isNaN: isNan,
+  } = Number;
+
+  return {
+    getCodeUnit: ($, n) => {
+      const codeUnit = call(charCodeAt, $, [n]);
+      return isNan(codeUnit) ? UNDEFINED : codeUnit;
+    },
+    stringCatenate: Object.defineProperties(
+      (...args) => call(concat, "", args),
+      { name: { value: "stringCatenate" }, length: { value: 2 } },
+    ),
+    stringFromCodeUnits: Object.defineProperties(
+      (...codeUnits) => {
+        for (let index = 0; index < codeUnits.length; ++index) {
+          // Iterate over each provided code unit and throw if it is
+          // out of range.
+          const nextCU = +codeUnits[index];
+          if (
+            !isIntegralNumber(nextCU) || nextCU < 0 || nextCU > 0xFFFF
+          ) {
+            // The code unit is not an integral number between 0 and
+            // 0xFFFF.
+            throw new RangeError(
+              `Piscēs: Code unit out of range: ${nextCU}.`,
+            );
+          } else {
+            // The code unit is acceptable.
+            /* do nothing */
+          }
+        }
+        return call(fromCharCode, UNDEFINED, codeUnits);
+      },
+      { name: { value: "stringFromCodeUnits" }, length: { value: 1 } },
+    ),
+  };
+})();
 
 /**
  * Returns the codepoint at the provided position in the string
@@ -419,7 +495,7 @@ export const getCodeUnit = createCallableFunction(
  */
 export const getCodepoint = createCallableFunction(
   stringPrototype.codePointAt,
-  "getCodepoint",
+  { name: "getCodepoint" },
 );
 
 /**
@@ -429,7 +505,7 @@ export const getCodepoint = createCallableFunction(
  */
 export const getFirstSubstringIndex = createCallableFunction(
   stringPrototype.indexOf,
-  "getFirstSubstringIndex",
+  { name: "getFirstSubstringIndex" },
 );
 
 /**
@@ -439,9 +515,37 @@ export const getFirstSubstringIndex = createCallableFunction(
  */
 export const getLastSubstringIndex = createCallableFunction(
   stringPrototype.lastIndexOf,
-  "getLastSubstringIndex",
+  { name: "getLastSubstringIndex" },
 );
 
+/** Returns whether the provided value is an array index. */
+export const isArrayIndexString = ($) => {
+  const value = canonicalNumericIndexString($);
+  if (value !== UNDEFINED) {
+    // The provided value is a canonical numeric index string; return
+    // whether it is in range for array indices.
+    return sameValue(value, 0) ||
+      value === toLength(value) && value > 0 && value < -1 >>> 0;
+  } else {
+    // The provided value is not a canonical numeric index string.
+    return false;
+  }
+};
+
+/** Returns whether the provided value is an integer index string. */
+export const isIntegerIndexString = ($) => {
+  const value = canonicalNumericIndexString($);
+  if (value !== UNDEFINED) {
+    // The provided value is a canonical numeric index string; return
+    // whether it is in range for integer indices.
+    return sameValue(value, 0) ||
+      value === toLength(value) && value > 0;
+  } else {
+    // The provided value is not a canonical numeric index string.
+    return false;
+  }
+};
+
 /**
  * Returns the result of joining the provided iterable.
  *
@@ -451,49 +555,52 @@ export const getLastSubstringIndex = createCallableFunction(
  */
 export const join = (() => {
   const { join: arrayJoin } = arrayPrototype;
-  const join = ($, separator = ",") =>
-    call(arrayJoin, [...$], [`${separator}`]);
+  const join = ($, separator) =>
+    call(
+      arrayJoin,
+      [...$],
+      [separator === UNDEFINED ? "," : `${separator}`],
+    );
   return join;
 })();
 
-export const {
-  /**
-   * Returns a string created from the raw value of the tagged template
-   * literal.
-   *
-   * ※ This is an alias for `String.raw`.
-   */
-  raw: rawString,
-
-  /**
-   * Returns a string created from the provided code units.
-   *
-   * ※ This is an alias for `String.fromCharCode`.
-   */
-  fromCharCode: stringFromCodeUnits,
+/**
+ * Returns a string created from the raw value of the tagged template
+ * literal.
+ *
+ * ※ This is effectively an alias for `String.raw`.
+ */
+export const rawString = createArrowFunction(String.raw, {
+  name: "rawString",
+});
 
-  /**
-   * Returns a string created from the provided codepoints.
-   *
-   * ※ This is an alias for `String.fromCodePoint`.
-   */
-  fromCodePoint: stringFromCodepoints,
-} = String;
+/**
+ * Returns a string created from the provided codepoints.
+ *
+ * ※ This is effectively an alias for `String.fromCodePoint`.
+ *
+ * ☡ This function throws an error if provided with an argument which
+ * is not an integral number from 0 to 10FFFF₁₆ inclusive.
+ */
+export const stringFromCodepoints = createArrowFunction(
+  String.fromCodePoint,
+  { name: "stringFromCodepoints" },
+);
 
 /**
- * Returns the result of splitting the provided value on A·S·C·I·I
+ * Returns the result of splitting the provided value on Ascii
  * whitespace.
  */
-export const splitOnASCIIWhitespace = ($) =>
-  stringSplit(stripAndCollapseASCIIWhitespace($), " ");
+export const splitOnAsciiWhitespace = ($) =>
+  stringSplit(stripAndCollapseAsciiWhitespace($), " ");
 
 /**
  * Returns the result of splitting the provided value on commas,
- * trimming A·S·C·I·I whitespace from the resulting tokens.
+ * trimming Ascii whitespace from the resulting tokens.
  */
 export const splitOnCommas = ($) =>
   stringSplit(
-    stripLeadingAndTrailingASCIIWhitespace(
+    stripLeadingAndTrailingAsciiWhitespace(
       stringReplaceAll(
         `${$}`,
         /[\n\r\t\f ]*,[\n\r\t\f ]*/gu,
@@ -503,16 +610,6 @@ export const splitOnCommas = ($) =>
     ",",
   );
 
-/**
- * Returns the result of catenating the string representations of the
- * provided values, returning a new string according to the algorithm
- * of `String::concat`.
- */
-export const stringCatenate = createCallableFunction(
-  stringPrototype.concat,
-  "stringCatenate",
-);
-
 /**
  * Returns whether the string representation of the provided value ends
  * with the provided search string according to the algorithm of
@@ -520,7 +617,7 @@ export const stringCatenate = createCallableFunction(
  */
 export const stringEndsWith = createCallableFunction(
   stringPrototype.endsWith,
-  "stringEndsWith",
+  { name: "stringEndsWith" },
 );
 
 /**
@@ -530,7 +627,7 @@ export const stringEndsWith = createCallableFunction(
  */
 export const stringIncludes = createCallableFunction(
   stringPrototype.includes,
-  "stringIncludes",
+  { name: "stringIncludes" },
 );
 
 /**
@@ -540,7 +637,7 @@ export const stringIncludes = createCallableFunction(
  */
 export const stringMatch = createCallableFunction(
   stringPrototype.match,
-  "stringMatch",
+  { name: "stringMatch" },
 );
 
 /**
@@ -550,16 +647,16 @@ export const stringMatch = createCallableFunction(
  */
 export const stringMatchAll = createCallableFunction(
   stringPrototype.matchAll,
-  "stringMatchAll",
+  { name: "stringMatchAll" },
 );
 
 /**
  * Returns the normalized form of the string representation of the
- * provided value according to the algorithm of `String::matchAll`.
+ * provided value according to the algorithm of `String::normalize`.
  */
 export const stringNormalize = createCallableFunction(
   stringPrototype.normalize,
-  "stringNormalize",
+  { name: "stringNormalize" },
 );
 
 /**
@@ -569,7 +666,7 @@ export const stringNormalize = createCallableFunction(
  */
 export const stringPadEnd = createCallableFunction(
   stringPrototype.padEnd,
-  "stringPadEnd",
+  { name: "stringPadEnd" },
 );
 
 /**
@@ -579,7 +676,7 @@ export const stringPadEnd = createCallableFunction(
  */
 export const stringPadStart = createCallableFunction(
   stringPrototype.padStart,
-  "stringPadStart",
+  { name: "stringPadStart" },
 );
 
 /**
@@ -589,7 +686,7 @@ export const stringPadStart = createCallableFunction(
  */
 export const stringRepeat = createCallableFunction(
   stringPrototype.repeat,
-  "stringRepeat",
+  { name: "stringRepeat" },
 );
 
 /**
@@ -599,7 +696,7 @@ export const stringRepeat = createCallableFunction(
  */
 export const stringReplace = createCallableFunction(
   stringPrototype.replace,
-  "stringReplace",
+  { name: "stringReplace" },
 );
 
 /**
@@ -609,7 +706,7 @@ export const stringReplace = createCallableFunction(
  */
 export const stringReplaceAll = createCallableFunction(
   stringPrototype.replaceAll,
-  "stringReplaceAll",
+  { name: "stringReplaceAll" },
 );
 
 /**
@@ -619,7 +716,7 @@ export const stringReplaceAll = createCallableFunction(
  */
 export const stringSearch = createCallableFunction(
   stringPrototype.search,
-  "stringSearch",
+  { name: "stringSearch" },
 );
 
 /**
@@ -628,7 +725,7 @@ export const stringSearch = createCallableFunction(
  */
 export const stringSlice = createCallableFunction(
   stringPrototype.slice,
-  "stringSlice",
+  { name: "stringSlice" },
 );
 
 /**
@@ -638,7 +735,7 @@ export const stringSlice = createCallableFunction(
  */
 export const stringSplit = createCallableFunction(
   stringPrototype.split,
-  "stringSplit",
+  { name: "stringSplit" },
 );
 
 /**
@@ -648,27 +745,29 @@ export const stringSplit = createCallableFunction(
  */
 export const stringStartsWith = createCallableFunction(
   stringPrototype.startsWith,
-  "stringStartsWith",
+  { name: "stringStartsWith" },
 );
 
 /**
- * Returns the `[[StringData]]` of the provided value.
+ * Returns the value of the provided string.
  *
- * ☡ This function will throw if the provided object does not have a
- * `[[StringData]]` internal slot.
+ * ※ This is effectively an alias for the `String::valueOf`.
+ *
+ * ☡ This function throws if the provided argument is not a string and
+ * does not have a `[[StringData]]` slot.
  */
 export const stringValue = createCallableFunction(
   stringPrototype.valueOf,
-  "stringValue",
+  { name: "stringValue" },
 );
 
 /**
- * Returns the result of stripping leading and trailing A·S·C·I·I
- * whitespace from the provided value and collapsing other A·S·C·I·I
+ * Returns the result of stripping leading and trailing Ascii
+ * whitespace from the provided value and collapsing other Ascii
  * whitespace in the string representation of the provided value.
  */
-export const stripAndCollapseASCIIWhitespace = ($) =>
-  stripLeadingAndTrailingASCIIWhitespace(
+export const stripAndCollapseAsciiWhitespace = ($) =>
+  stripLeadingAndTrailingAsciiWhitespace(
     stringReplaceAll(
       `${$}`,
       /[\n\r\t\f ]+/gu,
@@ -677,10 +776,10 @@ export const stripAndCollapseASCIIWhitespace = ($) =>
   );
 
 /**
- * Returns the result of stripping leading and trailing A·S·C·I·I
+ * Returns the result of stripping leading and trailing Ascii
  * whitespace from the string representation of the provided value.
  */
-export const stripLeadingAndTrailingASCIIWhitespace = ($) =>
+export const stripLeadingAndTrailingAsciiWhitespace = ($) =>
   call(reExec, /^[\n\r\t\f ]*([^]*?)[\n\r\t\f ]*$/u, [$])[1];
 
 /**
@@ -691,6 +790,16 @@ export const substring = createCallableFunction(
   stringPrototype.substring,
 );
 
+/**
+ * Returns the result of converting the provided value to a string of
+ * scalar values by replacing (unpaired) surrogate values with
+ * U+FFFD.
+ */
+export const toScalarValueString = createCallableFunction(
+  String.prototype.toWellFormed,
+  { name: "toScalarValueString" },
+);
+
 /**
  * Returns the result of converting the provided value to a string.
  *
This page took 0.047848 seconds and 4 git commands to generate.