summaryrefslogtreecommitdiff
path: root/deps/v8/src/builtins/string-replaceall.tq
blob: 30fa7745ab1cdd2468f58c219f0fb45550ab7a03 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
// 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;

extern builtin
StringIndexOf(implicit context: Context)(String, String, Smi): Smi;

// TODO(tebbi): This could be replaced with a fast C-call to StringSearchRaw.
macro AbstractStringIndexOf<A: type, B: type>(
    string: ConstSlice<A>, searchString: ConstSlice<B>, fromIndex: Smi): Smi {
  for (let i: intptr = SmiUntag(fromIndex);
       i <= string.length - searchString.length; i++) {
    if (IsSubstringAt(string, searchString, i)) {
      return SmiTag(i);
    }
  }
  return -1;
}

struct AbstractStringIndexOfFunctor {
  fromIndex: Smi;
}
// Ideally, this would be a method of AbstractStringIndexOfFunctor, but
// currently methods don't support templates.
macro Call<A: type, B: type>(
    self: AbstractStringIndexOfFunctor, string: ConstSlice<A>,
    searchStr: ConstSlice<B>): Smi {
  return AbstractStringIndexOf(string, searchStr, self.fromIndex);
}

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;
  }

  return TwoStringsToSlices<Smi>(
      string, searchString, AbstractStringIndexOfFunctor{fromIndex: fromIndex});
}

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;
}