Index: src/string.js |
diff --git a/src/string.js b/src/string.js |
index f4489efa12182262b748d15ee6863b1eb357ecbd..ca438fdde54155d967b77af501ed0724dbdb7b48 100644 |
--- a/src/string.js |
+++ b/src/string.js |
@@ -405,91 +405,97 @@ function addCaptureString(builder, matchInfo, index) { |
builder.addSpecialSlice(start, end); |
}; |
-// 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 = $Array(16); |
// Helper function for replacing regular expressions with the result of a |
-// function application in String.prototype.replace. |
+// function application in String.prototype.replace. The function application |
+// must be interleaved with the regexp matching (contrary to ECMA-262 |
+// 15.5.4.11) to mimic SpiderMonkey and KJS behavior when the function uses |
+// the static properties of the RegExp constructor. Example: |
+// 'abcd'.replace(/(.)/g, function() { return RegExp.$1; } |
+// should be 'abcd' and not 'dddd' (or anything else). |
function StringReplaceRegExpWithFunction(subject, regexp, replace) { |
+ var matchInfo = DoRegExpExec(regexp, subject, 0); |
+ if (IS_NULL(matchInfo)) return subject; |
+ |
+ var result = new ReplaceResultBuilder(subject); |
+ // There's at least one match. If the regexp is global, we have to loop |
+ // over all matches. The loop is not in C++ code here like the one in |
+ // RegExp.prototype.exec, because of the interleaved function application. |
+ // Unfortunately, that means this code is nearly duplicated, here and in |
+ // jsregexp.cc. |
if (regexp.global) { |
- var resultArray = reusableReplaceArray; |
- if (resultArray) { |
- reusableReplaceArray = null; |
+ var previous = 0; |
+ var startOfMatch; |
+ if (NUMBER_OF_CAPTURES(matchInfo) == 2) { |
+ // Both branches contain essentially the same loop except for the call |
+ // to the replace function. The branch is put outside of the loop for |
+ // speed |
+ do { |
+ startOfMatch = matchInfo[CAPTURE0]; |
+ result.addSpecialSlice(previous, startOfMatch); |
+ previous = matchInfo[CAPTURE1]; |
+ var match = SubString(subject, startOfMatch, previous); |
+ // Don't call directly to avoid exposing the built-in global object. |
+ result.add(replace.call(null, match, startOfMatch, subject)); |
+ // Can't use matchInfo any more from here, since the function could |
+ // overwrite it. |
+ // Continue with the next match. |
+ // Increment previous if we matched an empty string, as per ECMA-262 |
+ // 15.5.4.10. |
+ if (previous == startOfMatch) { |
+ // Add the skipped character to the output, if any. |
+ if (previous < subject.length) { |
+ result.addSpecialSlice(previous, previous + 1); |
+ } |
+ previous++; |
+ // Per ECMA-262 15.10.6.2, if the previous index is greater than the |
+ // string length, there is no match |
+ if (previous > subject.length) { |
+ return result.generate(); |
+ } |
+ } |
+ matchInfo = DoRegExpExec(regexp, subject, previous); |
+ } while (!IS_NULL(matchInfo)); |
} 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 = $Array(16); |
- } |
- try { |
- // Must handle exceptions thrown by the replace functions correctly, |
- // including unregistering global regexps. |
- var res = %RegExpExecMultiple(regexp, |
- subject, |
- lastMatchInfo, |
- resultArray); |
- regexp.lastIndex = 0; |
- if (IS_NULL(res)) { |
- // No matches at all. |
- return subject; |
- } |
- var len = res.length; |
- var i = 0; |
- if (NUMBER_OF_CAPTURES(lastMatchInfo) == 2) { |
- var match_start = 0; |
- while (i < len) { |
- var elem = res[i]; |
- if (%_IsSmi(elem)) { |
- if (elem > 0) { |
- match_start = (elem >> 11) + (elem & 0x7ff); |
- } else { |
- match_start = res[++i] - elem; |
- } |
- } else { |
- var func_result = replace.call(null, elem, match_start, subject); |
- if (!IS_STRING(func_result)) func_result = TO_STRING(func_result); |
- res[i] = func_result; |
- match_start += elem.length; |
+ do { |
+ startOfMatch = matchInfo[CAPTURE0]; |
+ result.addSpecialSlice(previous, startOfMatch); |
+ previous = matchInfo[CAPTURE1]; |
+ result.add(ApplyReplacementFunction(replace, matchInfo, subject)); |
+ // Can't use matchInfo any more from here, since the function could |
+ // overwrite it. |
+ // Continue with the next match. |
+ // Increment previous if we matched an empty string, as per ECMA-262 |
+ // 15.5.4.10. |
+ if (previous == startOfMatch) { |
+ // Add the skipped character to the output, if any. |
+ if (previous < subject.length) { |
+ result.addSpecialSlice(previous, previous + 1); |
} |
- i++; |
- } |
- } else { |
- while (i < len) { |
- var elem = res[i]; |
- if (!%_IsSmi(elem)) { |
- // elem must be an Array. |
- // Use the apply argument as backing for global RegExp properties. |
- lastMatchInfoOverride = elem; |
- var func_result = replace.apply(null, elem); |
- if (!IS_STRING(func_result)) func_result = TO_STRING(func_result); |
- res[i] = func_result; |
+ previous++; |
+ // Per ECMA-262 15.10.6.2, if the previous index is greater than the |
+ // string length, there is no match |
+ if (previous > subject.length) { |
+ return result.generate(); |
} |
- i++; |
} |
- } |
- var result = new ReplaceResultBuilder(subject, res); |
- return result.generate(); |
- } finally { |
- lastMatchInfoOverride = null; |
- resultArray.length = 0; |
- reusableReplaceArray = resultArray; |
+ matchInfo = DoRegExpExec(regexp, subject, previous); |
+ } while (!IS_NULL(matchInfo)); |
} |
+ |
+ // Tack on the final right substring after the last match. |
+ result.addSpecialSlice(previous, subject.length); |
+ |
} else { // Not a global regexp, no need to loop. |
- var matchInfo = DoRegExpExec(regexp, subject, 0); |
- if (IS_NULL(matchInfo)) return subject; |
- |
- var result = new ReplaceResultBuilder(subject); |
result.addSpecialSlice(0, matchInfo[CAPTURE0]); |
var endOfMatch = matchInfo[CAPTURE1]; |
result.add(ApplyReplacementFunction(replace, matchInfo, subject)); |
// Can't use matchInfo any more from here, since the function could |
// overwrite it. |
result.addSpecialSlice(endOfMatch, subject.length); |
- return result.generate(); |
} |
+ |
+ return result.generate(); |
} |
@@ -888,11 +894,8 @@ function StringSup() { |
// ReplaceResultBuilder support. |
function ReplaceResultBuilder(str) { |
- if (%_ArgumentsLength() > 1) { |
- this.elements = %_Arguments(1); |
- } else { |
- this.elements = new $Array(); |
- } |
+ this.__proto__ = void 0; |
+ this.elements = new $Array(); |
this.special_string = str; |
} |