| Index: src/string.js
|
| diff --git a/src/string.js b/src/string.js
|
| index 2fe17f8e5ed814680f00f2d79df55a5cccc9a686..0acfc16e7d5eb4f51476cfd7ee66756332adbdf7 100644
|
| --- a/src/string.js
|
| +++ b/src/string.js
|
| @@ -28,6 +28,7 @@
|
|
|
| // This file relies on the fact that the following declaration has been made
|
| // in runtime.js:
|
| +// const $Function = global.Function
|
| // const $String = global.String;
|
| // const $NaN = 0/0;
|
|
|
| @@ -384,96 +385,87 @@ function addCaptureString(builder, matchInfo, index) {
|
| };
|
|
|
|
|
| +var nestedReplace = 0;
|
| +var staticReplaceResult = $Array(64);
|
| +var replaceAppliers = { __proto__: null }; // Really empty object.
|
| +
|
| +function makeReplaceApplier(n) {
|
| + var source = "return func.call(null, arr[idx+1],";
|
| + for (var i = 0; i < n; i++) {
|
| + source += " arr[idx+" + (i + 2) +"],";
|
| + }
|
| + source += " arr[idx], subject);";
|
| + return $Function("func, arr, idx, subject", source);
|
| +}
|
| +
|
| // Helper function for replacing regular expressions with the result of a
|
| -// 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 application in String.prototype.replace.
|
| 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 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();
|
| + var matches = (nestedReplace > 0) ? $Array(0) : staticReplaceResult;
|
| + var match_count = %RegExpExecMultiple(regexp, subject, lastMatchInfo, matches);
|
| + regexp.lastIndex = 0;
|
| + if (match_count == 0) return subject; // No matches at all.
|
| + // Capture count, not including actual match.
|
| + var capture_count = matches[0];
|
| + var result = new ReplaceResultBuilder(subject);
|
| + var match_index = 1;
|
| + var index = 0;
|
| + nestedReplace++;
|
| + try {
|
| + if (capture_count == 0) {
|
| + while (match_index < matches.length) {
|
| + var match_start = matches[match_index];
|
| + if (index < match_start) {
|
| + result.addSpecialSlice(index, match_start);
|
| }
|
| + var match = matches[match_index + 1];
|
| + var func_result = replace.call(null, match, match_start, subject);
|
| + if (!IS_STRING(func_result)) func_result = TO_STRING(func_result);
|
| + result.add(func_result);
|
| + index = match_start + match.length;
|
| + match_index += 2;
|
| }
|
| - matchInfo = DoRegExpExec(regexp, subject, previous);
|
| - } while (!IS_NULL(matchInfo));
|
| - } else {
|
| - 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);
|
| - }
|
| - 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();
|
| + } else {
|
| + var applier = replaceAppliers[capture_count];
|
| + if (!applier) {
|
| + applier = replaceAppliers[capture_count] =
|
| + makeReplaceApplier(capture_count);
|
| + }
|
| + while (match_index < matches.length) {
|
| + var match_start = matches[match_index];
|
| + if (index < match_start) {
|
| + result.addSpecialSlice(index, match_start);
|
| }
|
| + var match = matches[match_index + 1];
|
| + var func_result = applier(replace, matches, match_index, subject);
|
| + if (!IS_STRING(func_result)) func_result = TO_STRING(func_result);
|
| + result.add(func_result);
|
| + index = match_start + match.length;
|
| + match_index += capture_count + 2;
|
| }
|
| - matchInfo = DoRegExpExec(regexp, subject, previous);
|
| - } while (!IS_NULL(matchInfo));
|
| + }
|
| + if (index < subject.length) {
|
| + result.addSpecialSlice(index, subject.length);
|
| + }
|
| + } finally {
|
| + nestedReplace--;
|
| }
|
| -
|
| - // Tack on the final right substring after the last match.
|
| - result.addSpecialSlice(previous, subject.length);
|
| -
|
| + return result.generate();
|
| } 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();
|
| }
|
|
|
|
|
|
|