Index: src/string.js |
diff --git a/src/string.js b/src/string.js |
index 9433249188c9fccf26f42513a9bb011f1724c4a9..59a501f9ee502220124a68869aedeb9b10e03a13 100644 |
--- a/src/string.js |
+++ b/src/string.js |
@@ -241,7 +241,13 @@ function StringReplace(search, replace) { |
%_Log('regexp', 'regexp-replace,%0r,%1S', [search, subject]); |
if (IS_FUNCTION(replace)) { |
regExpCache.type = 'none'; |
- return StringReplaceRegExpWithFunction(subject, search, replace); |
+ if (search.global) { |
+ return StringReplaceGlobalRegExpWithFunction(subject, search, replace); |
+ } else { |
+ return StringReplaceNonGlobalRegExpWithFunction(subject, |
+ search, |
+ replace); |
+ } |
} else { |
return StringReplaceRegExp(subject, search, replace); |
} |
@@ -396,9 +402,9 @@ function CaptureString(string, lastCaptureInfo, 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)]; |
- // If either start or end is missing return undefined. |
- if (start < 0 || end < 0) return; |
return SubString(string, start, end); |
}; |
@@ -410,9 +416,8 @@ function addCaptureString(builder, matchInfo, index) { |
var scaled = index << 1; |
// Compute start and end. |
var start = matchInfo[CAPTURE(scaled)]; |
+ if (start < 0) return; |
var end = matchInfo[CAPTURE(scaled + 1)]; |
- // If either start or end is missing return. |
- if (start < 0 || end <= start) return; |
builder.addSpecialSlice(start, end); |
}; |
@@ -423,112 +428,116 @@ var reusableReplaceArray = $Array(16); |
// Helper function for replacing regular expressions with the result of a |
// function application in String.prototype.replace. |
-function StringReplaceRegExpWithFunction(subject, regexp, replace) { |
- if (regexp.global) { |
- 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 = $Array(16); |
- } |
- |
- 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; |
- var override = [null, 0, subject]; |
- 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; |
- } |
+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 = $Array(16); |
+ } |
+ var res = %RegExpExecMultiple(regexp, |
+ subject, |
+ lastMatchInfo, |
+ resultArray); |
+ regexp.lastIndex = 0; |
+ if (IS_NULL(res)) { |
+ // No matches at all. |
+ reusableReplaceArray = resultArray; |
+ return subject; |
+ } |
+ var len = res.length; |
+ var i = 0; |
+ if (NUMBER_OF_CAPTURES(lastMatchInfo) == 2) { |
+ var match_start = 0; |
+ var override = [null, 0, subject]; |
+ var receiver = %GetGlobalReceiver(); |
+ while (i < len) { |
+ var elem = res[i]; |
+ if (%_IsSmi(elem)) { |
+ if (elem > 0) { |
+ match_start = (elem >> 11) + (elem & 0x7ff); |
} else { |
- override[0] = elem; |
- override[1] = match_start; |
- lastMatchInfoOverride = override; |
- var func_result = replace.call(null, elem, match_start, subject); |
- if (!IS_STRING(func_result)) { |
- func_result = NonStringToString(func_result); |
- } |
- res[i] = func_result; |
- match_start += elem.length; |
+ match_start = res[++i] - elem; |
} |
- i++; |
+ } else { |
+ override[0] = elem; |
+ override[1] = match_start; |
+ lastMatchInfoOverride = override; |
+ var func_result = |
+ %_CallFunction(receiver, elem, match_start, subject, replace); |
+ if (!IS_STRING(func_result)) { |
+ func_result = NonStringToString(func_result); |
+ } |
+ res[i] = func_result; |
+ match_start += elem.length; |
} |
- } 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 = NonStringToString(func_result); |
- } |
- res[i] = func_result; |
+ 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 = NonStringToString(func_result); |
} |
- i++; |
+ res[i] = func_result; |
} |
+ i++; |
} |
- var resultBuilder = new ReplaceResultBuilder(subject, res); |
- var result = resultBuilder.generate(); |
- resultArray.length = 0; |
- reusableReplaceArray = resultArray; |
- return result; |
- } 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(); |
} |
+ var resultBuilder = new ReplaceResultBuilder(subject, res); |
+ var result = resultBuilder.generate(); |
+ resultArray.length = 0; |
+ reusableReplaceArray = resultArray; |
+ return result; |
} |
-// Helper function to apply a string replacement function once. |
-function ApplyReplacementFunction(replace, matchInfo, subject) { |
+function StringReplaceNonGlobalRegExpWithFunction(subject, regexp, replace) { |
+ var matchInfo = DoRegExpExec(regexp, subject, 0); |
+ if (IS_NULL(matchInfo)) return subject; |
+ var result = new ReplaceResultBuilder(subject); |
+ var index = matchInfo[CAPTURE0]; |
+ result.addSpecialSlice(0, index); |
+ var endOfMatch = matchInfo[CAPTURE1]; |
// Compute the parameter list consisting of the match, captures, index, |
// and subject for the replace function invocation. |
- var index = matchInfo[CAPTURE0]; |
// The number of captures plus one for the match. |
var m = NUMBER_OF_CAPTURES(matchInfo) >> 1; |
+ var replacement; |
if (m == 1) { |
- var s = CaptureString(subject, matchInfo, 0); |
+ // 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. |
- return replace.call(null, s, index, subject); |
- } |
- var parameters = $Array(m + 2); |
- for (var j = 0; j < m; j++) { |
- parameters[j] = CaptureString(subject, matchInfo, j); |
+ replacement = |
+ %_CallFunction(%GetGlobalReceiver(), s, index, subject, replace); |
+ } else { |
+ var parameters = $Array(m + 2); |
+ for (var j = 0; j < m; j++) { |
+ parameters[j] = CaptureString(subject, matchInfo, j); |
+ } |
+ parameters[j] = index; |
+ parameters[j + 1] = subject; |
+ |
+ replacement = replace.apply(null, parameters); |
} |
- parameters[j] = index; |
- parameters[j + 1] = subject; |
- return replace.apply(null, parameters); |
+ |
+ result.add(replacement); // The add method converts to string if necessary. |
+ // Can't use matchInfo any more from here, since the function could |
+ // overwrite it. |
+ result.addSpecialSlice(endOfMatch, subject.length); |
+ return result.generate(); |
} |
+ |
// ECMA-262 section 15.5.4.12 |
function StringSearch(re) { |
var regexp; |