// Copyright 2019 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include 'src/builtins/builtins-string-gen.h' extern macro ReplaceSymbolConstant(): Symbol; extern macro StringBuiltinsAssembler::GetSubstitution( implicit context: Context)(String, Smi, Smi, String): String; transitioning macro ThrowIfNotGlobal(implicit context: Context)(searchValue: JSAny): void { let shouldThrow: bool; typeswitch (searchValue) { case (fastRegExp: FastJSRegExp): { shouldThrow = !fastRegExp.global; } case (Object): { const flags = GetProperty(searchValue, 'flags'); RequireObjectCoercible(flags, 'String.prototype.replaceAll'); shouldThrow = StringIndexOf(ToString_Inline(flags), StringConstant('g'), 0) == -1; } } if (shouldThrow) { ThrowTypeError( MessageTemplate::kRegExpGlobalInvokedOnNonGlobal, 'String.prototype.replaceAll'); } } // https://tc39.es/ecma262/#sec-string.prototype.replaceall transitioning javascript builtin StringPrototypeReplaceAll( js-implicit context: NativeContext, receiver: JSAny)( searchValue: JSAny, replaceValue: JSAny): JSAny { // 1. Let O be ? RequireObjectCoercible(this value). RequireObjectCoercible(receiver, 'String.prototype.replaceAll'); // 2. If searchValue is neither undefined nor null, then if (searchValue != Undefined && searchValue != Null) { // a. Let isRegExp be ? IsRegExp(searchString). // b. If isRegExp is true, then // i. Let flags be ? Get(searchValue, "flags"). // ii. Perform ? RequireObjectCoercible(flags). // iii. If ? ToString(flags) does not contain "g", throw a // TypeError exception. if (regexp::IsRegExp(searchValue)) { ThrowIfNotGlobal(searchValue); } // TODO(joshualitt): We could easily add fast paths for string // searchValues and potential FastRegExps. // c. Let replacer be ? GetMethod(searchValue, @@replace). // d. If replacer is not undefined, then // i. Return ? Call(replacer, searchValue, « O, replaceValue »). try { const replacer = GetMethod(searchValue, ReplaceSymbolConstant()) otherwise ReplaceSymbolIsNullOrUndefined; return Call(context, replacer, searchValue, receiver, replaceValue); } label ReplaceSymbolIsNullOrUndefined {} } // 3. Let string be ? ToString(O). const string = ToString_Inline(receiver); // 4. Let searchString be ? ToString(searchValue). const searchString = ToString_Inline(searchValue); // 5. Let functionalReplace be IsCallable(replaceValue). let replaceValueArg = replaceValue; const functionalReplace = Is(replaceValue); // 6. If functionalReplace is false, then if (!functionalReplace) { // a. Let replaceValue be ? ToString(replaceValue). replaceValueArg = ToString_Inline(replaceValue); } // 7. Let searchLength be the length of searchString. const searchLength = searchString.length_smi; // 8. Let advanceBy be max(1, searchLength). const advanceBy = SmiMax(1, searchLength); // We combine the two loops from the spec into one to avoid // needing a growable array. // // 9. Let matchPositions be a new empty List. // 10. Let position be ! StringIndexOf(string, searchString, 0). // 11. Repeat, while position is not -1 // a. Append position to the end of matchPositions. // b. Let position be ! StringIndexOf(string, searchString, // position + advanceBy). // 12. Let endOfLastMatch be 0. // 13. Let result be the empty string value. // 14. For each position in matchPositions, do let endOfLastMatch: Smi = 0; let result: String = kEmptyString; let position = AbstractStringIndexOf(string, searchString, 0); while (position != -1) { // a. If functionalReplace is true, then // b. Else, let replacement: String; if (functionalReplace) { // i. Let replacement be ? ToString(? Call(replaceValue, undefined, // « searchString, position, // string »). replacement = ToString_Inline(Call( context, UnsafeCast(replaceValueArg), Undefined, searchString, position, string)); } else { // i. Assert: Type(replaceValue) is String. const replaceValueString = UnsafeCast(replaceValueArg); // ii. Let captures be a new empty List. // iii. Let replacement be GetSubstitution(searchString, // string, position, captures, // undefined, replaceValue). // Note: Instead we just call a simpler GetSubstitution primitive. const matchEndPosition = position + searchLength; replacement = GetSubstitution( string, position, matchEndPosition, replaceValueString); } // c. Let stringSlice be the substring of string consisting of the code // units from endOfLastMatch (inclusive) up through position // (exclusive). const stringSlice = string::SubString( string, Unsigned(SmiUntag(endOfLastMatch)), Unsigned(SmiUntag(position))); // d. Let result be the string-concatenation of result, stringSlice, // and replacement. // TODO(joshualitt): This leaves a completely degenerate ConsString tree. // We could be smarter here. result = result + stringSlice + replacement; // e. Let endOfLastMatch be position + searchLength. endOfLastMatch = position + searchLength; position = AbstractStringIndexOf(string, searchString, position + advanceBy); } // 15. If endOfLastMatch < the length of string, then if (endOfLastMatch < string.length_smi) { // a. Let result be the string-concatenation of result and the substring // of string consisting of the code units from endOfLastMatch // (inclusive) up through the final code unit of string (inclusive). result = result + string::SubString( string, Unsigned(SmiUntag(endOfLastMatch)), Unsigned(string.length_intptr)); } // 16. Return result. return result; }