summaryrefslogtreecommitdiff
path: root/deps/v8/src/builtins/regexp-replace.tq
blob: 4b5d542b60bf7ef9da43a38f261dce3b0702e356 (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
// 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-regexp-gen.h'

namespace regexp_replace {

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

  extern transitioning macro
  RegExpBuiltinsAssembler::ReplaceGlobalCallableFastPath(
      implicit context: Context)(JSRegExp, String, Callable): String;
  extern macro
  RegExpBuiltinsAssembler::AdvanceStringIndexFast(String, Number, bool): Smi;

  extern transitioning runtime
  RegExpReplaceRT(Context, JSReceiver, String, Object): String;
  extern transitioning runtime
  StringReplaceNonGlobalRegExpWithFunction(implicit context: Context)(
      String, JSRegExp, Callable): String;

  extern macro
  RegExpBuiltinsAssembler::RegExpPrototypeExecBodyWithoutResultFast(
      implicit context: Context)(JSReceiver, String):
      RegExpMatchInfo labels IfDidNotMatch;

  transitioning macro RegExpReplaceFastString(implicit context: Context)(
      regexp: FastJSRegExp, string: String, replaceString: String): String {
    // The fast path is reached only if {receiver} is an unmodified JSRegExp
    // instance, {replace_value} is non-callable, and ToString({replace_value})
    // does not contain '$', i.e. we're doing a simple string replacement.
    let result: String = kEmptyString;
    let lastMatchEnd: Smi = 0;
    let unicode: bool = false;
    let replaceLength: Smi = replaceString.length_smi;
    const global: bool = regexp.global;

    if (global) {
      unicode = regexp.unicode;
      regexp.lastIndex = 0;
    }

    while (true) {
      const match: RegExpMatchInfo = RegExpPrototypeExecBodyWithoutResultFast(
          regexp, string) otherwise break;
      const matchStart: Smi = match.GetStartOfCapture(0);
      const matchEnd: Smi = match.GetEndOfCapture(0);

      // TODO(jgruber): We could skip many of the checks that using SubString
      // here entails.
      result = result + SubString(string, lastMatchEnd, matchStart);
      lastMatchEnd = matchEnd;

      if (replaceLength != 0) result = result + replaceString;

      // Non-global case ends here after the first replacement.
      if (!global) break;

      // If match is the empty string, we have to increment lastIndex.
      if (matchEnd == matchStart) {
        regexp.lastIndex =
            AdvanceStringIndexFast(string, regexp.lastIndex, unicode);
      }
    }

    return result + SubString(string, lastMatchEnd, string.length_smi);
  }

  transitioning builtin RegExpReplace(implicit context: Context)(
      regexp: FastJSRegExp, string: String, replaceValue: Object): String {
    // TODO(pwong): Remove assert when all callers (StringPrototypeReplace) are
    // from Torque.
    assert(Is<FastJSRegExp>(regexp));

    // 2. Is {replace_value} callable?
    typeswitch (replaceValue) {
      case (replaceFn: Callable): {
        return regexp.global ?
            ReplaceGlobalCallableFastPath(regexp, string, replaceFn) :
            StringReplaceNonGlobalRegExpWithFunction(string, regexp, replaceFn);
      }
      case (Object): {
        const stableRegexp: JSRegExp = regexp;
        const replaceString: String = ToString_Inline(context, replaceValue);

        try {
          // ToString(replaceValue) could potentially change the shape of the
          // RegExp object. Recheck that we are still on the fast path and bail
          // to runtime otherwise.
          const fastRegexp = Cast<FastJSRegExp>(stableRegexp) otherwise Runtime;
          if (StringIndexOf(
                  replaceString, SingleCharacterStringConstant('$'), 0) != -1) {
            goto Runtime;
          }

          return RegExpReplaceFastString(fastRegexp, string, replaceString);
        }
        label Runtime deferred {
          return RegExpReplaceRT(context, stableRegexp, string, replaceString);
        }
      }
    }
  }

  transitioning javascript builtin RegExpPrototypeReplace(
      context: Context, receiver: Object, ...arguments): Object {
    const methodName: constexpr string = 'RegExp.prototype.@@replace';

    // RegExpPrototypeReplace is a bit of a beast - a summary of dispatch logic:
    //
    // if (!IsFastRegExp(receiver)) CallRuntime(RegExpReplace)
    // if (IsCallable(replace)) {
    //   if (IsGlobal(receiver)) {
    //     // Called 'fast-path' but contains several runtime calls.
    //     ReplaceGlobalCallableFastPath()
    //   } else {
    //     CallRuntime(StringReplaceNonGlobalRegExpWithFunction)
    //   }
    // } else {
    //   if (replace.contains("$")) {
    //     CallRuntime(RegExpReplace)
    //   } else {
    //     ReplaceSimpleStringFastPath()
    //   }
    // }

    const string: Object = arguments[0];
    const replaceValue: Object = arguments[1];

    // Let rx be the this value.
    // If Type(rx) is not Object, throw a TypeError exception.
    const rx = Cast<JSReceiver>(receiver)
        otherwise ThrowTypeError(kIncompatibleMethodReceiver, methodName);

    // Let S be ? ToString(string).
    const s = ToString_Inline(context, string);

    // Fast-path checks: 1. Is the {receiver} an unmodified JSRegExp instance?
    try {
      const fastRx: FastJSRegExp = Cast<FastJSRegExp>(rx) otherwise Runtime;
      return RegExpReplace(fastRx, s, replaceValue);
    }
    label Runtime deferred {
      return RegExpReplaceRT(context, rx, s, replaceValue);
    }
  }

}