Simplified replace JS.

Refactored code so global/non-global regexps are handled in separate functions.
Inlined ApplyReplaceFunction at its only call point.

Review URL: http://codereview.chromium.org/1994019

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@4653 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
lrn@chromium.org 2010-05-13 12:13:27 +00:00
parent d24efe5348
commit 773c503d30

View File

@ -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;
}
} 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;
}
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);
}
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();
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 {
match_start = res[++i] - elem;
}
} 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;
}
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);
}
res[i] = func_result;
}
i++;
}
}
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);
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);
}
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;
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;