Simplified replace JS.
authorlrn@chromium.org <lrn@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 13 May 2010 12:13:27 +0000 (12:13 +0000)
committerlrn@chromium.org <lrn@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 13 May 2010 12:13:27 +0000 (12:13 +0000)
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

src/string.js

index 9433249..59a501f 100644 (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;
-          }
+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;