Home | History | Annotate | Download | only in src
      1 // Copyright 2006-2009 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 // const $String = global.String;
     32 // const $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   return %_ValueOf(this);
     52 }
     53 
     54 
     55 // ECMA-262 section 15.5.4.3
     56 function StringValueOf() {
     57   if (!IS_STRING(this) && !IS_STRING_WRAPPER(this))
     58     throw new $TypeError('String.prototype.valueOf is not generic');
     59   return %_ValueOf(this);
     60 }
     61 
     62 
     63 // ECMA-262, section 15.5.4.4
     64 function StringCharAt(pos) {
     65   var char_code = %_FastCharCodeAt(this, pos);
     66   if (!%_IsSmi(char_code)) {
     67     var subject = TO_STRING_INLINE(this);
     68     var index = TO_INTEGER(pos);
     69     if (index >= subject.length || index < 0) return "";
     70     char_code = %StringCharCodeAt(subject, index);
     71   }
     72   return %CharFromCode(char_code);
     73 }
     74 
     75 
     76 // ECMA-262 section 15.5.4.5
     77 function StringCharCodeAt(pos) {
     78   var fast_answer = %_FastCharCodeAt(this, pos);
     79   if (%_IsSmi(fast_answer)) {
     80     return fast_answer;
     81   }
     82   var subject = TO_STRING_INLINE(this);
     83   var index = TO_INTEGER(pos);
     84   return %StringCharCodeAt(subject, index);
     85 }
     86 
     87 
     88 // ECMA-262, section 15.5.4.6
     89 function StringConcat() {
     90   var len = %_ArgumentsLength();
     91   var this_as_string = TO_STRING_INLINE(this);
     92   if (len === 1) {
     93     return this_as_string + %_Arguments(0);
     94   }
     95   var parts = new $Array(len + 1);
     96   parts[0] = this_as_string;
     97   for (var i = 0; i < len; i++) {
     98     var part = %_Arguments(i);
     99     parts[i + 1] = TO_STRING_INLINE(part);
    100   }
    101   return %StringBuilderConcat(parts, len + 1, "");
    102 }
    103 
    104 // Match ES3 and Safari
    105 %FunctionSetLength(StringConcat, 1);
    106 
    107 
    108 // ECMA-262 section 15.5.4.7
    109 function StringIndexOf(searchString /* position */) {  // length == 1
    110   var subject_str = TO_STRING_INLINE(this);
    111   var pattern_str = TO_STRING_INLINE(searchString);
    112   var subject_str_len = subject_str.length;
    113   var pattern_str_len = pattern_str.length;
    114   var index = 0;
    115   if (%_ArgumentsLength() > 1) {
    116     var arg1 = %_Arguments(1);  // position
    117     index = TO_INTEGER(arg1);
    118   }
    119   if (index < 0) index = 0;
    120   if (index > subject_str_len) index = subject_str_len;
    121   if (pattern_str_len + index > subject_str_len) return -1;
    122   return %StringIndexOf(subject_str, pattern_str, index);
    123 }
    124 
    125 
    126 // ECMA-262 section 15.5.4.8
    127 function StringLastIndexOf(searchString /* position */) {  // length == 1
    128   var sub = TO_STRING_INLINE(this);
    129   var subLength = sub.length;
    130   var pat = TO_STRING_INLINE(searchString);
    131   var patLength = pat.length;
    132   var index = subLength - patLength;
    133   if (%_ArgumentsLength() > 1) {
    134     var position = ToNumber(%_Arguments(1));
    135     if (!$isNaN(position)) {
    136       position = TO_INTEGER(position);
    137       if (position < 0) {
    138         position = 0;
    139       }
    140       if (position + patLength < subLength) {
    141         index = position
    142       }
    143     }
    144   }
    145   if (index < 0) {
    146     return -1;
    147   }
    148   return %StringLastIndexOf(sub, pat, index);
    149 }
    150 
    151 
    152 // ECMA-262 section 15.5.4.9
    153 //
    154 // This function is implementation specific.  For now, we do not
    155 // do anything locale specific.
    156 function StringLocaleCompare(other) {
    157   if (%_ArgumentsLength() === 0) return 0;
    158 
    159   var this_str = TO_STRING_INLINE(this);
    160   var other_str = TO_STRING_INLINE(other);
    161   return %StringLocaleCompare(this_str, other_str);
    162 }
    163 
    164 
    165 // ECMA-262 section 15.5.4.10
    166 function StringMatch(regexp) {
    167   if (!IS_REGEXP(regexp)) regexp = new $RegExp(regexp);
    168   var subject = TO_STRING_INLINE(this);
    169 
    170   if (!regexp.global) return regexp.exec(subject);
    171   %_Log('regexp', 'regexp-match,%0S,%1r', [subject, regexp]);
    172   // lastMatchInfo is defined in regexp.js.
    173   return %StringMatch(subject, regexp, lastMatchInfo);
    174 }
    175 
    176 
    177 // SubString is an internal function that returns the sub string of 'string'.
    178 // If resulting string is of length 1, we use the one character cache
    179 // otherwise we call the runtime system.
    180 function SubString(string, start, end) {
    181   // Use the one character string cache.
    182   if (start + 1 == end) {
    183     var char_code = %_FastCharCodeAt(string, start);
    184     if (!%_IsSmi(char_code)) {
    185       char_code = %StringCharCodeAt(string, start);
    186     }
    187     return %CharFromCode(char_code);
    188   }
    189   return %_SubString(string, start, end);
    190 }
    191 
    192 
    193 // This has the same size as the lastMatchInfo array, and can be used for
    194 // functions that expect that structure to be returned.  It is used when the
    195 // needle is a string rather than a regexp.  In this case we can't update
    196 // lastMatchArray without erroneously affecting the properties on the global
    197 // RegExp object.
    198 var reusableMatchInfo = [2, "", "", -1, -1];
    199 
    200 
    201 // ECMA-262, section 15.5.4.11
    202 function StringReplace(search, replace) {
    203   var subject = TO_STRING_INLINE(this);
    204 
    205   // Delegate to one of the regular expression variants if necessary.
    206   if (IS_REGEXP(search)) {
    207     %_Log('regexp', 'regexp-replace,%0r,%1S', [search, subject]);
    208     if (IS_FUNCTION(replace)) {
    209       return StringReplaceRegExpWithFunction(subject, search, replace);
    210     } else {
    211       return StringReplaceRegExp(subject, search, replace);
    212     }
    213   }
    214 
    215   // Convert the search argument to a string and search for it.
    216   search = TO_STRING_INLINE(search);
    217   var start = %StringIndexOf(subject, search, 0);
    218   if (start < 0) return subject;
    219   var end = start + search.length;
    220 
    221   var builder = new ReplaceResultBuilder(subject);
    222   // prefix
    223   builder.addSpecialSlice(0, start);
    224 
    225   // Compute the string to replace with.
    226   if (IS_FUNCTION(replace)) {
    227     builder.add(replace.call(null, search, start, subject));
    228   } else {
    229     reusableMatchInfo[CAPTURE0] = start;
    230     reusableMatchInfo[CAPTURE1] = end;
    231     replace = TO_STRING_INLINE(replace);
    232     ExpandReplacement(replace, subject, reusableMatchInfo, builder);
    233   }
    234 
    235   // suffix
    236   builder.addSpecialSlice(end, subject.length);
    237 
    238   return builder.generate();
    239 }
    240 
    241 
    242 // Helper function for regular expressions in String.prototype.replace.
    243 function StringReplaceRegExp(subject, regexp, replace) {
    244   replace = TO_STRING_INLINE(replace);
    245   return %StringReplaceRegExpWithString(subject,
    246                                         regexp,
    247                                         replace,
    248                                         lastMatchInfo);
    249 };
    250 
    251 
    252 // Expand the $-expressions in the string and return a new string with
    253 // the result.
    254 function ExpandReplacement(string, subject, matchInfo, builder) {
    255   var next = %StringIndexOf(string, '$', 0);
    256   if (next < 0) {
    257     builder.add(string);
    258     return;
    259   }
    260 
    261   // Compute the number of captures; see ECMA-262, 15.5.4.11, p. 102.
    262   var m = NUMBER_OF_CAPTURES(matchInfo) >> 1;  // Includes the match.
    263 
    264   if (next > 0) builder.add(SubString(string, 0, next));
    265   var length = string.length;
    266 
    267   while (true) {
    268     var expansion = '$';
    269     var position = next + 1;
    270     if (position < length) {
    271       var peek = %_FastCharCodeAt(string, position);
    272       if (!%_IsSmi(peek)) {
    273         peek = %StringCharCodeAt(string, position);
    274       }
    275       if (peek == 36) {         // $$
    276         ++position;
    277         builder.add('$');
    278       } else if (peek == 38) {  // $& - match
    279         ++position;
    280         builder.addSpecialSlice(matchInfo[CAPTURE0],
    281                                 matchInfo[CAPTURE1]);
    282       } else if (peek == 96) {  // $` - prefix
    283         ++position;
    284         builder.addSpecialSlice(0, matchInfo[CAPTURE0]);
    285       } else if (peek == 39) {  // $' - suffix
    286         ++position;
    287         builder.addSpecialSlice(matchInfo[CAPTURE1], subject.length);
    288       } else if (peek >= 48 && peek <= 57) {  // $n, 0 <= n <= 9
    289         ++position;
    290         var n = peek - 48;
    291         if (position < length) {
    292           peek = %_FastCharCodeAt(string, position);
    293           if (!%_IsSmi(peek)) {
    294             peek = %StringCharCodeAt(string, position);
    295           }
    296           // $nn, 01 <= nn <= 99
    297           if (n != 0 && peek == 48 || peek >= 49 && peek <= 57) {
    298             var nn = n * 10 + (peek - 48);
    299             if (nn < m) {
    300               // If the two digit capture reference is within range of
    301               // the captures, we use it instead of the single digit
    302               // one. Otherwise, we fall back to using the single
    303               // digit reference. This matches the behavior of
    304               // SpiderMonkey.
    305               ++position;
    306               n = nn;
    307             }
    308           }
    309         }
    310         if (0 < n && n < m) {
    311           addCaptureString(builder, matchInfo, n);
    312         } else {
    313           // Because of the captures range check in the parsing of two
    314           // digit capture references, we can only enter here when a
    315           // single digit capture reference is outside the range of
    316           // captures.
    317           builder.add('$');
    318           --position;
    319         }
    320       } else {
    321         builder.add('$');
    322       }
    323     } else {
    324       builder.add('$');
    325     }
    326 
    327     // Go the the next $ in the string.
    328     next = %StringIndexOf(string, '$', position);
    329 
    330     // Return if there are no more $ characters in the string. If we
    331     // haven't reached the end, we need to append the suffix.
    332     if (next < 0) {
    333       if (position < length) {
    334         builder.add(SubString(string, position, length));
    335       }
    336       return;
    337     }
    338 
    339     // Append substring between the previous and the next $ character.
    340     builder.add(SubString(string, position, next));
    341   }
    342 };
    343 
    344 
    345 // Compute the string of a given regular expression capture.
    346 function CaptureString(string, lastCaptureInfo, index) {
    347   // Scale the index.
    348   var scaled = index << 1;
    349   // Compute start and end.
    350   var start = lastCaptureInfo[CAPTURE(scaled)];
    351   var end = lastCaptureInfo[CAPTURE(scaled + 1)];
    352   // If either start or end is missing return undefined.
    353   if (start < 0 || end < 0) return;
    354   return SubString(string, start, end);
    355 };
    356 
    357 
    358 // Add the string of a given regular expression capture to the
    359 // ReplaceResultBuilder
    360 function addCaptureString(builder, matchInfo, index) {
    361   // Scale the index.
    362   var scaled = index << 1;
    363   // Compute start and end.
    364   var start = matchInfo[CAPTURE(scaled)];
    365   var end = matchInfo[CAPTURE(scaled + 1)];
    366   // If either start or end is missing return.
    367   if (start < 0 || end <= start) return;
    368   builder.addSpecialSlice(start, end);
    369 };
    370 
    371 
    372 // Helper function for replacing regular expressions with the result of a
    373 // function application in String.prototype.replace.  The function application
    374 // must be interleaved with the regexp matching (contrary to ECMA-262
    375 // 15.5.4.11) to mimic SpiderMonkey and KJS behavior when the function uses
    376 // the static properties of the RegExp constructor.  Example:
    377 //     'abcd'.replace(/(.)/g, function() { return RegExp.$1; }
    378 // should be 'abcd' and not 'dddd' (or anything else).
    379 function StringReplaceRegExpWithFunction(subject, regexp, replace) {
    380   var matchInfo = DoRegExpExec(regexp, subject, 0);
    381   if (IS_NULL(matchInfo)) return subject;
    382 
    383   var result = new ReplaceResultBuilder(subject);
    384   // There's at least one match.  If the regexp is global, we have to loop
    385   // over all matches.  The loop is not in C++ code here like the one in
    386   // RegExp.prototype.exec, because of the interleaved function application.
    387   // Unfortunately, that means this code is nearly duplicated, here and in
    388   // jsregexp.cc.
    389   if (regexp.global) {
    390     var numberOfCaptures = NUMBER_OF_CAPTURES(matchInfo) >> 1;
    391     var previous = 0;
    392     do {
    393       var startOfMatch = matchInfo[CAPTURE0];
    394       result.addSpecialSlice(previous, startOfMatch);
    395       previous = matchInfo[CAPTURE1];
    396       if (numberOfCaptures == 1) {
    397         var match = SubString(subject, startOfMatch, previous);
    398         // Don't call directly to avoid exposing the built-in global object.
    399         result.add(replace.call(null, match, startOfMatch, subject));
    400       } else {
    401         result.add(ApplyReplacementFunction(replace, matchInfo, subject));
    402       }
    403       // Can't use matchInfo any more from here, since the function could
    404       // overwrite it.
    405       // Continue with the next match.
    406       // Increment previous if we matched an empty string, as per ECMA-262
    407       // 15.5.4.10.
    408       if (previous == startOfMatch) {
    409         // Add the skipped character to the output, if any.
    410         if (previous < subject.length) {
    411           result.addSpecialSlice(previous, previous + 1);
    412         }
    413         previous++;
    414       }
    415 
    416       // Per ECMA-262 15.10.6.2, if the previous index is greater than the
    417       // string length, there is no match
    418       matchInfo = (previous > subject.length)
    419           ? null
    420           : DoRegExpExec(regexp, subject, previous);
    421     } while (!IS_NULL(matchInfo));
    422 
    423     // Tack on the final right substring after the last match, if necessary.
    424     if (previous < subject.length) {
    425       result.addSpecialSlice(previous, subject.length);
    426     }
    427   } else { // Not a global regexp, no need to loop.
    428     result.addSpecialSlice(0, matchInfo[CAPTURE0]);
    429     var endOfMatch = matchInfo[CAPTURE1];
    430     result.add(ApplyReplacementFunction(replace, matchInfo, subject));
    431     // Can't use matchInfo any more from here, since the function could
    432     // overwrite it.
    433     result.addSpecialSlice(endOfMatch, subject.length);
    434   }
    435 
    436   return result.generate();
    437 }
    438 
    439 
    440 // Helper function to apply a string replacement function once.
    441 function ApplyReplacementFunction(replace, matchInfo, subject) {
    442   // Compute the parameter list consisting of the match, captures, index,
    443   // and subject for the replace function invocation.
    444   var index = matchInfo[CAPTURE0];
    445   // The number of captures plus one for the match.
    446   var m = NUMBER_OF_CAPTURES(matchInfo) >> 1;
    447   if (m == 1) {
    448     var s = CaptureString(subject, matchInfo, 0);
    449     // Don't call directly to avoid exposing the built-in global object.
    450     return replace.call(null, s, index, subject);
    451   }
    452   var parameters = $Array(m + 2);
    453   for (var j = 0; j < m; j++) {
    454     parameters[j] = CaptureString(subject, matchInfo, j);
    455   }
    456   parameters[j] = index;
    457   parameters[j + 1] = subject;
    458   return replace.apply(null, parameters);
    459 }
    460 
    461 
    462 // ECMA-262 section 15.5.4.12
    463 function StringSearch(re) {
    464   var regexp = new $RegExp(re);
    465   var s = TO_STRING_INLINE(this);
    466   var last_idx = regexp.lastIndex; // keep old lastIndex
    467   regexp.lastIndex = 0;            // ignore re.global property
    468   var result = regexp.exec(s);
    469   regexp.lastIndex = last_idx;     // restore lastIndex
    470   if (result == null)
    471     return -1;
    472   else
    473     return result.index;
    474 }
    475 
    476 
    477 // ECMA-262 section 15.5.4.13
    478 function StringSlice(start, end) {
    479   var s = TO_STRING_INLINE(this);
    480   var s_len = s.length;
    481   var start_i = TO_INTEGER(start);
    482   var end_i = s_len;
    483   if (end !== void 0)
    484     end_i = TO_INTEGER(end);
    485 
    486   if (start_i < 0) {
    487     start_i += s_len;
    488     if (start_i < 0)
    489       start_i = 0;
    490   } else {
    491     if (start_i > s_len)
    492       start_i = s_len;
    493   }
    494 
    495   if (end_i < 0) {
    496     end_i += s_len;
    497     if (end_i < 0)
    498       end_i = 0;
    499   } else {
    500     if (end_i > s_len)
    501       end_i = s_len;
    502   }
    503 
    504   var num_c = end_i - start_i;
    505   if (num_c < 0)
    506     num_c = 0;
    507 
    508   return SubString(s, start_i, start_i + num_c);
    509 }
    510 
    511 
    512 // ECMA-262 section 15.5.4.14
    513 function StringSplit(separator, limit) {
    514   var subject = TO_STRING_INLINE(this);
    515   limit = (IS_UNDEFINED(limit)) ? 0xffffffff : TO_UINT32(limit);
    516   if (limit === 0) return [];
    517 
    518   // ECMA-262 says that if separator is undefined, the result should
    519   // be an array of size 1 containing the entire string.  SpiderMonkey
    520   // and KJS have this behaviour only when no separator is given.  If
    521   // undefined is explicitly given, they convert it to a string and
    522   // use that.  We do as SpiderMonkey and KJS.
    523   if (%_ArgumentsLength() === 0) {
    524     return [subject];
    525   }
    526 
    527   var length = subject.length;
    528   if (!IS_REGEXP(separator)) {
    529     separator = TO_STRING_INLINE(separator);
    530     var separator_length = separator.length;
    531 
    532     // If the separator string is empty then return the elements in the subject.
    533     if (separator_length === 0) {
    534       var result = $Array(length);
    535       for (var i = 0; i < length; i++) result[i] = subject[i];
    536       return result;
    537     }
    538 
    539     var result = [];
    540     var start_index = 0;
    541     var index;
    542     while (true) {
    543       if (start_index + separator_length > length ||
    544           (index = %StringIndexOf(subject, separator, start_index)) === -1) {
    545         result.push(SubString(subject, start_index, length));
    546         break;
    547       }
    548       if (result.push(SubString(subject, start_index, index)) === limit) break;
    549       start_index = index + separator_length;
    550     }
    551 
    552     return result;
    553   }
    554 
    555   %_Log('regexp', 'regexp-split,%0S,%1r', [subject, separator]);
    556 
    557   if (length === 0) {
    558     if (splitMatch(separator, subject, 0, 0) != null) return [];
    559     return [subject];
    560   }
    561 
    562   var currentIndex = 0;
    563   var startIndex = 0;
    564   var result = [];
    565 
    566   while (true) {
    567 
    568     if (startIndex === length) {
    569       result[result.length] = subject.slice(currentIndex, length);
    570       return result;
    571     }
    572 
    573     var matchInfo = splitMatch(separator, subject, currentIndex, startIndex);
    574 
    575     if (IS_NULL(matchInfo)) {
    576       result[result.length] = subject.slice(currentIndex, length);
    577       return result;
    578     }
    579 
    580     var endIndex = matchInfo[CAPTURE1];
    581 
    582     // We ignore a zero-length match at the currentIndex.
    583     if (startIndex === endIndex && endIndex === currentIndex) {
    584       startIndex++;
    585       continue;
    586     }
    587 
    588     result[result.length] = SubString(subject, currentIndex, matchInfo[CAPTURE0]);
    589     if (result.length === limit) return result;
    590 
    591     var num_captures = NUMBER_OF_CAPTURES(matchInfo);
    592     for (var i = 2; i < num_captures; i += 2) {
    593       var start = matchInfo[CAPTURE(i)];
    594       var end = matchInfo[CAPTURE(i + 1)];
    595       if (start != -1 && end != -1) {
    596         result[result.length] = SubString(subject, start, end);
    597       } else {
    598         result[result.length] = void 0;
    599       }
    600       if (result.length === limit) return result;
    601     }
    602 
    603     startIndex = currentIndex = endIndex;
    604   }
    605 }
    606 
    607 
    608 // ECMA-262 section 15.5.4.14
    609 // Helper function used by split.  This version returns the matchInfo
    610 // instead of allocating a new array with basically the same information.
    611 function splitMatch(separator, subject, current_index, start_index) {
    612   var matchInfo = DoRegExpExec(separator, subject, start_index);
    613   if (matchInfo == null) return null;
    614   // Section 15.5.4.14 paragraph two says that we do not allow zero length
    615   // matches at the end of the string.
    616   if (matchInfo[CAPTURE0] === subject.length) return null;
    617   return matchInfo;
    618 }
    619 
    620 
    621 // ECMA-262 section 15.5.4.15
    622 function StringSubstring(start, end) {
    623   var s = TO_STRING_INLINE(this);
    624   var s_len = s.length;
    625 
    626   var start_i = TO_INTEGER(start);
    627   if (start_i < 0) {
    628     start_i = 0;
    629   } else if (start_i > s_len) {
    630     start_i = s_len;
    631   }
    632 
    633   var end_i = s_len;
    634   if (!IS_UNDEFINED(end)) {
    635     end_i = TO_INTEGER(end);
    636     if (end_i > s_len) {
    637       end_i = s_len;
    638     } else {
    639       if (end_i < 0) end_i = 0;
    640       if (start_i > end_i) {
    641         var tmp = end_i;
    642         end_i = start_i;
    643         start_i = tmp;
    644       }
    645     }
    646   }
    647 
    648   return SubString(s, start_i, end_i);
    649 }
    650 
    651 
    652 // This is not a part of ECMA-262.
    653 function StringSubstr(start, n) {
    654   var s = TO_STRING_INLINE(this);
    655   var len;
    656 
    657   // Correct n: If not given, set to string length; if explicitly
    658   // set to undefined, zero, or negative, returns empty string.
    659   if (n === void 0) {
    660     len = s.length;
    661   } else {
    662     len = TO_INTEGER(n);
    663     if (len <= 0) return '';
    664   }
    665 
    666   // Correct start: If not given (or undefined), set to zero; otherwise
    667   // convert to integer and handle negative case.
    668   if (start === void 0) {
    669     start = 0;
    670   } else {
    671     start = TO_INTEGER(start);
    672     // If positive, and greater than or equal to the string length,
    673     // return empty string.
    674     if (start >= s.length) return '';
    675     // If negative and absolute value is larger than the string length,
    676     // use zero.
    677     if (start < 0) {
    678       start += s.length;
    679       if (start < 0) start = 0;
    680     }
    681   }
    682 
    683   var end = start + len;
    684   if (end > s.length) end = s.length;
    685 
    686   return SubString(s, start, end);
    687 }
    688 
    689 
    690 // ECMA-262, 15.5.4.16
    691 function StringToLowerCase() {
    692   return %StringToLowerCase(TO_STRING_INLINE(this));
    693 }
    694 
    695 
    696 // ECMA-262, 15.5.4.17
    697 function StringToLocaleLowerCase() {
    698   return %StringToLowerCase(TO_STRING_INLINE(this));
    699 }
    700 
    701 
    702 // ECMA-262, 15.5.4.18
    703 function StringToUpperCase() {
    704   return %StringToUpperCase(TO_STRING_INLINE(this));
    705 }
    706 
    707 
    708 // ECMA-262, 15.5.4.19
    709 function StringToLocaleUpperCase() {
    710   return %StringToUpperCase(TO_STRING_INLINE(this));
    711 }
    712 
    713 // ES5, 15.5.4.20
    714 function StringTrim() {
    715   return %StringTrim(TO_STRING_INLINE(this), true, true);
    716 }
    717 
    718 function StringTrimLeft() {
    719   return %StringTrim(TO_STRING_INLINE(this), true, false);
    720 }
    721 
    722 function StringTrimRight() {
    723   return %StringTrim(TO_STRING_INLINE(this), false, true);
    724 }
    725 
    726 // ECMA-262, section 15.5.3.2
    727 function StringFromCharCode(code) {
    728   var n = %_ArgumentsLength();
    729   if (n == 1) return %CharFromCode(ToNumber(code) & 0xffff)
    730 
    731   // NOTE: This is not super-efficient, but it is necessary because we
    732   // want to avoid converting to numbers from within the virtual
    733   // machine. Maybe we can find another way of doing this?
    734   var codes = new $Array(n);
    735   for (var i = 0; i < n; i++) codes[i] = ToNumber(%_Arguments(i));
    736   return %StringFromCharCodeArray(codes);
    737 }
    738 
    739 
    740 // Helper function for very basic XSS protection.
    741 function HtmlEscape(str) {
    742   return TO_STRING_INLINE(str).replace(/</g, "&lt;")
    743                               .replace(/>/g, "&gt;")
    744                               .replace(/"/g, "&quot;")
    745                               .replace(/'/g, "&#039;");
    746 };
    747 
    748 
    749 // Compatibility support for KJS.
    750 // Tested by mozilla/js/tests/js1_5/Regress/regress-276103.js.
    751 function StringLink(s) {
    752   return "<a href=\"" + HtmlEscape(s) + "\">" + this + "</a>";
    753 }
    754 
    755 
    756 function StringAnchor(name) {
    757   return "<a name=\"" + HtmlEscape(name) + "\">" + this + "</a>";
    758 }
    759 
    760 
    761 function StringFontcolor(color) {
    762   return "<font color=\"" + HtmlEscape(color) + "\">" + this + "</font>";
    763 }
    764 
    765 
    766 function StringFontsize(size) {
    767   return "<font size=\"" + HtmlEscape(size) + "\">" + this + "</font>";
    768 }
    769 
    770 
    771 function StringBig() {
    772   return "<big>" + this + "</big>";
    773 }
    774 
    775 
    776 function StringBlink() {
    777   return "<blink>" + this + "</blink>";
    778 }
    779 
    780 
    781 function StringBold() {
    782   return "<b>" + this + "</b>";
    783 }
    784 
    785 
    786 function StringFixed() {
    787   return "<tt>" + this + "</tt>";
    788 }
    789 
    790 
    791 function StringItalics() {
    792   return "<i>" + this + "</i>";
    793 }
    794 
    795 
    796 function StringSmall() {
    797   return "<small>" + this + "</small>";
    798 }
    799 
    800 
    801 function StringStrike() {
    802   return "<strike>" + this + "</strike>";
    803 }
    804 
    805 
    806 function StringSub() {
    807   return "<sub>" + this + "</sub>";
    808 }
    809 
    810 
    811 function StringSup() {
    812   return "<sup>" + this + "</sup>";
    813 }
    814 
    815 
    816 // ReplaceResultBuilder support.
    817 function ReplaceResultBuilder(str) {
    818   this.elements = new $Array();
    819   this.special_string = str;
    820 }
    821 
    822 
    823 ReplaceResultBuilder.prototype.add = function(str) {
    824   str = TO_STRING_INLINE(str);
    825   if (str.length > 0) {
    826     var elements = this.elements;
    827     elements[elements.length] = str;
    828   }
    829 }
    830 
    831 
    832 ReplaceResultBuilder.prototype.addSpecialSlice = function(start, end) {
    833   var len = end - start;
    834   if (len == 0) return;
    835   var elements = this.elements;
    836   if (start < 0x80000 && len < 0x800) {
    837     elements[elements.length] = (start << 11) + len;
    838   } else {
    839     // 0 < len <= String::kMaxLength and Smi::kMaxValue >= String::kMaxLength,
    840     // so -len is a smi.
    841     elements[elements.length] = -len;
    842     elements[elements.length] = start;
    843   }
    844 }
    845 
    846 
    847 ReplaceResultBuilder.prototype.generate = function() {
    848   var elements = this.elements;
    849   return %StringBuilderConcat(elements, elements.length, this.special_string);
    850 }
    851 
    852 
    853 function StringToJSON(key) {
    854   return CheckJSONPrimitive(this.valueOf());
    855 }
    856 
    857 
    858 // -------------------------------------------------------------------
    859 
    860 function SetupString() {
    861   // Setup the constructor property on the String prototype object.
    862   %SetProperty($String.prototype, "constructor", $String, DONT_ENUM);
    863 
    864 
    865   // Setup the non-enumerable functions on the String object.
    866   InstallFunctions($String, DONT_ENUM, $Array(
    867     "fromCharCode", StringFromCharCode
    868   ));
    869 
    870 
    871   // Setup the non-enumerable functions on the String prototype object.
    872   InstallFunctionsOnHiddenPrototype($String.prototype, DONT_ENUM, $Array(
    873     "valueOf", StringValueOf,
    874     "toString", StringToString,
    875     "charAt", StringCharAt,
    876     "charCodeAt", StringCharCodeAt,
    877     "concat", StringConcat,
    878     "indexOf", StringIndexOf,
    879     "lastIndexOf", StringLastIndexOf,
    880     "localeCompare", StringLocaleCompare,
    881     "match", StringMatch,
    882     "replace", StringReplace,
    883     "search", StringSearch,
    884     "slice", StringSlice,
    885     "split", StringSplit,
    886     "substring", StringSubstring,
    887     "substr", StringSubstr,
    888     "toLowerCase", StringToLowerCase,
    889     "toLocaleLowerCase", StringToLocaleLowerCase,
    890     "toUpperCase", StringToUpperCase,
    891     "toLocaleUpperCase", StringToLocaleUpperCase,
    892     "trim", StringTrim,
    893     "trimLeft", StringTrimLeft,
    894     "trimRight", StringTrimRight,
    895     "link", StringLink,
    896     "anchor", StringAnchor,
    897     "fontcolor", StringFontcolor,
    898     "fontsize", StringFontsize,
    899     "big", StringBig,
    900     "blink", StringBlink,
    901     "bold", StringBold,
    902     "fixed", StringFixed,
    903     "italics", StringItalics,
    904     "small", StringSmall,
    905     "strike", StringStrike,
    906     "sub", StringSub,
    907     "sup", StringSup,
    908     "toJSON", StringToJSON
    909   ));
    910 }
    911 
    912 
    913 SetupString();
    914