tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

RegExpGlobalReplaceOpt.h.js (5590B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 // Function template for the following functions:
      6 //   * RegExpGlobalReplaceOptFunc
      7 //   * RegExpGlobalReplaceOptSubst
      8 //   * RegExpGlobalReplaceOptElemBase
      9 // Define the following macro and include this file to declare function:
     10 //   * FUNC_NAME     -- function name (required)
     11 //       e.g.
     12 //         #define FUNC_NAME RegExpGlobalReplaceOpt
     13 // Define one of the following macros (without value) to switch the code:
     14 //   * SUBSTITUTION     -- replaceValue is a string with "$"
     15 //   * FUNCTIONAL       -- replaceValue is a function
     16 //   * ELEMBASE         -- replaceValue is a function that returns an element
     17 //                         of an object
     18 
     19 // ES2023 draft rev 2c78e6f6b5bc6bfbf79dd8a12a9593e5b57afcd2
     20 // 22.2.5.11 RegExp.prototype [ @@replace ] ( string, replaceValue )
     21 // steps 9-17.
     22 // Optimized path for @@replace with the following conditions:
     23 //   * global flag is true
     24 function FUNC_NAME(
     25  rx,
     26  S,
     27  lengthS,
     28  replaceValue,
     29  flags,
     30 #ifdef SUBSTITUTION
     31  firstDollarIndex,
     32 #endif
     33 #ifdef ELEMBASE
     34  elemBase
     35 #endif
     36 ) {
     37  // Step 9.a.
     38  var fullUnicode = !!(flags & REGEXP_UNICODE_FLAG);
     39 
     40  // Step 9.b.
     41  var lastIndex = 0;
     42  rx.lastIndex = 0;
     43 
     44 #if defined(FUNCTIONAL) || defined(ELEMBASE)
     45  // Save the original source and flags, so we can check if the replacer
     46  // function recompiled the regexp.
     47  var originalSource = UnsafeGetStringFromReservedSlot(rx, REGEXP_SOURCE_SLOT);
     48  var originalFlags = flags;
     49 #endif
     50 
     51 #if defined(FUNCTIONAL)
     52  var hasCaptureGroups = RegExpHasCaptureGroups(rx, S);
     53 #endif
     54 
     55  // Step 13 (reordered).
     56  var accumulatedResult = "";
     57 
     58  // Step 14 (reordered).
     59  var nextSourcePosition = 0;
     60 
     61  // Step 12.
     62  while (true) {
     63    var replacement;
     64    var matchLength;
     65 #if defined(FUNCTIONAL)
     66    // If the regexp has no capture groups, use a fast path that doesn't
     67    // allocate a match result object. This also inlines the call to
     68    // RegExpGetFunctionalReplacement.
     69    if (!hasCaptureGroups) {
     70      // Step 12.a.
     71      var position = RegExpSearcher(rx, S, lastIndex);
     72 
     73      // Step 12.b.
     74      if (position === -1) {
     75        break;
     76      }
     77 
     78      // Steps 15.c-f.
     79      lastIndex = RegExpSearcherLastLimit(S);
     80      var matched = Substring(S, position, lastIndex - position);
     81      matchLength = matched.length;
     82 
     83      // Steps 15.g-l.
     84      replacement = ToString(
     85        callContentFunction(
     86          replaceValue,
     87          undefined,
     88          matched,
     89          position,
     90          S
     91        )
     92      );
     93    } else
     94 #endif
     95    {
     96      // Step 12.a.
     97      var result = RegExpMatcher(rx, S, lastIndex);
     98 
     99      // Step 12.b.
    100      if (result === null) {
    101        break;
    102      }
    103 
    104      // Steps 15.a-b (skipped).
    105      assert(result.length >= 1, "RegExpMatcher doesn't return an empty array");
    106 
    107      // Step 15.c.
    108      var matched = result[0];
    109 
    110      // Step 15.d.
    111      matchLength = matched.length | 0;
    112 
    113      // Steps 15.e-f.
    114      var position = result.index | 0;
    115      lastIndex = position + matchLength;
    116 
    117      // Steps 15.g-l.
    118 #if defined(FUNCTIONAL)
    119      replacement = RegExpGetFunctionalReplacement(
    120        result,
    121        S,
    122        position,
    123        replaceValue
    124      );
    125 #elif defined(SUBSTITUTION)
    126      // Step 15.l.i
    127      var namedCaptures = result.groups;
    128      if (namedCaptures !== undefined) {
    129        namedCaptures = ToObject(namedCaptures);
    130      }
    131      // Step 15.l.ii
    132      replacement = RegExpGetSubstitution(
    133        result,
    134        S,
    135        position,
    136        replaceValue,
    137        firstDollarIndex,
    138        namedCaptures
    139      );
    140 #elif defined(ELEMBASE)
    141      if (IsObject(elemBase)) {
    142        var prop = GetStringDataProperty(elemBase, matched);
    143        if (prop !== undefined) {
    144          assert(
    145            typeof prop === "string",
    146            "GetStringDataProperty should return either string or undefined"
    147          );
    148          replacement = prop;
    149        } else {
    150          elemBase = undefined;
    151        }
    152      }
    153 
    154      if (!IsObject(elemBase)) {
    155        replacement = RegExpGetFunctionalReplacement(
    156          result,
    157          S,
    158          position,
    159          replaceValue
    160        );
    161      }
    162 #else
    163 #error "Unexpected case"
    164 #endif
    165    }
    166 
    167    // Step 15.m.ii.
    168    accumulatedResult +=
    169      Substring(S, nextSourcePosition, position - nextSourcePosition) +
    170      replacement;
    171 
    172    // Step 15.m.iii.
    173    nextSourcePosition = lastIndex;
    174 
    175    // Step 12.c.iii.2.
    176    if (matchLength === 0) {
    177      lastIndex = fullUnicode
    178        ? AdvanceStringIndex(S, lastIndex)
    179        : lastIndex + 1;
    180      if (lastIndex > lengthS) {
    181        break;
    182      }
    183      lastIndex |= 0;
    184    }
    185 
    186 #if defined(FUNCTIONAL) || defined(ELEMBASE)
    187    // Ensure the current source and flags match the original regexp, the
    188    // replaceValue function may have called RegExp#compile.
    189    if (
    190      UnsafeGetStringFromReservedSlot(rx, REGEXP_SOURCE_SLOT) !==
    191        originalSource ||
    192      UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT) !== originalFlags
    193    ) {
    194      var legacy = !!(originalFlags & REGEXP_LEGACY_FEATURES_ENABLED_FLAG);
    195      var newFlags = originalFlags & ~REGEXP_LEGACY_FEATURES_ENABLED_FLAG;
    196      rx = RegExpConstructRaw(originalSource, newFlags, legacy);
    197    }
    198 #endif
    199  }
    200 
    201  // Step 16.
    202  if (nextSourcePosition >= lengthS) {
    203    return accumulatedResult;
    204  }
    205 
    206  // Step 17.
    207  return (
    208    accumulatedResult +
    209    Substring(S, nextSourcePosition, lengthS - nextSourcePosition)
    210  );
    211 }