Index: src/js/regexp.js |
diff --git a/src/js/regexp.js b/src/js/regexp.js |
index a163952451f082ea50e12678a7a94fffa3e4052e..3ca5c39811ea869449295a715298642ac0f0f790 100644 |
--- a/src/js/regexp.js |
+++ b/src/js/regexp.js |
@@ -9,6 +9,7 @@ |
// ------------------------------------------------------------------- |
// Imports |
+var ExpandReplacement; |
var FLAG_harmony_tolength; |
var GlobalObject = global.Object; |
var GlobalRegExp = global.RegExp; |
@@ -17,6 +18,7 @@ var InternalArray = utils.InternalArray; |
var InternalPackedArray = utils.InternalPackedArray; |
var MakeTypeError; |
var matchSymbol = utils.ImportNow("match_symbol"); |
+var replaceSymbol = utils.ImportNow("replace_symbol"); |
var searchSymbol = utils.ImportNow("search_symbol"); |
var splitSymbol = utils.ImportNow("split_symbol"); |
@@ -25,6 +27,7 @@ utils.ImportFromExperimental(function(from) { |
}); |
utils.Import(function(from) { |
+ ExpandReplacement = from.ExpandReplacement; |
MakeTypeError = from.MakeTypeError; |
}); |
@@ -379,6 +382,175 @@ function RegExpMatch(string) { |
} |
+// ES6 21.2.5.8. |
+ |
+// TODO(lrn): This array will survive indefinitely if replace is never |
+// called again. However, it will be empty, since the contents are cleared |
+// in the finally block. |
+var reusableReplaceArray = new InternalArray(4); |
+ |
+// Helper function for replacing regular expressions with the result of a |
+// function application in String.prototype.replace. |
+function StringReplaceGlobalRegExpWithFunction(subject, regexp, replace) { |
+ var resultArray = reusableReplaceArray; |
+ if (resultArray) { |
+ reusableReplaceArray = null; |
+ } else { |
+ // Inside a nested replace (replace called from the replacement function |
+ // of another replace) or we have failed to set the reusable array |
+ // back due to an exception in a replacement function. Create a new |
+ // array to use in the future, or until the original is written back. |
+ resultArray = new InternalArray(16); |
+ } |
+ var res = %RegExpExecMultiple(regexp, |
+ subject, |
+ RegExpLastMatchInfo, |
+ resultArray); |
+ regexp.lastIndex = 0; |
+ if (IS_NULL(res)) { |
+ // No matches at all. |
+ reusableReplaceArray = resultArray; |
+ return subject; |
+ } |
+ var len = res.length; |
+ if (NUMBER_OF_CAPTURES(RegExpLastMatchInfo) == 2) { |
+ // 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. |
+ // The builder will consist of some integers that indicate slices of the |
+ // input string and some replacements that were returned from the replace |
+ // function. |
+ var match_start = 0; |
+ for (var i = 0; i < len; i++) { |
+ var elem = res[i]; |
+ if (%_IsSmi(elem)) { |
+ // Integers represent slices of the original string. |
+ if (elem > 0) { |
+ match_start = (elem >> 11) + (elem & 0x7ff); |
+ } else { |
+ match_start = res[++i] - elem; |
+ } |
+ } else { |
+ var func_result = replace(elem, match_start, subject); |
+ // Overwrite the i'th element in the results with the string we got |
+ // back from the callback function. |
+ res[i] = TO_STRING(func_result); |
+ match_start += elem.length; |
+ } |
+ } |
+ } else { |
+ for (var i = 0; i < len; i++) { |
+ var elem = res[i]; |
+ if (!%_IsSmi(elem)) { |
+ // elem must be an Array. |
+ // Use the apply argument as backing for global RegExp properties. |
+ var func_result = %Apply(replace, UNDEFINED, elem, 0, elem.length); |
+ // Overwrite the i'th element in the results with the string we got |
+ // back from the callback function. |
+ res[i] = TO_STRING(func_result); |
+ } |
+ } |
+ } |
+ var result = %StringBuilderConcat(res, len, subject); |
+ resultArray.length = 0; |
+ reusableReplaceArray = resultArray; |
+ return result; |
+} |
+ |
+ |
+// Compute the string of a given regular expression capture. |
+function CaptureString(string, lastCaptureInfo, index) { |
+ // Scale the index. |
+ var scaled = index << 1; |
+ // Compute start and end. |
+ var start = lastCaptureInfo[CAPTURE(scaled)]; |
+ // If start isn't valid, return undefined. |
+ if (start < 0) return; |
+ var end = lastCaptureInfo[CAPTURE(scaled + 1)]; |
+ return %_SubString(string, start, end); |
+} |
+ |
+ |
+function StringReplaceNonGlobalRegExpWithFunction(subject, regexp, replace) { |
+ var matchInfo = DoRegExpExec(regexp, subject, 0); |
+ if (IS_NULL(matchInfo)) { |
+ regexp.lastIndex = 0; |
+ return subject; |
+ } |
+ var index = matchInfo[CAPTURE0]; |
+ var result = %_SubString(subject, 0, index); |
+ var endOfMatch = matchInfo[CAPTURE1]; |
+ // Compute the parameter list consisting of the match, captures, index, |
+ // and subject for the replace function invocation. |
+ // The number of captures plus one for the match. |
+ var m = NUMBER_OF_CAPTURES(matchInfo) >> 1; |
+ var replacement; |
+ if (m == 1) { |
+ // No captures, only the match, which is always valid. |
+ var s = %_SubString(subject, index, endOfMatch); |
+ // Don't call directly to avoid exposing the built-in global object. |
+ replacement = replace(s, index, subject); |
+ } else { |
+ var parameters = new InternalArray(m + 2); |
+ for (var j = 0; j < m; j++) { |
+ parameters[j] = CaptureString(subject, matchInfo, j); |
+ } |
+ parameters[j] = index; |
+ parameters[j + 1] = subject; |
+ |
+ replacement = %Apply(replace, UNDEFINED, parameters, 0, j + 2); |
+ } |
+ |
+ result += replacement; // The add method converts to string if necessary. |
+ // Can't use matchInfo any more from here, since the function could |
+ // overwrite it. |
+ return result + %_SubString(subject, endOfMatch, subject.length); |
+} |
+ |
+ |
+function RegExpReplace(string, replace) { |
+ // TODO(littledan): allow non-regexp receivers. |
+ if (!IS_REGEXP(this)) { |
+ throw MakeTypeError(kIncompatibleMethodReceiver, |
+ "RegExp.prototype.@@replace", this); |
+ } |
+ var subject = TO_STRING(string); |
+ var search = this; |
+ |
+ if (!IS_CALLABLE(replace)) { |
+ replace = TO_STRING(replace); |
+ |
+ if (!REGEXP_GLOBAL(search)) { |
+ // Non-global regexp search, string replace. |
+ var match = DoRegExpExec(search, subject, 0); |
+ if (match == null) { |
+ search.lastIndex = 0 |
+ return subject; |
+ } |
+ if (replace.length == 0) { |
+ return %_SubString(subject, 0, match[CAPTURE0]) + |
+ %_SubString(subject, match[CAPTURE1], subject.length) |
+ } |
+ return ExpandReplacement(replace, subject, RegExpLastMatchInfo, |
+ %_SubString(subject, 0, match[CAPTURE0])) + |
+ %_SubString(subject, match[CAPTURE1], subject.length); |
+ } |
+ |
+ // Global regexp search, string replace. |
+ search.lastIndex = 0; |
+ return %StringReplaceGlobalRegExpWithString( |
+ subject, search, replace, RegExpLastMatchInfo); |
+ } |
+ |
+ if (REGEXP_GLOBAL(search)) { |
+ // Global regexp search, function replace. |
+ return StringReplaceGlobalRegExpWithFunction(subject, search, replace); |
+ } |
+ // Non-global regexp search, function replace. |
+ return StringReplaceNonGlobalRegExpWithFunction(subject, search, replace); |
+} |
+ |
+ |
// ES6 21.2.5.9. |
function RegExpSearch(string) { |
// TODO(yangguo): allow non-regexp receivers. |
@@ -511,6 +683,7 @@ utils.InstallFunctions(GlobalRegExp.prototype, DONT_ENUM, [ |
"toString", RegExpToString, |
"compile", RegExpCompileJS, |
matchSymbol, RegExpMatch, |
+ replaceSymbol, RegExpReplace, |
searchSymbol, RegExpSearch, |
splitSymbol, RegExpSplit, |
]); |