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