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