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