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