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