1// Copyright 2019 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include 'src/builtins/builtins-string-gen.h' 6 7extern macro ReplaceSymbolConstant(): Symbol; 8 9extern macro StringBuiltinsAssembler::GetSubstitution( 10 implicit context: Context)(String, Smi, Smi, String): String; 11 12transitioning macro 13ThrowIfNotGlobal(implicit context: Context)(searchValue: JSAny): void { 14 let shouldThrow: bool; 15 typeswitch (searchValue) { 16 case (fastRegExp: FastJSRegExp): { 17 shouldThrow = !fastRegExp.global; 18 } 19 case (Object): { 20 const flags = GetProperty(searchValue, 'flags'); 21 RequireObjectCoercible(flags, 'String.prototype.replaceAll'); 22 shouldThrow = 23 StringIndexOf(ToString_Inline(flags), StringConstant('g'), 0) == -1; 24 } 25 } 26 if (shouldThrow) { 27 ThrowTypeError( 28 MessageTemplate::kRegExpGlobalInvokedOnNonGlobal, 29 'String.prototype.replaceAll'); 30 } 31} 32 33// https://tc39.es/ecma262/#sec-string.prototype.replaceall 34transitioning javascript builtin StringPrototypeReplaceAll( 35 js-implicit context: NativeContext, receiver: JSAny)( 36 searchValue: JSAny, replaceValue: JSAny): JSAny { 37 // 1. Let O be ? RequireObjectCoercible(this value). 38 RequireObjectCoercible(receiver, 'String.prototype.replaceAll'); 39 40 // 2. If searchValue is neither undefined nor null, then 41 if (searchValue != Undefined && searchValue != Null) { 42 // a. Let isRegExp be ? IsRegExp(searchString). 43 // b. If isRegExp is true, then 44 // i. Let flags be ? Get(searchValue, "flags"). 45 // ii. Perform ? RequireObjectCoercible(flags). 46 // iii. If ? ToString(flags) does not contain "g", throw a 47 // TypeError exception. 48 if (regexp::IsRegExp(searchValue)) { 49 ThrowIfNotGlobal(searchValue); 50 } 51 52 // TODO(joshualitt): We could easily add fast paths for string 53 // searchValues and potential FastRegExps. 54 // c. Let replacer be ? GetMethod(searchValue, @@replace). 55 // d. If replacer is not undefined, then 56 // i. Return ? Call(replacer, searchValue, « O, replaceValue »). 57 try { 58 const replacer = GetMethod(searchValue, ReplaceSymbolConstant()) 59 otherwise ReplaceSymbolIsNullOrUndefined; 60 return Call(context, replacer, searchValue, receiver, replaceValue); 61 } label ReplaceSymbolIsNullOrUndefined {} 62 } 63 64 // 3. Let string be ? ToString(O). 65 const string = ToString_Inline(receiver); 66 67 // 4. Let searchString be ? ToString(searchValue). 68 const searchString = ToString_Inline(searchValue); 69 70 // 5. Let functionalReplace be IsCallable(replaceValue). 71 let replaceValueArg = replaceValue; 72 const functionalReplace = Is<Callable>(replaceValue); 73 74 // 6. If functionalReplace is false, then 75 if (!functionalReplace) { 76 // a. Let replaceValue be ? ToString(replaceValue). 77 replaceValueArg = ToString_Inline(replaceValue); 78 } 79 80 // 7. Let searchLength be the length of searchString. 81 const searchLength = searchString.length_smi; 82 83 // 8. Let advanceBy be max(1, searchLength). 84 const advanceBy = SmiMax(1, searchLength); 85 86 // We combine the two loops from the spec into one to avoid 87 // needing a growable array. 88 // 89 // 9. Let matchPositions be a new empty List. 90 // 10. Let position be ! StringIndexOf(string, searchString, 0). 91 // 11. Repeat, while position is not -1 92 // a. Append position to the end of matchPositions. 93 // b. Let position be ! StringIndexOf(string, searchString, 94 // position + advanceBy). 95 // 12. Let endOfLastMatch be 0. 96 // 13. Let result be the empty string value. 97 // 14. For each position in matchPositions, do 98 let endOfLastMatch: Smi = 0; 99 let result: String = kEmptyString; 100 let position = AbstractStringIndexOf(string, searchString, 0); 101 while (position != -1) { 102 // a. If functionalReplace is true, then 103 // b. Else, 104 let replacement: String; 105 if (functionalReplace) { 106 // i. Let replacement be ? ToString(? Call(replaceValue, undefined, 107 // « searchString, position, 108 // string »). 109 replacement = ToString_Inline(Call( 110 context, UnsafeCast<Callable>(replaceValueArg), Undefined, 111 searchString, position, string)); 112 } else { 113 // i. Assert: Type(replaceValue) is String. 114 const replaceValueString = UnsafeCast<String>(replaceValueArg); 115 116 // ii. Let captures be a new empty List. 117 // iii. Let replacement be GetSubstitution(searchString, 118 // string, position, captures, 119 // undefined, replaceValue). 120 // Note: Instead we just call a simpler GetSubstitution primitive. 121 const matchEndPosition = position + searchLength; 122 replacement = GetSubstitution( 123 string, position, matchEndPosition, replaceValueString); 124 } 125 126 // c. Let stringSlice be the substring of string consisting of the code 127 // units from endOfLastMatch (inclusive) up through position 128 // (exclusive). 129 const stringSlice = string::SubString( 130 string, Unsigned(SmiUntag(endOfLastMatch)), 131 Unsigned(SmiUntag(position))); 132 133 // d. Let result be the string-concatenation of result, stringSlice, 134 // and replacement. 135 // TODO(joshualitt): This leaves a completely degenerate ConsString tree. 136 // We could be smarter here. 137 result = result + stringSlice + replacement; 138 139 // e. Let endOfLastMatch be position + searchLength. 140 endOfLastMatch = position + searchLength; 141 142 position = 143 AbstractStringIndexOf(string, searchString, position + advanceBy); 144 } 145 146 // 15. If endOfLastMatch < the length of string, then 147 if (endOfLastMatch < string.length_smi) { 148 // a. Let result be the string-concatenation of result and the substring 149 // of string consisting of the code units from endOfLastMatch 150 // (inclusive) up through the final code unit of string (inclusive). 151 result = result + 152 string::SubString( 153 string, Unsigned(SmiUntag(endOfLastMatch)), 154 Unsigned(string.length_intptr)); 155 } 156 157 // 16. Return result. 158 return result; 159} 160