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