Index: src/string.js |
diff --git a/src/string.js b/src/string.js |
index 0349837ccb2d855edcfd389186c96f76b14d2743..6054e908ae51e655b757e8b1f7be9e39eb608cf2 100644 |
--- a/src/string.js |
+++ b/src/string.js |
@@ -219,60 +219,79 @@ function StringReplace(search, replace) { |
} |
var subject = TO_STRING_INLINE(this); |
- // Delegate to one of the regular expression variants if necessary. |
+ // Decision tree for dispatch |
+ // .. regexp search |
+ // .... string replace |
+ // ...... non-global search |
+ // ........ empty string replace |
+ // ........ non-empty string replace (with $-expansion) |
+ // ...... global search |
+ // ........ no need to circumvent last match info override |
+ // ........ need to circument last match info override |
+ // .... function replace |
+ // ...... global search |
+ // ...... non-global search |
+ // .. string search |
+ // .... special case that replaces with one single character |
+ // ...... function replace |
+ // ...... string replace (with $-expansion) |
+ |
if (IS_REGEXP(search)) { |
- // Emulate RegExp.prototype.exec's side effect in step 5, even though |
+ // Emulate RegExp.prototype.exec's side effect in step 5, even if |
// value is discarded. |
ToInteger(search.lastIndex); |
%_Log('regexp', 'regexp-replace,%0r,%1S', [search, subject]); |
- if (IS_SPEC_FUNCTION(replace)) { |
- if (search.global) { |
- return StringReplaceGlobalRegExpWithFunction(subject, search, replace); |
- } else { |
- return StringReplaceNonGlobalRegExpWithFunction(subject, |
- search, |
- replace); |
- } |
- } else { |
- if (lastMatchInfoOverride == null) { |
- var answer = %StringReplaceRegExpWithString(subject, |
- search, |
- TO_STRING_INLINE(replace), |
- lastMatchInfo); |
- if (IS_UNDEFINED(answer)) { // No match. Return subject string. |
- search.lastIndex = 0; |
+ |
+ if (!IS_SPEC_FUNCTION(replace)) { |
+ if (!search.global) { |
+ // Non-global regexp search, string replace. |
+ var match = DoRegExpExec(search, subject, 0); |
+ if (match == null) { |
+ search.lastIndex = 0 |
return subject; |
} |
- if (search.global) search.lastIndex = 0; |
- return answer; |
+ replace = TO_STRING_INLINE(replace); |
+ if (replace.length == 0) { |
+ return %_SubString(subject, 0, match[CAPTURE0]) + |
+ %_SubString(subject, match[CAPTURE1], subject.length) |
+ } |
+ return ExpandReplacement(replace, subject, lastMatchInfo, |
+ %_SubString(subject, 0, match[CAPTURE0])) + |
+ %_SubString(subject, match[CAPTURE1], subject.length); |
+ } |
+ |
+ // Global regexp search, string replace. |
+ search.lastIndex = 0; |
+ if (lastMatchInfoOverride == null) { |
+ return %StringReplaceGlobalRegExpWithString( |
+ subject, search, replace, lastMatchInfo); |
} else { |
// We use this hack to detect whether StringReplaceRegExpWithString |
// found at least one hit. In that case we need to remove any |
// override. |
var saved_subject = lastMatchInfo[LAST_SUBJECT_INDEX]; |
lastMatchInfo[LAST_SUBJECT_INDEX] = 0; |
- var answer = %StringReplaceRegExpWithString(subject, |
- search, |
- TO_STRING_INLINE(replace), |
- lastMatchInfo); |
- if (IS_UNDEFINED(answer)) { // No match. Return subject string. |
- search.lastIndex = 0; |
- lastMatchInfo[LAST_SUBJECT_INDEX] = saved_subject; |
- return subject; |
- } |
+ var answer = %StringReplaceGlobalRegExpWithString( |
+ subject, search, replace, lastMatchInfo); |
if (%_IsSmi(lastMatchInfo[LAST_SUBJECT_INDEX])) { |
lastMatchInfo[LAST_SUBJECT_INDEX] = saved_subject; |
} else { |
lastMatchInfoOverride = null; |
} |
- if (search.global) search.lastIndex = 0; |
return answer; |
} |
} |
+ |
+ if (search.global) { |
+ // Global regexp search, function replace. |
+ return StringReplaceGlobalRegExpWithFunction(subject, search, replace); |
+ } |
+ // Non-global regexp search, function replace. |
+ return StringReplaceNonGlobalRegExpWithFunction(subject, search, replace); |
} |
- // Convert the search argument to a string and search for it. |
search = TO_STRING_INLINE(search); |
+ |
if (search.length == 1 && |
subject.length > 0xFF && |
IS_STRING(replace) && |
@@ -295,8 +314,10 @@ function StringReplace(search, replace) { |
} else { |
reusableMatchInfo[CAPTURE0] = start; |
reusableMatchInfo[CAPTURE1] = end; |
- replace = TO_STRING_INLINE(replace); |
- result = ExpandReplacement(replace, subject, reusableMatchInfo, result); |
+ result = ExpandReplacement(TO_STRING_INLINE(replace), |
+ subject, |
+ reusableMatchInfo, |
+ result); |
} |
return result + %_SubString(subject, end, subject.length); |
@@ -333,6 +354,31 @@ function ExpandReplacement(string, subject, matchInfo, result) { |
} else if (peek == 39) { // $' - suffix |
++position; |
result += %_SubString(subject, matchInfo[CAPTURE1], subject.length); |
+ } else if (peek >= 48 && peek <= 57) { |
+ // Valid indices are $1 .. $9, $01 .. $09 and $10 .. $99 |
+ var scaled_index = (peek - 48) << 1; |
+ var advance = 1; |
+ var number_of_captures = NUMBER_OF_CAPTURES(matchInfo); |
+ if (position + 1 < string.length) { |
+ var next = %_StringCharCodeAt(string, position + 1); |
+ if (next >= 48 && next <= 57) { |
+ var new_scaled_index = scaled_index * 10 + ((next - 48) << 1); |
+ if (new_scaled_index < number_of_captures) { |
+ scaled_index = new_scaled_index; |
+ advance = 2; |
+ } |
+ } |
+ } |
+ if (scaled_index != 0 && scaled_index < number_of_captures) { |
+ var start = matchInfo[CAPTURE(scaled_index)]; |
+ if (start >= 0) { |
+ result += |
+ %_SubString(subject, start, matchInfo[CAPTURE(scaled_index + 1)]); |
+ } |
+ position += advance; |
+ } else { |
+ result += '$'; |
+ } |
} else { |
result += '$'; |
} |