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