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