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 }