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