| // 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 { | 
 |  | 
 | extern builtin | 
 | SubString(implicit context: Context)(String, Smi, Smi): String; | 
 |  | 
 | extern runtime RegExpExecMultiple(implicit context: Context)( | 
 |     JSRegExp, String, RegExpMatchInfo, JSArray): Null|JSArray; | 
 | extern transitioning runtime | 
 | RegExpReplaceRT(Context, JSReceiver, String, Object): String; | 
 | extern transitioning runtime | 
 | StringBuilderConcat(implicit context: Context)(JSArray, Smi, String): String; | 
 | extern transitioning runtime | 
 | StringReplaceNonGlobalRegExpWithFunction(implicit context: Context)( | 
 |     String, JSRegExp, Callable): String; | 
 |  | 
 | transitioning macro RegExpReplaceCallableNoExplicitCaptures( | 
 |     implicit context: Context)( | 
 |     matchesElements: FixedArray, matchesLength: intptr, string: String, | 
 |     replaceFn: Callable) { | 
 |   let matchStart: Smi = 0; | 
 |   for (let i: intptr = 0; i < matchesLength; i++) { | 
 |     typeswitch (matchesElements.objects[i]) { | 
 |       // Element represents a slice. | 
 |       case (elSmi: Smi): { | 
 |         // The slice's match start and end is either encoded as one or two | 
 |         // smis. A positive smi indicates a single smi encoding (see | 
 |         // ReplacementStringBuilder::AddSubjectSlice()). | 
 |         if (elSmi > 0) { | 
 |           // For single smi encoding, see | 
 |           // StringBuilderSubstringLength::encode() and | 
 |           // StringBuilderSubstringPosition::encode(). | 
 |           const elInt: intptr = Convert<intptr>(elSmi); | 
 |           const newMatchStart: intptr = (elInt >> 11) + (elInt & 0x7FF); | 
 |           matchStart = Convert<Smi>(newMatchStart); | 
 |         } else { | 
 |           // For two smi encoding, the length is negative followed by the | 
 |           // match start. | 
 |           const nextEl: Smi = UnsafeCast<Smi>(matchesElements.objects[++i]); | 
 |           matchStart = nextEl - elSmi; | 
 |         } | 
 |       } | 
 |       // Element represents the matched substring, which is then passed to the | 
 |       // replace function. | 
 |       case (elString: String): { | 
 |         const replacementObj: JSAny = | 
 |             Call(context, replaceFn, Undefined, elString, matchStart, string); | 
 |         const replacement: String = ToString_Inline(replacementObj); | 
 |         matchesElements.objects[i] = replacement; | 
 |         matchStart += elString.length_smi; | 
 |       } | 
 |       case (Object): deferred { | 
 |         unreachable; | 
 |       } | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | transitioning macro | 
 | RegExpReplaceCallableWithExplicitCaptures(implicit context: Context)( | 
 |     matchesElements: FixedArray, matchesLength: intptr, replaceFn: Callable) { | 
 |   for (let i: intptr = 0; i < matchesLength; i++) { | 
 |     const elArray = | 
 |         Cast<JSArray>(matchesElements.objects[i]) otherwise continue; | 
 |  | 
 |     // The JSArray is expanded into the function args by Reflect.apply(). | 
 |     // TODO(jgruber): Remove indirection through Call->ReflectApply. | 
 |     const replacementObj: JSAny = Call( | 
 |         context, GetReflectApply(), Undefined, replaceFn, Undefined, elArray); | 
 |  | 
 |     // Overwrite the i'th element in the results with the string | 
 |     // we got back from the callback function. | 
 |     matchesElements.objects[i] = ToString_Inline(replacementObj); | 
 |   } | 
 | } | 
 |  | 
 | transitioning macro RegExpReplaceFastGlobalCallable(implicit context: Context)( | 
 |     regexp: FastJSRegExp, string: String, replaceFn: Callable): String { | 
 |   regexp.lastIndex = 0; | 
 |  | 
 |   const kInitialCapacity: intptr = 16; | 
 |   const kInitialLength: Smi = 0; | 
 |   const result: Null|JSArray = RegExpExecMultiple( | 
 |       regexp, string, GetRegExpLastMatchInfo(), | 
 |       AllocateJSArray( | 
 |           ElementsKind::PACKED_ELEMENTS, GetFastPackedElementsJSArrayMap(), | 
 |           kInitialCapacity, kInitialLength)); | 
 |  | 
 |   regexp.lastIndex = 0; | 
 |  | 
 |   // If no matches, return the subject string. | 
 |   if (result == Null) return string; | 
 |  | 
 |   const matches: JSArray = UnsafeCast<JSArray>(result); | 
 |   const matchesLength: Smi = Cast<Smi>(matches.length) otherwise unreachable; | 
 |   const matchesLengthInt: intptr = Convert<intptr>(matchesLength); | 
 |   const matchesElements: FixedArray = UnsafeCast<FixedArray>(matches.elements); | 
 |  | 
 |   // Reload last match info since it might have changed. | 
 |   const nofCaptures: Smi = GetRegExpLastMatchInfo().NumberOfCaptures(); | 
 |  | 
 |   // If the number of captures is two then there are no explicit captures in | 
 |   // the regexp, just the implicit capture that captures the whole match. In | 
 |   // this case we can simplify quite a bit and end up with something faster. | 
 |   if (nofCaptures == 2) { | 
 |     RegExpReplaceCallableNoExplicitCaptures( | 
 |         matchesElements, matchesLengthInt, string, replaceFn); | 
 |   } else { | 
 |     RegExpReplaceCallableWithExplicitCaptures( | 
 |         matchesElements, matchesLengthInt, replaceFn); | 
 |   } | 
 |  | 
 |   return StringBuilderConcat(matches, matchesLength, string); | 
 | } | 
 |  | 
 | transitioning macro RegExpReplaceFastString(implicit context: Context)( | 
 |     regexp: JSRegExp, 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; | 
 |   const replaceLength: Smi = replaceString.length_smi; | 
 |   const fastRegexp = UnsafeCast<FastJSRegExp>(regexp); | 
 |   const global: bool = fastRegexp.global; | 
 |  | 
 |   if (global) { | 
 |     unicode = fastRegexp.unicode; | 
 |     fastRegexp.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) { | 
 |       typeswitch (regexp) { | 
 |         case (fastRegexp: FastJSRegExp): { | 
 |           fastRegexp.lastIndex = | 
 |               AdvanceStringIndexFast(string, fastRegexp.lastIndex, unicode); | 
 |         } | 
 |         case (Object): { | 
 |           const lastIndex: JSAny = SlowLoadLastIndex(regexp); | 
 |           const thisIndex: Number = ToLength_Inline(lastIndex); | 
 |           const nextIndex: Number = | 
 |               AdvanceStringIndexSlow(string, thisIndex, unicode); | 
 |           SlowStoreLastIndex(regexp, nextIndex); | 
 |         } | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   return result + SubString(string, lastMatchEnd, string.length_smi); | 
 | } | 
 |  | 
 | transitioning builtin RegExpReplace(implicit context: Context)( | 
 |     regexp: FastJSRegExp, string: String, replaceValue: JSAny): 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 ? | 
 |           RegExpReplaceFastGlobalCallable(regexp, string, replaceFn) : | 
 |           StringReplaceNonGlobalRegExpWithFunction(string, regexp, replaceFn); | 
 |     } | 
 |     case (JSAny): { | 
 |       const stableRegexp: JSRegExp = regexp; | 
 |       const replaceString: String = ToString_Inline(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); | 
 |       } | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | const kRegExpReplaceCalledOnSlowRegExp: constexpr int31 | 
 |     generates 'v8::Isolate::kRegExpReplaceCalledOnSlowRegExp'; | 
 |  | 
 | transitioning javascript builtin RegExpPrototypeReplace( | 
 |     js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny { | 
 |   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. | 
 |   //     RegExpReplaceFastGlobalCallable() | 
 |   //   } else { | 
 |   //     CallRuntime(StringReplaceNonGlobalRegExpWithFunction) | 
 |   //   } | 
 |   // } else { | 
 |   //   if (replace.contains("$")) { | 
 |   //     CallRuntime(RegExpReplace) | 
 |   //   } else { | 
 |   //     RegExpReplaceFastString() | 
 |   //   } | 
 |   // } | 
 |  | 
 |   const string: JSAny = arguments[0]; | 
 |   const replaceValue: JSAny = 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( | 
 |       MessageTemplate::kIncompatibleMethodReceiver, methodName); | 
 |  | 
 |   // Let S be ? ToString(string). | 
 |   const s = ToString_Inline(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 { | 
 |     IncrementUseCounter(context, SmiConstant(kRegExpReplaceCalledOnSlowRegExp)); | 
 |     return RegExpReplaceRT(context, rx, s, replaceValue); | 
 |   } | 
 | } | 
 | } |