Home | History | Annotate | Download | only in src
      1 // Copyright 2012 the V8 project authors. All rights reserved.
      2 // Redistribution and use in source and binary forms, with or without
      3 // modification, are permitted provided that the following conditions are
      4 // met:
      5 //
      6 //     * Redistributions of source code must retain the above copyright
      7 //       notice, this list of conditions and the following disclaimer.
      8 //     * Redistributions in binary form must reproduce the above
      9 //       copyright notice, this list of conditions and the following
     10 //       disclaimer in the documentation and/or other materials provided
     11 //       with the distribution.
     12 //     * Neither the name of Google Inc. nor the names of its
     13 //       contributors may be used to endorse or promote products derived
     14 //       from this software without specific prior written permission.
     15 //
     16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27 
     28 
     29 // This file relies on the fact that the following declaration has been made
     30 // in runtime.js:
     31 // var $String = global.String;
     32 // var $NaN = 0/0;
     33 
     34 
     35 // Set the String function and constructor.
     36 %SetCode($String, function(x) {
     37   var value = %_ArgumentsLength() == 0 ? '' : TO_STRING_INLINE(x);
     38   if (%_IsConstructCall()) {
     39     %_SetValueOf(this, value);
     40   } else {
     41     return value;
     42   }
     43 });
     44 
     45 %FunctionSetPrototype($String, new $String());
     46 
     47 // ECMA-262 section 15.5.4.2
     48 function StringToString() {
     49   if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) {
     50     throw new $TypeError('String.prototype.toString is not generic');
     51   }
     52   return %_ValueOf(this);
     53 }
     54 
     55 
     56 // ECMA-262 section 15.5.4.3
     57 function StringValueOf() {
     58   if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) {
     59     throw new $TypeError('String.prototype.valueOf is not generic');
     60   }
     61   return %_ValueOf(this);
     62 }
     63 
     64 
     65 // ECMA-262, section 15.5.4.4
     66 function StringCharAt(pos) {
     67   if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
     68     throw MakeTypeError("called_on_null_or_undefined",
     69                         ["String.prototype.charAt"]);
     70   }
     71   var result = %_StringCharAt(this, pos);
     72   if (%_IsSmi(result)) {
     73     result = %_StringCharAt(TO_STRING_INLINE(this), TO_INTEGER(pos));
     74   }
     75   return result;
     76 }
     77 
     78 
     79 // ECMA-262 section 15.5.4.5
     80 function StringCharCodeAt(pos) {
     81   if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
     82     throw MakeTypeError("called_on_null_or_undefined",
     83                         ["String.prototype.charCodeAt"]);
     84   }
     85   var result = %_StringCharCodeAt(this, pos);
     86   if (!%_IsSmi(result)) {
     87     result = %_StringCharCodeAt(TO_STRING_INLINE(this), TO_INTEGER(pos));
     88   }
     89   return result;
     90 }
     91 
     92 
     93 // ECMA-262, section 15.5.4.6
     94 function StringConcat() {
     95   if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
     96     throw MakeTypeError("called_on_null_or_undefined",
     97                         ["String.prototype.concat"]);
     98   }
     99   var len = %_ArgumentsLength();
    100   var this_as_string = TO_STRING_INLINE(this);
    101   if (len === 1) {
    102     return this_as_string + %_Arguments(0);
    103   }
    104   var parts = new InternalArray(len + 1);
    105   parts[0] = this_as_string;
    106   for (var i = 0; i < len; i++) {
    107     var part = %_Arguments(i);
    108     parts[i + 1] = TO_STRING_INLINE(part);
    109   }
    110   return %StringBuilderConcat(parts, len + 1, "");
    111 }
    112 
    113 // Match ES3 and Safari
    114 %FunctionSetLength(StringConcat, 1);
    115 
    116 
    117 // ECMA-262 section 15.5.4.7
    118 function StringIndexOf(pattern /* position */) {  // length == 1
    119   if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    120     throw MakeTypeError("called_on_null_or_undefined",
    121                         ["String.prototype.indexOf"]);
    122   }
    123   var subject = TO_STRING_INLINE(this);
    124   pattern = TO_STRING_INLINE(pattern);
    125   var index = 0;
    126   if (%_ArgumentsLength() > 1) {
    127     index = %_Arguments(1);  // position
    128     index = TO_INTEGER(index);
    129     if (index < 0) index = 0;
    130     if (index > subject.length) index = subject.length;
    131   }
    132   return %StringIndexOf(subject, pattern, index);
    133 }
    134 
    135 
    136 // ECMA-262 section 15.5.4.8
    137 function StringLastIndexOf(pat /* position */) {  // length == 1
    138   if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    139     throw MakeTypeError("called_on_null_or_undefined",
    140                         ["String.prototype.lastIndexOf"]);
    141   }
    142   var sub = TO_STRING_INLINE(this);
    143   var subLength = sub.length;
    144   var pat = TO_STRING_INLINE(pat);
    145   var patLength = pat.length;
    146   var index = subLength - patLength;
    147   if (%_ArgumentsLength() > 1) {
    148     var position = ToNumber(%_Arguments(1));
    149     if (!NUMBER_IS_NAN(position)) {
    150       position = TO_INTEGER(position);
    151       if (position < 0) {
    152         position = 0;
    153       }
    154       if (position + patLength < subLength) {
    155         index = position;
    156       }
    157     }
    158   }
    159   if (index < 0) {
    160     return -1;
    161   }
    162   return %StringLastIndexOf(sub, pat, index);
    163 }
    164 
    165 
    166 // ECMA-262 section 15.5.4.9
    167 //
    168 // This function is implementation specific.  For now, we do not
    169 // do anything locale specific.
    170 function StringLocaleCompare(other) {
    171   if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    172     throw MakeTypeError("called_on_null_or_undefined",
    173                         ["String.prototype.localeCompare"]);
    174   }
    175   if (%_ArgumentsLength() === 0) return 0;
    176   return %StringLocaleCompare(TO_STRING_INLINE(this),
    177                               TO_STRING_INLINE(other));
    178 }
    179 
    180 
    181 // ECMA-262 section 15.5.4.10
    182 function StringMatch(regexp) {
    183   if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    184     throw MakeTypeError("called_on_null_or_undefined",
    185                         ["String.prototype.match"]);
    186   }
    187   var subject = TO_STRING_INLINE(this);
    188   if (IS_REGEXP(regexp)) {
    189     if (!regexp.global) return RegExpExecNoTests(regexp, subject, 0);
    190     %_Log('regexp', 'regexp-match,%0S,%1r', [subject, regexp]);
    191     // lastMatchInfo is defined in regexp.js.
    192     return %StringMatch(subject, regexp, lastMatchInfo);
    193   }
    194   // Non-regexp argument.
    195   regexp = new $RegExp(regexp);
    196   return RegExpExecNoTests(regexp, subject, 0);
    197 }
    198 
    199 
    200 // SubString is an internal function that returns the sub string of 'string'.
    201 // If resulting string is of length 1, we use the one character cache
    202 // otherwise we call the runtime system.
    203 function SubString(string, start, end) {
    204   // Use the one character string cache.
    205   if (start + 1 == end) return %_StringCharAt(string, start);
    206   return %_SubString(string, start, end);
    207 }
    208 
    209 
    210 // This has the same size as the lastMatchInfo array, and can be used for
    211 // functions that expect that structure to be returned.  It is used when the
    212 // needle is a string rather than a regexp.  In this case we can't update
    213 // lastMatchArray without erroneously affecting the properties on the global
    214 // RegExp object.
    215 var reusableMatchInfo = [2, "", "", -1, -1];
    216 
    217 
    218 // ECMA-262, section 15.5.4.11
    219 function StringReplace(search, replace) {
    220   if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    221     throw MakeTypeError("called_on_null_or_undefined",
    222                         ["String.prototype.replace"]);
    223   }
    224   var subject = TO_STRING_INLINE(this);
    225 
    226   // Delegate to one of the regular expression variants if necessary.
    227   if (IS_REGEXP(search)) {
    228     %_Log('regexp', 'regexp-replace,%0r,%1S', [search, subject]);
    229     if (IS_SPEC_FUNCTION(replace)) {
    230       if (search.global) {
    231         return StringReplaceGlobalRegExpWithFunction(subject, search, replace);
    232       } else {
    233         return StringReplaceNonGlobalRegExpWithFunction(subject,
    234                                                         search,
    235                                                         replace);
    236       }
    237     } else {
    238       return %StringReplaceRegExpWithString(subject,
    239                                             search,
    240                                             TO_STRING_INLINE(replace),
    241                                             lastMatchInfo);
    242     }
    243   }
    244 
    245   // Convert the search argument to a string and search for it.
    246   search = TO_STRING_INLINE(search);
    247   if (search.length == 1 &&
    248       subject.length > 0xFF &&
    249       IS_STRING(replace) &&
    250       %StringIndexOf(replace, '$', 0) < 0) {
    251     // Searching by traversing a cons string tree and replace with cons of
    252     // slices works only when the replaced string is a single character, being
    253     // replaced by a simple string and only pays off for long strings.
    254     return %StringReplaceOneCharWithString(subject, search, replace);
    255   }
    256   var start = %StringIndexOf(subject, search, 0);
    257   if (start < 0) return subject;
    258   var end = start + search.length;
    259 
    260   var builder = new ReplaceResultBuilder(subject);
    261   // prefix
    262   builder.addSpecialSlice(0, start);
    263 
    264   // Compute the string to replace with.
    265   if (IS_SPEC_FUNCTION(replace)) {
    266     var receiver = %GetDefaultReceiver(replace);
    267     builder.add(%_CallFunction(receiver,
    268                                search,
    269                                start,
    270                                subject,
    271                                replace));
    272   } else {
    273     reusableMatchInfo[CAPTURE0] = start;
    274     reusableMatchInfo[CAPTURE1] = end;
    275     replace = TO_STRING_INLINE(replace);
    276     ExpandReplacement(replace, subject, reusableMatchInfo, builder);
    277   }
    278 
    279   // suffix
    280   builder.addSpecialSlice(end, subject.length);
    281 
    282   return builder.generate();
    283 }
    284 
    285 
    286 // Expand the $-expressions in the string and return a new string with
    287 // the result.
    288 function ExpandReplacement(string, subject, matchInfo, builder) {
    289   var length = string.length;
    290   var builder_elements = builder.elements;
    291   var next = %StringIndexOf(string, '$', 0);
    292   if (next < 0) {
    293     if (length > 0) builder_elements.push(string);
    294     return;
    295   }
    296 
    297   // Compute the number of captures; see ECMA-262, 15.5.4.11, p. 102.
    298   var m = NUMBER_OF_CAPTURES(matchInfo) >> 1;  // Includes the match.
    299 
    300   if (next > 0) builder_elements.push(SubString(string, 0, next));
    301 
    302   while (true) {
    303     var expansion = '$';
    304     var position = next + 1;
    305     if (position < length) {
    306       var peek = %_StringCharCodeAt(string, position);
    307       if (peek == 36) {         // $$
    308         ++position;
    309         builder_elements.push('$');
    310       } else if (peek == 38) {  // $& - match
    311         ++position;
    312         builder.addSpecialSlice(matchInfo[CAPTURE0],
    313                                 matchInfo[CAPTURE1]);
    314       } else if (peek == 96) {  // $` - prefix
    315         ++position;
    316         builder.addSpecialSlice(0, matchInfo[CAPTURE0]);
    317       } else if (peek == 39) {  // $' - suffix
    318         ++position;
    319         builder.addSpecialSlice(matchInfo[CAPTURE1], subject.length);
    320       } else if (peek >= 48 && peek <= 57) {  // $n, 0 <= n <= 9
    321         ++position;
    322         var n = peek - 48;
    323         if (position < length) {
    324           peek = %_StringCharCodeAt(string, position);
    325           // $nn, 01 <= nn <= 99
    326           if (n != 0 && peek == 48 || peek >= 49 && peek <= 57) {
    327             var nn = n * 10 + (peek - 48);
    328             if (nn < m) {
    329               // If the two digit capture reference is within range of
    330               // the captures, we use it instead of the single digit
    331               // one. Otherwise, we fall back to using the single
    332               // digit reference. This matches the behavior of
    333               // SpiderMonkey.
    334               ++position;
    335               n = nn;
    336             }
    337           }
    338         }
    339         if (0 < n && n < m) {
    340           addCaptureString(builder, matchInfo, n);
    341         } else {
    342           // Because of the captures range check in the parsing of two
    343           // digit capture references, we can only enter here when a
    344           // single digit capture reference is outside the range of
    345           // captures.
    346           builder_elements.push('$');
    347           --position;
    348         }
    349       } else {
    350         builder_elements.push('$');
    351       }
    352     } else {
    353       builder_elements.push('$');
    354     }
    355 
    356     // Go the the next $ in the string.
    357     next = %StringIndexOf(string, '$', position);
    358 
    359     // Return if there are no more $ characters in the string. If we
    360     // haven't reached the end, we need to append the suffix.
    361     if (next < 0) {
    362       if (position < length) {
    363         builder_elements.push(SubString(string, position, length));
    364       }
    365       return;
    366     }
    367 
    368     // Append substring between the previous and the next $ character.
    369     if (next > position) {
    370       builder_elements.push(SubString(string, position, next));
    371     }
    372   }
    373 }
    374 
    375 
    376 // Compute the string of a given regular expression capture.
    377 function CaptureString(string, lastCaptureInfo, index) {
    378   // Scale the index.
    379   var scaled = index << 1;
    380   // Compute start and end.
    381   var start = lastCaptureInfo[CAPTURE(scaled)];
    382   // If start isn't valid, return undefined.
    383   if (start < 0) return;
    384   var end = lastCaptureInfo[CAPTURE(scaled + 1)];
    385   return SubString(string, start, end);
    386 }
    387 
    388 
    389 // Add the string of a given regular expression capture to the
    390 // ReplaceResultBuilder
    391 function addCaptureString(builder, matchInfo, index) {
    392   // Scale the index.
    393   var scaled = index << 1;
    394   // Compute start and end.
    395   var start = matchInfo[CAPTURE(scaled)];
    396   if (start < 0) return;
    397   var end = matchInfo[CAPTURE(scaled + 1)];
    398   builder.addSpecialSlice(start, end);
    399 }
    400 
    401 // TODO(lrn): This array will survive indefinitely if replace is never
    402 // called again. However, it will be empty, since the contents are cleared
    403 // in the finally block.
    404 var reusableReplaceArray = new InternalArray(16);
    405 
    406 // Helper function for replacing regular expressions with the result of a
    407 // function application in String.prototype.replace.
    408 function StringReplaceGlobalRegExpWithFunction(subject, regexp, replace) {
    409   var resultArray = reusableReplaceArray;
    410   if (resultArray) {
    411     reusableReplaceArray = null;
    412   } else {
    413     // Inside a nested replace (replace called from the replacement function
    414     // of another replace) or we have failed to set the reusable array
    415     // back due to an exception in a replacement function. Create a new
    416     // array to use in the future, or until the original is written back.
    417     resultArray = new InternalArray(16);
    418   }
    419   var res = %RegExpExecMultiple(regexp,
    420                                 subject,
    421                                 lastMatchInfo,
    422                                 resultArray);
    423   regexp.lastIndex = 0;
    424   if (IS_NULL(res)) {
    425     // No matches at all.
    426     reusableReplaceArray = resultArray;
    427     return subject;
    428   }
    429   var len = res.length;
    430   var i = 0;
    431   if (NUMBER_OF_CAPTURES(lastMatchInfo) == 2) {
    432     var match_start = 0;
    433     var override = new InternalArray(null, 0, subject);
    434     var receiver = %GetDefaultReceiver(replace);
    435     while (i < len) {
    436       var elem = res[i];
    437       if (%_IsSmi(elem)) {
    438         if (elem > 0) {
    439           match_start = (elem >> 11) + (elem & 0x7ff);
    440         } else {
    441           match_start = res[++i] - elem;
    442         }
    443       } else {
    444         override[0] = elem;
    445         override[1] = match_start;
    446         lastMatchInfoOverride = override;
    447         var func_result =
    448             %_CallFunction(receiver, elem, match_start, subject, replace);
    449         res[i] = TO_STRING_INLINE(func_result);
    450         match_start += elem.length;
    451       }
    452       i++;
    453     }
    454   } else {
    455     var receiver = %GetDefaultReceiver(replace);
    456     while (i < len) {
    457       var elem = res[i];
    458       if (!%_IsSmi(elem)) {
    459         // elem must be an Array.
    460         // Use the apply argument as backing for global RegExp properties.
    461         lastMatchInfoOverride = elem;
    462         var func_result = %Apply(replace, receiver, elem, 0, elem.length);
    463         res[i] = TO_STRING_INLINE(func_result);
    464       }
    465       i++;
    466     }
    467   }
    468   var resultBuilder = new ReplaceResultBuilder(subject, res);
    469   var result = resultBuilder.generate();
    470   resultArray.length = 0;
    471   reusableReplaceArray = resultArray;
    472   return result;
    473 }
    474 
    475 
    476 function StringReplaceNonGlobalRegExpWithFunction(subject, regexp, replace) {
    477   var matchInfo = DoRegExpExec(regexp, subject, 0);
    478   if (IS_NULL(matchInfo)) return subject;
    479   var result = new ReplaceResultBuilder(subject);
    480   var index = matchInfo[CAPTURE0];
    481   result.addSpecialSlice(0, index);
    482   var endOfMatch = matchInfo[CAPTURE1];
    483   // Compute the parameter list consisting of the match, captures, index,
    484   // and subject for the replace function invocation.
    485   // The number of captures plus one for the match.
    486   var m = NUMBER_OF_CAPTURES(matchInfo) >> 1;
    487   var replacement;
    488   var receiver = %GetDefaultReceiver(replace);
    489   if (m == 1) {
    490     // No captures, only the match, which is always valid.
    491     var s = SubString(subject, index, endOfMatch);
    492     // Don't call directly to avoid exposing the built-in global object.
    493     replacement =
    494         %_CallFunction(receiver, s, index, subject, replace);
    495   } else {
    496     var parameters = new InternalArray(m + 2);
    497     for (var j = 0; j < m; j++) {
    498       parameters[j] = CaptureString(subject, matchInfo, j);
    499     }
    500     parameters[j] = index;
    501     parameters[j + 1] = subject;
    502 
    503     replacement = %Apply(replace, receiver, parameters, 0, j + 2);
    504   }
    505 
    506   result.add(replacement);  // The add method converts to string if necessary.
    507   // Can't use matchInfo any more from here, since the function could
    508   // overwrite it.
    509   result.addSpecialSlice(endOfMatch, subject.length);
    510   return result.generate();
    511 }
    512 
    513 
    514 // ECMA-262 section 15.5.4.12
    515 function StringSearch(re) {
    516   if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    517     throw MakeTypeError("called_on_null_or_undefined",
    518                         ["String.prototype.search"]);
    519   }
    520   var regexp;
    521   if (IS_STRING(re)) {
    522     regexp = %_GetFromCache(STRING_TO_REGEXP_CACHE_ID, re);
    523   } else if (IS_REGEXP(re)) {
    524     regexp = re;
    525   } else {
    526     regexp = new $RegExp(re);
    527   }
    528   var match = DoRegExpExec(regexp, TO_STRING_INLINE(this), 0);
    529   if (match) {
    530     return match[CAPTURE0];
    531   }
    532   return -1;
    533 }
    534 
    535 
    536 // ECMA-262 section 15.5.4.13
    537 function StringSlice(start, end) {
    538   if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    539     throw MakeTypeError("called_on_null_or_undefined",
    540                         ["String.prototype.slice"]);
    541   }
    542   var s = TO_STRING_INLINE(this);
    543   var s_len = s.length;
    544   var start_i = TO_INTEGER(start);
    545   var end_i = s_len;
    546   if (end !== void 0) {
    547     end_i = TO_INTEGER(end);
    548   }
    549 
    550   if (start_i < 0) {
    551     start_i += s_len;
    552     if (start_i < 0) {
    553       start_i = 0;
    554     }
    555   } else {
    556     if (start_i > s_len) {
    557       return '';
    558     }
    559   }
    560 
    561   if (end_i < 0) {
    562     end_i += s_len;
    563     if (end_i < 0) {
    564       return '';
    565     }
    566   } else {
    567     if (end_i > s_len) {
    568       end_i = s_len;
    569     }
    570   }
    571 
    572   if (end_i <= start_i) {
    573     return '';
    574   }
    575 
    576   return SubString(s, start_i, end_i);
    577 }
    578 
    579 
    580 // ECMA-262 section 15.5.4.14
    581 function StringSplit(separator, limit) {
    582   if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    583     throw MakeTypeError("called_on_null_or_undefined",
    584                         ["String.prototype.split"]);
    585   }
    586   var subject = TO_STRING_INLINE(this);
    587   limit = (IS_UNDEFINED(limit)) ? 0xffffffff : TO_UINT32(limit);
    588 
    589   // ECMA-262 says that if separator is undefined, the result should
    590   // be an array of size 1 containing the entire string.
    591   if (IS_UNDEFINED(separator)) {
    592     return [subject];
    593   }
    594 
    595   var length = subject.length;
    596   if (!IS_REGEXP(separator)) {
    597     separator = TO_STRING_INLINE(separator);
    598 
    599     if (limit === 0) return [];
    600 
    601     var separator_length = separator.length;
    602 
    603     // If the separator string is empty then return the elements in the subject.
    604     if (separator_length === 0) return %StringToArray(subject, limit);
    605 
    606     var result = %StringSplit(subject, separator, limit);
    607 
    608     return result;
    609   }
    610 
    611   if (limit === 0) return [];
    612 
    613   // Separator is a regular expression.
    614   return StringSplitOnRegExp(subject, separator, limit, length);
    615 }
    616 
    617 
    618 function StringSplitOnRegExp(subject, separator, limit, length) {
    619   %_Log('regexp', 'regexp-split,%0S,%1r', [subject, separator]);
    620 
    621   if (length === 0) {
    622     if (DoRegExpExec(separator, subject, 0, 0) != null) {
    623       return [];
    624     }
    625     return [subject];
    626   }
    627 
    628   var currentIndex = 0;
    629   var startIndex = 0;
    630   var startMatch = 0;
    631   var result = [];
    632 
    633   outer_loop:
    634   while (true) {
    635 
    636     if (startIndex === length) {
    637       result.push(SubString(subject, currentIndex, length));
    638       break;
    639     }
    640 
    641     var matchInfo = DoRegExpExec(separator, subject, startIndex);
    642     if (matchInfo == null || length === (startMatch = matchInfo[CAPTURE0])) {
    643       result.push(SubString(subject, currentIndex, length));
    644       break;
    645     }
    646     var endIndex = matchInfo[CAPTURE1];
    647 
    648     // We ignore a zero-length match at the currentIndex.
    649     if (startIndex === endIndex && endIndex === currentIndex) {
    650       startIndex++;
    651       continue;
    652     }
    653 
    654     if (currentIndex + 1 == startMatch) {
    655       result.push(%_StringCharAt(subject, currentIndex));
    656     } else {
    657       result.push(%_SubString(subject, currentIndex, startMatch));
    658     }
    659 
    660     if (result.length === limit) break;
    661 
    662     var matchinfo_len = NUMBER_OF_CAPTURES(matchInfo) + REGEXP_FIRST_CAPTURE;
    663     for (var i = REGEXP_FIRST_CAPTURE + 2; i < matchinfo_len; ) {
    664       var start = matchInfo[i++];
    665       var end = matchInfo[i++];
    666       if (end != -1) {
    667         if (start + 1 == end) {
    668           result.push(%_StringCharAt(subject, start));
    669         } else {
    670           result.push(%_SubString(subject, start, end));
    671         }
    672       } else {
    673         result.push(void 0);
    674       }
    675       if (result.length === limit) break outer_loop;
    676     }
    677 
    678     startIndex = currentIndex = endIndex;
    679   }
    680   return result;
    681 }
    682 
    683 
    684 // ECMA-262 section 15.5.4.15
    685 function StringSubstring(start, end) {
    686   if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    687     throw MakeTypeError("called_on_null_or_undefined",
    688                         ["String.prototype.subString"]);
    689   }
    690   var s = TO_STRING_INLINE(this);
    691   var s_len = s.length;
    692 
    693   var start_i = TO_INTEGER(start);
    694   if (start_i < 0) {
    695     start_i = 0;
    696   } else if (start_i > s_len) {
    697     start_i = s_len;
    698   }
    699 
    700   var end_i = s_len;
    701   if (!IS_UNDEFINED(end)) {
    702     end_i = TO_INTEGER(end);
    703     if (end_i > s_len) {
    704       end_i = s_len;
    705     } else {
    706       if (end_i < 0) end_i = 0;
    707       if (start_i > end_i) {
    708         var tmp = end_i;
    709         end_i = start_i;
    710         start_i = tmp;
    711       }
    712     }
    713   }
    714 
    715   return ((start_i + 1 == end_i)
    716           ? %_StringCharAt(s, start_i)
    717           : %_SubString(s, start_i, end_i));
    718 }
    719 
    720 
    721 // This is not a part of ECMA-262.
    722 function StringSubstr(start, n) {
    723   if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    724     throw MakeTypeError("called_on_null_or_undefined",
    725                         ["String.prototype.substr"]);
    726   }
    727   var s = TO_STRING_INLINE(this);
    728   var len;
    729 
    730   // Correct n: If not given, set to string length; if explicitly
    731   // set to undefined, zero, or negative, returns empty string.
    732   if (n === void 0) {
    733     len = s.length;
    734   } else {
    735     len = TO_INTEGER(n);
    736     if (len <= 0) return '';
    737   }
    738 
    739   // Correct start: If not given (or undefined), set to zero; otherwise
    740   // convert to integer and handle negative case.
    741   if (start === void 0) {
    742     start = 0;
    743   } else {
    744     start = TO_INTEGER(start);
    745     // If positive, and greater than or equal to the string length,
    746     // return empty string.
    747     if (start >= s.length) return '';
    748     // If negative and absolute value is larger than the string length,
    749     // use zero.
    750     if (start < 0) {
    751       start += s.length;
    752       if (start < 0) start = 0;
    753     }
    754   }
    755 
    756   var end = start + len;
    757   if (end > s.length) end = s.length;
    758 
    759   return ((start + 1 == end)
    760           ? %_StringCharAt(s, start)
    761           : %_SubString(s, start, end));
    762 }
    763 
    764 
    765 // ECMA-262, 15.5.4.16
    766 function StringToLowerCase() {
    767   if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    768     throw MakeTypeError("called_on_null_or_undefined",
    769                         ["String.prototype.toLowerCase"]);
    770   }
    771   return %StringToLowerCase(TO_STRING_INLINE(this));
    772 }
    773 
    774 
    775 // ECMA-262, 15.5.4.17
    776 function StringToLocaleLowerCase() {
    777   if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    778     throw MakeTypeError("called_on_null_or_undefined",
    779                         ["String.prototype.toLocaleLowerCase"]);
    780   }
    781   return %StringToLowerCase(TO_STRING_INLINE(this));
    782 }
    783 
    784 
    785 // ECMA-262, 15.5.4.18
    786 function StringToUpperCase() {
    787   if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    788     throw MakeTypeError("called_on_null_or_undefined",
    789                         ["String.prototype.toUpperCase"]);
    790   }
    791   return %StringToUpperCase(TO_STRING_INLINE(this));
    792 }
    793 
    794 
    795 // ECMA-262, 15.5.4.19
    796 function StringToLocaleUpperCase() {
    797   if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    798     throw MakeTypeError("called_on_null_or_undefined",
    799                         ["String.prototype.toLocaleUpperCase"]);
    800   }
    801   return %StringToUpperCase(TO_STRING_INLINE(this));
    802 }
    803 
    804 // ES5, 15.5.4.20
    805 function StringTrim() {
    806   if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    807     throw MakeTypeError("called_on_null_or_undefined",
    808                         ["String.prototype.trim"]);
    809   }
    810   return %StringTrim(TO_STRING_INLINE(this), true, true);
    811 }
    812 
    813 function StringTrimLeft() {
    814   if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    815     throw MakeTypeError("called_on_null_or_undefined",
    816                         ["String.prototype.trimLeft"]);
    817   }
    818   return %StringTrim(TO_STRING_INLINE(this), true, false);
    819 }
    820 
    821 function StringTrimRight() {
    822   if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    823     throw MakeTypeError("called_on_null_or_undefined",
    824                         ["String.prototype.trimRight"]);
    825   }
    826   return %StringTrim(TO_STRING_INLINE(this), false, true);
    827 }
    828 
    829 var static_charcode_array = new InternalArray(4);
    830 
    831 // ECMA-262, section 15.5.3.2
    832 function StringFromCharCode(code) {
    833   var n = %_ArgumentsLength();
    834   if (n == 1) {
    835     if (!%_IsSmi(code)) code = ToNumber(code);
    836     return %_StringCharFromCode(code & 0xffff);
    837   }
    838 
    839   // NOTE: This is not super-efficient, but it is necessary because we
    840   // want to avoid converting to numbers from within the virtual
    841   // machine. Maybe we can find another way of doing this?
    842   var codes = static_charcode_array;
    843   for (var i = 0; i < n; i++) {
    844     var code = %_Arguments(i);
    845     if (!%_IsSmi(code)) code = ToNumber(code);
    846     codes[i] = code;
    847   }
    848   codes.length = n;
    849   return %StringFromCharCodeArray(codes);
    850 }
    851 
    852 
    853 // Helper function for very basic XSS protection.
    854 function HtmlEscape(str) {
    855   return TO_STRING_INLINE(str).replace(/</g, "&lt;")
    856                               .replace(/>/g, "&gt;")
    857                               .replace(/"/g, "&quot;")
    858                               .replace(/'/g, "&#039;");
    859 }
    860 
    861 
    862 // Compatibility support for KJS.
    863 // Tested by mozilla/js/tests/js1_5/Regress/regress-276103.js.
    864 function StringLink(s) {
    865   return "<a href=\"" + HtmlEscape(s) + "\">" + this + "</a>";
    866 }
    867 
    868 
    869 function StringAnchor(name) {
    870   return "<a name=\"" + HtmlEscape(name) + "\">" + this + "</a>";
    871 }
    872 
    873 
    874 function StringFontcolor(color) {
    875   return "<font color=\"" + HtmlEscape(color) + "\">" + this + "</font>";
    876 }
    877 
    878 
    879 function StringFontsize(size) {
    880   return "<font size=\"" + HtmlEscape(size) + "\">" + this + "</font>";
    881 }
    882 
    883 
    884 function StringBig() {
    885   return "<big>" + this + "</big>";
    886 }
    887 
    888 
    889 function StringBlink() {
    890   return "<blink>" + this + "</blink>";
    891 }
    892 
    893 
    894 function StringBold() {
    895   return "<b>" + this + "</b>";
    896 }
    897 
    898 
    899 function StringFixed() {
    900   return "<tt>" + this + "</tt>";
    901 }
    902 
    903 
    904 function StringItalics() {
    905   return "<i>" + this + "</i>";
    906 }
    907 
    908 
    909 function StringSmall() {
    910   return "<small>" + this + "</small>";
    911 }
    912 
    913 
    914 function StringStrike() {
    915   return "<strike>" + this + "</strike>";
    916 }
    917 
    918 
    919 function StringSub() {
    920   return "<sub>" + this + "</sub>";
    921 }
    922 
    923 
    924 function StringSup() {
    925   return "<sup>" + this + "</sup>";
    926 }
    927 
    928 
    929 // ReplaceResultBuilder support.
    930 function ReplaceResultBuilder(str) {
    931   if (%_ArgumentsLength() > 1) {
    932     this.elements = %_Arguments(1);
    933   } else {
    934     this.elements = new InternalArray();
    935   }
    936   this.special_string = str;
    937 }
    938 
    939 SetUpLockedPrototype(ReplaceResultBuilder,
    940   $Array("elements", "special_string"), $Array(
    941   "add", function(str) {
    942     str = TO_STRING_INLINE(str);
    943     if (str.length > 0) this.elements.push(str);
    944   },
    945   "addSpecialSlice", function(start, end) {
    946     var len = end - start;
    947     if (start < 0 || len <= 0) return;
    948     if (start < 0x80000 && len < 0x800) {
    949       this.elements.push((start << 11) | len);
    950     } else {
    951       // 0 < len <= String::kMaxLength and Smi::kMaxValue >= String::kMaxLength,
    952       // so -len is a smi.
    953       var elements = this.elements;
    954       elements.push(-len);
    955       elements.push(start);
    956     }
    957   },
    958   "generate", function() {
    959     var elements = this.elements;
    960     return %StringBuilderConcat(elements, elements.length, this.special_string);
    961   }
    962 ));
    963 
    964 
    965 // -------------------------------------------------------------------
    966 
    967 function SetUpString() {
    968   %CheckIsBootstrapping();
    969   // Set up the constructor property on the String prototype object.
    970   %SetProperty($String.prototype, "constructor", $String, DONT_ENUM);
    971 
    972 
    973   // Set up the non-enumerable functions on the String object.
    974   InstallFunctions($String, DONT_ENUM, $Array(
    975     "fromCharCode", StringFromCharCode
    976   ));
    977 
    978 
    979   // Set up the non-enumerable functions on the String prototype object.
    980   InstallFunctions($String.prototype, DONT_ENUM, $Array(
    981     "valueOf", StringValueOf,
    982     "toString", StringToString,
    983     "charAt", StringCharAt,
    984     "charCodeAt", StringCharCodeAt,
    985     "concat", StringConcat,
    986     "indexOf", StringIndexOf,
    987     "lastIndexOf", StringLastIndexOf,
    988     "localeCompare", StringLocaleCompare,
    989     "match", StringMatch,
    990     "replace", StringReplace,
    991     "search", StringSearch,
    992     "slice", StringSlice,
    993     "split", StringSplit,
    994     "substring", StringSubstring,
    995     "substr", StringSubstr,
    996     "toLowerCase", StringToLowerCase,
    997     "toLocaleLowerCase", StringToLocaleLowerCase,
    998     "toUpperCase", StringToUpperCase,
    999     "toLocaleUpperCase", StringToLocaleUpperCase,
   1000     "trim", StringTrim,
   1001     "trimLeft", StringTrimLeft,
   1002     "trimRight", StringTrimRight,
   1003     "link", StringLink,
   1004     "anchor", StringAnchor,
   1005     "fontcolor", StringFontcolor,
   1006     "fontsize", StringFontsize,
   1007     "big", StringBig,
   1008     "blink", StringBlink,
   1009     "bold", StringBold,
   1010     "fixed", StringFixed,
   1011     "italics", StringItalics,
   1012     "small", StringSmall,
   1013     "strike", StringStrike,
   1014     "sub", StringSub,
   1015     "sup", StringSup
   1016   ));
   1017 }
   1018 
   1019 SetUpString();
   1020