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