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
34function 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
45function 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
54function 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
63function 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
77function 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
91function 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
115function 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
134function 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.
167function 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
178function 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.
208var reusableMatchInfo = [2, "", "", -1, -1];
209
210
211// ECMA-262, section 15.5.4.11
212function 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.
328function 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.
410function 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.
425var reusableReplaceArray = new InternalArray(16);
426
427// Helper function for replacing regular expressions with the result of a
428// function application in String.prototype.replace.
429function 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
506function 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
545function 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
567function 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
611function 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
648var ArrayPushBuiltin = $Array.prototype.push;
649
650function 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
713function 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.
748function 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
790function 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
800function 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
810function 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
820function 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
829function 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
837function 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
845function 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
855function 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.
885function 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.
895function StringLink(s) {
896  return "<a href=\"" + HtmlEscape(s) + "\">" + this + "</a>";
897}
898
899
900function StringAnchor(name) {
901  return "<a name=\"" + HtmlEscape(name) + "\">" + this + "</a>";
902}
903
904
905function StringFontcolor(color) {
906  return "<font color=\"" + HtmlEscape(color) + "\">" + this + "</font>";
907}
908
909
910function StringFontsize(size) {
911  return "<font size=\"" + HtmlEscape(size) + "\">" + this + "</font>";
912}
913
914
915function StringBig() {
916  return "<big>" + this + "</big>";
917}
918
919
920function StringBlink() {
921  return "<blink>" + this + "</blink>";
922}
923
924
925function StringBold() {
926  return "<b>" + this + "</b>";
927}
928
929
930function StringFixed() {
931  return "<tt>" + this + "</tt>";
932}
933
934
935function StringItalics() {
936  return "<i>" + this + "</i>";
937}
938
939
940function StringSmall() {
941  return "<small>" + this + "</small>";
942}
943
944
945function StringStrike() {
946  return "<strike>" + this + "</strike>";
947}
948
949
950function StringSub() {
951  return "<sub>" + this + "</sub>";
952}
953
954
955function StringSup() {
956  return "<sup>" + this + "</sup>";
957}
958
959// -------------------------------------------------------------------
960
961function 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
1016SetUpString();
1017