// 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(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(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(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(rx) otherwise Runtime; return RegExpReplace(fastRx, s, replaceValue); } label Runtime deferred { return RegExpReplaceRT(context, rx, s, replaceValue); } } }