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