| 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;
|
| }
|
|
|
|
|