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 ArrayJoin;
     13 var GlobalRegExp = global.RegExp;
     14 var GlobalString = global.String;
     15 var MaxSimple;
     16 var MinSimple;
     17 var matchSymbol = utils.ImportNow("match_symbol");
     18 var replaceSymbol = utils.ImportNow("replace_symbol");
     19 var searchSymbol = utils.ImportNow("search_symbol");
     20 var splitSymbol = utils.ImportNow("split_symbol");
     21 
     22 utils.Import(function(from) {
     23   ArrayJoin = from.ArrayJoin;
     24   MaxSimple = from.MaxSimple;
     25   MinSimple = from.MinSimple;
     26 });
     27 
     28 //-------------------------------------------------------------------
     29 
     30 // ECMA-262, section 15.5.4.6
     31 function StringConcat(other /* and more */) {  // length == 1
     32   "use strict";
     33   CHECK_OBJECT_COERCIBLE(this, "String.prototype.concat");
     34   var s = TO_STRING(this);
     35   var len = arguments.length;
     36   for (var i = 0; i < len; ++i) {
     37     s = s + TO_STRING(arguments[i]);
     38   }
     39   return s;
     40 }
     41 
     42 
     43 // ES6 21.1.3.11.
     44 function StringMatchJS(pattern) {
     45   CHECK_OBJECT_COERCIBLE(this, "String.prototype.match");
     46 
     47   if (!IS_NULL_OR_UNDEFINED(pattern)) {
     48     var matcher = pattern[matchSymbol];
     49     if (!IS_UNDEFINED(matcher)) {
     50       return %_Call(matcher, pattern, this);
     51     }
     52   }
     53 
     54   var subject = TO_STRING(this);
     55 
     56   // Equivalent to RegExpCreate (ES#sec-regexpcreate)
     57   var regexp = %RegExpCreate(pattern);
     58   return regexp[matchSymbol](subject);
     59 }
     60 
     61 // ES#sec-getsubstitution
     62 // GetSubstitution(matched, str, position, captures, replacement)
     63 // Expand the $-expressions in the string and return a new string with
     64 // the result.
     65 function GetSubstitution(matched, string, position, captures, replacement) {
     66   var matchLength = matched.length;
     67   var stringLength = string.length;
     68   var capturesLength = captures.length;
     69   var tailPos = position + matchLength;
     70   var result = "";
     71   var pos, expansion, peek, next, scaledIndex, advance, newScaledIndex;
     72 
     73   var next = %StringIndexOf(replacement, '$', 0);
     74   if (next < 0) {
     75     result += replacement;
     76     return result;
     77   }
     78 
     79   if (next > 0) result += %_SubString(replacement, 0, next);
     80 
     81   while (true) {
     82     expansion = '$';
     83     pos = next + 1;
     84     if (pos < replacement.length) {
     85       peek = %_StringCharCodeAt(replacement, pos);
     86       if (peek == 36) {         // $$
     87         ++pos;
     88         result += '$';
     89       } else if (peek == 38) {  // $& - match
     90         ++pos;
     91         result += matched;
     92       } else if (peek == 96) {  // $` - prefix
     93         ++pos;
     94         result += %_SubString(string, 0, position);
     95       } else if (peek == 39) {  // $' - suffix
     96         ++pos;
     97         result += %_SubString(string, tailPos, stringLength);
     98       } else if (peek >= 48 && peek <= 57) {
     99         // Valid indices are $1 .. $9, $01 .. $09 and $10 .. $99
    100         scaledIndex = (peek - 48);
    101         advance = 1;
    102         if (pos + 1 < replacement.length) {
    103           next = %_StringCharCodeAt(replacement, pos + 1);
    104           if (next >= 48 && next <= 57) {
    105             newScaledIndex = scaledIndex * 10 + ((next - 48));
    106             if (newScaledIndex < capturesLength) {
    107               scaledIndex = newScaledIndex;
    108               advance = 2;
    109             }
    110           }
    111         }
    112         if (scaledIndex != 0 && scaledIndex < capturesLength) {
    113           var capture = captures.at(scaledIndex);
    114           if (!IS_UNDEFINED(capture)) result += capture;
    115           pos += advance;
    116         } else {
    117           result += '$';
    118         }
    119       } else {
    120         result += '$';
    121       }
    122     } else {
    123       result += '$';
    124     }
    125 
    126     // Go the the next $ in the replacement.
    127     next = %StringIndexOf(replacement, '$', pos);
    128 
    129     // Return if there are no more $ characters in the replacement. If we
    130     // haven't reached the end, we need to append the suffix.
    131     if (next < 0) {
    132       if (pos < replacement.length) {
    133         result += %_SubString(replacement, pos, replacement.length);
    134       }
    135       return result;
    136     }
    137 
    138     // Append substring between the previous and the next $ character.
    139     if (next > pos) {
    140       result += %_SubString(replacement, pos, next);
    141     }
    142   }
    143   return result;
    144 }
    145 
    146 // ES6, section 21.1.3.14
    147 function StringReplace(search, replace) {
    148   CHECK_OBJECT_COERCIBLE(this, "String.prototype.replace");
    149 
    150   // Decision tree for dispatch
    151   // .. regexp search (in src/js/regexp.js, RegExpReplace)
    152   // .... string replace
    153   // ...... non-global search
    154   // ........ empty string replace
    155   // ........ non-empty string replace (with $-expansion)
    156   // ...... global search
    157   // ........ no need to circumvent last match info override
    158   // ........ need to circument last match info override
    159   // .... function replace
    160   // ...... global search
    161   // ...... non-global search
    162   // .. string search
    163   // .... special case that replaces with one single character
    164   // ...... function replace
    165   // ...... string replace (with $-expansion)
    166 
    167   if (!IS_NULL_OR_UNDEFINED(search)) {
    168     var replacer = search[replaceSymbol];
    169     if (!IS_UNDEFINED(replacer)) {
    170       return %_Call(replacer, search, this, replace);
    171     }
    172   }
    173 
    174   var subject = TO_STRING(this);
    175 
    176   search = TO_STRING(search);
    177 
    178   if (search.length == 1 &&
    179       subject.length > 0xFF &&
    180       IS_STRING(replace) &&
    181       %StringIndexOf(replace, '$', 0) < 0) {
    182     // Searching by traversing a cons string tree and replace with cons of
    183     // slices works only when the replaced string is a single character, being
    184     // replaced by a simple string and only pays off for long strings.
    185     return %StringReplaceOneCharWithString(subject, search, replace);
    186   }
    187   var start = %StringIndexOf(subject, search, 0);
    188   if (start < 0) return subject;
    189   var end = start + search.length;
    190 
    191   var result = %_SubString(subject, 0, start);
    192 
    193   // Compute the string to replace with.
    194   if (IS_CALLABLE(replace)) {
    195     result += replace(search, start, subject);
    196   } else {
    197     // In this case, we don't have any capture groups and can get away with
    198     // faking the captures object by simply setting its length to 1.
    199     const captures = { length: 1 };
    200     const matched = %_SubString(subject, start, end);
    201     result += GetSubstitution(matched, subject, start, captures,
    202                               TO_STRING(replace));
    203   }
    204 
    205   return result + %_SubString(subject, end, subject.length);
    206 }
    207 
    208 
    209 // ES6 21.1.3.15.
    210 function StringSearch(pattern) {
    211   CHECK_OBJECT_COERCIBLE(this, "String.prototype.search");
    212 
    213   if (!IS_NULL_OR_UNDEFINED(pattern)) {
    214     var searcher = pattern[searchSymbol];
    215     if (!IS_UNDEFINED(searcher)) {
    216       return %_Call(searcher, pattern, this);
    217     }
    218   }
    219 
    220   var subject = TO_STRING(this);
    221 
    222   // Equivalent to RegExpCreate (ES#sec-regexpcreate)
    223   var regexp = %RegExpCreate(pattern);
    224   return %_Call(regexp[searchSymbol], regexp, subject);
    225 }
    226 
    227 
    228 // ECMA-262 section 15.5.4.13
    229 function StringSlice(start, end) {
    230   CHECK_OBJECT_COERCIBLE(this, "String.prototype.slice");
    231 
    232   var s = TO_STRING(this);
    233   var s_len = s.length;
    234   var start_i = TO_INTEGER(start);
    235   var end_i = s_len;
    236   if (!IS_UNDEFINED(end)) {
    237     end_i = TO_INTEGER(end);
    238   }
    239 
    240   if (start_i < 0) {
    241     start_i += s_len;
    242     if (start_i < 0) {
    243       start_i = 0;
    244     }
    245   } else {
    246     if (start_i > s_len) {
    247       return '';
    248     }
    249   }
    250 
    251   if (end_i < 0) {
    252     end_i += s_len;
    253     if (end_i < 0) {
    254       return '';
    255     }
    256   } else {
    257     if (end_i > s_len) {
    258       end_i = s_len;
    259     }
    260   }
    261 
    262   if (end_i <= start_i) {
    263     return '';
    264   }
    265 
    266   return %_SubString(s, start_i, end_i);
    267 }
    268 
    269 
    270 // ES6 21.1.3.17.
    271 function StringSplitJS(separator, limit) {
    272   CHECK_OBJECT_COERCIBLE(this, "String.prototype.split");
    273 
    274   if (!IS_NULL_OR_UNDEFINED(separator)) {
    275     var splitter = separator[splitSymbol];
    276     if (!IS_UNDEFINED(splitter)) {
    277       return %_Call(splitter, separator, this, limit);
    278     }
    279   }
    280 
    281   var subject = TO_STRING(this);
    282   limit = (IS_UNDEFINED(limit)) ? kMaxUint32 : TO_UINT32(limit);
    283 
    284   var length = subject.length;
    285   var separator_string = TO_STRING(separator);
    286 
    287   if (limit === 0) return [];
    288 
    289   // ECMA-262 says that if separator is undefined, the result should
    290   // be an array of size 1 containing the entire string.
    291   if (IS_UNDEFINED(separator)) return [subject];
    292 
    293   var separator_length = separator_string.length;
    294 
    295   // If the separator string is empty then return the elements in the subject.
    296   if (separator_length === 0) return %StringToArray(subject, limit);
    297 
    298   return %StringSplit(subject, separator_string, limit);
    299 }
    300 
    301 
    302 // ECMA-262, 15.5.4.16
    303 function StringToLowerCaseJS() {
    304   CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLowerCase");
    305 
    306   return %StringToLowerCase(TO_STRING(this));
    307 }
    308 
    309 
    310 // ECMA-262, 15.5.4.17
    311 function StringToLocaleLowerCase() {
    312   CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLocaleLowerCase");
    313 
    314   return %StringToLowerCase(TO_STRING(this));
    315 }
    316 
    317 
    318 // ECMA-262, 15.5.4.18
    319 function StringToUpperCaseJS() {
    320   CHECK_OBJECT_COERCIBLE(this, "String.prototype.toUpperCase");
    321 
    322   return %StringToUpperCase(TO_STRING(this));
    323 }
    324 
    325 
    326 // ECMA-262, 15.5.4.19
    327 function StringToLocaleUpperCase() {
    328   CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLocaleUpperCase");
    329 
    330   return %StringToUpperCase(TO_STRING(this));
    331 }
    332 
    333 
    334 // ES6 draft, revision 26 (2014-07-18), section B.2.3.2.1
    335 function HtmlEscape(str) {
    336   return %_Call(StringReplace, TO_STRING(str), /"/g, "&quot;");
    337 }
    338 
    339 
    340 // ES6 draft, revision 26 (2014-07-18), section B.2.3.2
    341 function StringAnchor(name) {
    342   CHECK_OBJECT_COERCIBLE(this, "String.prototype.anchor");
    343   return "<a name=\"" + HtmlEscape(name) + "\">" + TO_STRING(this) +
    344          "</a>";
    345 }
    346 
    347 
    348 // ES6 draft, revision 26 (2014-07-18), section B.2.3.3
    349 function StringBig() {
    350   CHECK_OBJECT_COERCIBLE(this, "String.prototype.big");
    351   return "<big>" + TO_STRING(this) + "</big>";
    352 }
    353 
    354 
    355 // ES6 draft, revision 26 (2014-07-18), section B.2.3.4
    356 function StringBlink() {
    357   CHECK_OBJECT_COERCIBLE(this, "String.prototype.blink");
    358   return "<blink>" + TO_STRING(this) + "</blink>";
    359 }
    360 
    361 
    362 // ES6 draft, revision 26 (2014-07-18), section B.2.3.5
    363 function StringBold() {
    364   CHECK_OBJECT_COERCIBLE(this, "String.prototype.bold");
    365   return "<b>" + TO_STRING(this) + "</b>";
    366 }
    367 
    368 
    369 // ES6 draft, revision 26 (2014-07-18), section B.2.3.6
    370 function StringFixed() {
    371   CHECK_OBJECT_COERCIBLE(this, "String.prototype.fixed");
    372   return "<tt>" + TO_STRING(this) + "</tt>";
    373 }
    374 
    375 
    376 // ES6 draft, revision 26 (2014-07-18), section B.2.3.7
    377 function StringFontcolor(color) {
    378   CHECK_OBJECT_COERCIBLE(this, "String.prototype.fontcolor");
    379   return "<font color=\"" + HtmlEscape(color) + "\">" + TO_STRING(this) +
    380          "</font>";
    381 }
    382 
    383 
    384 // ES6 draft, revision 26 (2014-07-18), section B.2.3.8
    385 function StringFontsize(size) {
    386   CHECK_OBJECT_COERCIBLE(this, "String.prototype.fontsize");
    387   return "<font size=\"" + HtmlEscape(size) + "\">" + TO_STRING(this) +
    388          "</font>";
    389 }
    390 
    391 
    392 // ES6 draft, revision 26 (2014-07-18), section B.2.3.9
    393 function StringItalics() {
    394   CHECK_OBJECT_COERCIBLE(this, "String.prototype.italics");
    395   return "<i>" + TO_STRING(this) + "</i>";
    396 }
    397 
    398 
    399 // ES6 draft, revision 26 (2014-07-18), section B.2.3.10
    400 function StringLink(s) {
    401   CHECK_OBJECT_COERCIBLE(this, "String.prototype.link");
    402   return "<a href=\"" + HtmlEscape(s) + "\">" + TO_STRING(this) + "</a>";
    403 }
    404 
    405 
    406 // ES6 draft, revision 26 (2014-07-18), section B.2.3.11
    407 function StringSmall() {
    408   CHECK_OBJECT_COERCIBLE(this, "String.prototype.small");
    409   return "<small>" + TO_STRING(this) + "</small>";
    410 }
    411 
    412 
    413 // ES6 draft, revision 26 (2014-07-18), section B.2.3.12
    414 function StringStrike() {
    415   CHECK_OBJECT_COERCIBLE(this, "String.prototype.strike");
    416   return "<strike>" + TO_STRING(this) + "</strike>";
    417 }
    418 
    419 
    420 // ES6 draft, revision 26 (2014-07-18), section B.2.3.13
    421 function StringSub() {
    422   CHECK_OBJECT_COERCIBLE(this, "String.prototype.sub");
    423   return "<sub>" + TO_STRING(this) + "</sub>";
    424 }
    425 
    426 
    427 // ES6 draft, revision 26 (2014-07-18), section B.2.3.14
    428 function StringSup() {
    429   CHECK_OBJECT_COERCIBLE(this, "String.prototype.sup");
    430   return "<sup>" + TO_STRING(this) + "</sup>";
    431 }
    432 
    433 // ES6, section 21.1.3.13
    434 function StringRepeat(count) {
    435   CHECK_OBJECT_COERCIBLE(this, "String.prototype.repeat");
    436 
    437   var s = TO_STRING(this);
    438   var n = TO_INTEGER(count);
    439 
    440   if (n < 0 || n === INFINITY) throw %make_range_error(kInvalidCountValue);
    441 
    442   // Early return to allow an arbitrarily-large repeat of the empty string.
    443   if (s.length === 0) return "";
    444 
    445   // The maximum string length is stored in a smi, so a longer repeat
    446   // must result in a range error.
    447   if (n > %_MaxSmi()) throw %make_range_error(kInvalidCountValue);
    448 
    449   var r = "";
    450   while (true) {
    451     if (n & 1) r += s;
    452     n >>= 1;
    453     if (n === 0) return r;
    454     s += s;
    455   }
    456 }
    457 
    458 
    459 // ES6 Draft 05-22-2014, section 21.1.3.3
    460 function StringCodePointAt(pos) {
    461   CHECK_OBJECT_COERCIBLE(this, "String.prototype.codePointAt");
    462 
    463   var string = TO_STRING(this);
    464   var size = string.length;
    465   pos = TO_INTEGER(pos);
    466   if (pos < 0 || pos >= size) {
    467     return UNDEFINED;
    468   }
    469   var first = %_StringCharCodeAt(string, pos);
    470   if (first < 0xD800 || first > 0xDBFF || pos + 1 == size) {
    471     return first;
    472   }
    473   var second = %_StringCharCodeAt(string, pos + 1);
    474   if (second < 0xDC00 || second > 0xDFFF) {
    475     return first;
    476   }
    477   return (first - 0xD800) * 0x400 + second + 0x2400;
    478 }
    479 
    480 
    481 // -------------------------------------------------------------------
    482 // String methods related to templates
    483 
    484 // ES6 Draft 03-17-2015, section 21.1.2.4
    485 function StringRaw(callSite) {
    486   "use strict";
    487   var numberOfSubstitutions = arguments.length;
    488   var cooked = TO_OBJECT(callSite);
    489   var raw = TO_OBJECT(cooked.raw);
    490   var literalSegments = TO_LENGTH(raw.length);
    491   if (literalSegments <= 0) return "";
    492 
    493   var result = TO_STRING(raw[0]);
    494 
    495   for (var i = 1; i < literalSegments; ++i) {
    496     if (i < numberOfSubstitutions) {
    497       result += TO_STRING(arguments[i]);
    498     }
    499     result += TO_STRING(raw[i]);
    500   }
    501 
    502   return result;
    503 }
    504 
    505 // -------------------------------------------------------------------
    506 
    507 // Set up the non-enumerable functions on the String object.
    508 utils.InstallFunctions(GlobalString, DONT_ENUM, [
    509   "raw", StringRaw
    510 ]);
    511 
    512 // Set up the non-enumerable functions on the String prototype object.
    513 utils.InstallFunctions(GlobalString.prototype, DONT_ENUM, [
    514   "codePointAt", StringCodePointAt,
    515   "concat", StringConcat,
    516   "match", StringMatchJS,
    517   "repeat", StringRepeat,
    518   "replace", StringReplace,
    519   "search", StringSearch,
    520   "slice", StringSlice,
    521   "split", StringSplitJS,
    522   "toLowerCase", StringToLowerCaseJS,
    523   "toLocaleLowerCase", StringToLocaleLowerCase,
    524   "toUpperCase", StringToUpperCaseJS,
    525   "toLocaleUpperCase", StringToLocaleUpperCase,
    526 
    527   "link", StringLink,
    528   "anchor", StringAnchor,
    529   "fontcolor", StringFontcolor,
    530   "fontsize", StringFontsize,
    531   "big", StringBig,
    532   "blink", StringBlink,
    533   "bold", StringBold,
    534   "fixed", StringFixed,
    535   "italics", StringItalics,
    536   "small", StringSmall,
    537   "strike", StringStrike,
    538   "sub", StringSub,
    539   "sup", StringSup
    540 ]);
    541 
    542 // -------------------------------------------------------------------
    543 // Exports
    544 
    545 utils.Export(function(to) {
    546   to.StringMatch = StringMatchJS;
    547   to.StringReplace = StringReplace;
    548   to.StringSlice = StringSlice;
    549   to.StringSplit = StringSplitJS;
    550 });
    551 
    552 })
    553