blob: 9211304b345214954ccc7336a674114c75dd9302 [file] [log] [blame]
// 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'
namespace string {
extern macro ReplaceSymbolConstant(): Symbol;
extern macro StringBuiltinsAssembler::GetSubstitution(
implicit context: Context)(String, Smi, Smi, String): String;
extern builtin
StringIndexOf(implicit context: Context)(String, String, Smi): Smi;
macro TryFastAbstractStringIndexOf(implicit context: Context)(
string: String, searchString: String, fromIndex: Smi): Smi labels Slow {
const stringLen = string.length_uintptr;
const searchLen = searchString.length_uintptr;
const directString = Cast<DirectString>(string) otherwise Slow;
const directSearchStr = Cast<DirectString>(searchString) otherwise Slow;
const fromIndexUint = Unsigned(SmiUntag(fromIndex));
for (let i: uintptr = fromIndexUint; i < stringLen; i++) {
let j = i;
let k: uintptr = 0;
while (j < stringLen && k < searchLen &&
StringCharCodeAt(directString, j) ==
StringCharCodeAt(directSearchStr, k)) {
j++;
k++;
}
if (k == searchLen) {
return SmiTag(Signed(i));
}
}
return -1;
}
macro AbstractStringIndexOf(implicit context: Context)(
string: String, searchString: String, fromIndex: Smi): Smi {
// Special case the empty string.
const searchStringLength = searchString.length_intptr;
const stringLength = string.length_intptr;
if (searchStringLength == 0 && SmiUntag(fromIndex) <= stringLength) {
return fromIndex;
}
// Don't bother to search if the searchString would go past the end
// of the string. This is actually necessary because of runtime
// checks.
if (SmiUntag(fromIndex) + searchStringLength > stringLength) {
return -1;
}
try {
return TryFastAbstractStringIndexOf(string, searchString, fromIndex)
otherwise Slow;
} label Slow {
for (let i: intptr = SmiUntag(fromIndex);
i + searchStringLength <= stringLength; i++) {
if (StringCompareSequence(
context, string, searchString, Convert<Number>(SmiTag(i))) ==
True) {
return SmiTag(i);
}
}
return -1;
}
}
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<Callable>(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<Callable>(replaceValueArg), Undefined,
searchString, position, string));
} else {
// i. Assert: Type(replaceValue) is String.
const replaceValueString = UnsafeCast<String>(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;
}
}