Upstream version 5.34.92.0
[platform/framework/web/crosswalk.git] / src / v8 / src / string.js
1 // Copyright 2012 the V8 project authors. All rights reserved.
2 // Redistribution and use in source and binary forms, with or without
3 // modification, are permitted provided that the following conditions are
4 // met:
5 //
6 //     * Redistributions of source code must retain the above copyright
7 //       notice, this list of conditions and the following disclaimer.
8 //     * Redistributions in binary form must reproduce the above
9 //       copyright notice, this list of conditions and the following
10 //       disclaimer in the documentation and/or other materials provided
11 //       with the distribution.
12 //     * Neither the name of Google Inc. nor the names of its
13 //       contributors may be used to endorse or promote products derived
14 //       from this software without specific prior written permission.
15 //
16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
28 // This file relies on the fact that the following declaration has been made
29 // in runtime.js:
30 // var $String = global.String;
31
32 // -------------------------------------------------------------------
33
34 function StringConstructor(x) {
35   var value = %_ArgumentsLength() == 0 ? '' : TO_STRING_INLINE(x);
36   if (%_IsConstructCall()) {
37     %_SetValueOf(this, value);
38   } else {
39     return value;
40   }
41 }
42
43
44 // ECMA-262 section 15.5.4.2
45 function StringToString() {
46   if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) {
47     throw new $TypeError('String.prototype.toString is not generic');
48   }
49   return %_ValueOf(this);
50 }
51
52
53 // ECMA-262 section 15.5.4.3
54 function StringValueOf() {
55   if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) {
56     throw new $TypeError('String.prototype.valueOf is not generic');
57   }
58   return %_ValueOf(this);
59 }
60
61
62 // ECMA-262, section 15.5.4.4
63 function StringCharAt(pos) {
64   CHECK_OBJECT_COERCIBLE(this, "String.prototype.charAt");
65
66   var result = %_StringCharAt(this, pos);
67   if (%_IsSmi(result)) {
68     result = %_StringCharAt(TO_STRING_INLINE(this), TO_INTEGER(pos));
69   }
70   return result;
71 }
72
73
74 // ECMA-262 section 15.5.4.5
75 function StringCharCodeAt(pos) {
76   CHECK_OBJECT_COERCIBLE(this, "String.prototype.charCodeAt");
77
78   var result = %_StringCharCodeAt(this, pos);
79   if (!%_IsSmi(result)) {
80     result = %_StringCharCodeAt(TO_STRING_INLINE(this), TO_INTEGER(pos));
81   }
82   return result;
83 }
84
85
86 // ECMA-262, section 15.5.4.6
87 function StringConcat() {
88   CHECK_OBJECT_COERCIBLE(this, "String.prototype.concat");
89
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 InternalArray(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
109 function StringIndexOf(pattern /* position */) {  // length == 1
110   CHECK_OBJECT_COERCIBLE(this, "String.prototype.indexOf");
111
112   var subject = TO_STRING_INLINE(this);
113   pattern = TO_STRING_INLINE(pattern);
114   var index = 0;
115   if (%_ArgumentsLength() > 1) {
116     index = %_Arguments(1);  // position
117     index = TO_INTEGER(index);
118     if (index < 0) index = 0;
119     if (index > subject.length) index = subject.length;
120   }
121   return %StringIndexOf(subject, pattern, index);
122 }
123
124
125 // ECMA-262 section 15.5.4.8
126 function StringLastIndexOf(pat /* position */) {  // length == 1
127   CHECK_OBJECT_COERCIBLE(this, "String.prototype.lastIndexOf");
128
129   var sub = TO_STRING_INLINE(this);
130   var subLength = sub.length;
131   var pat = TO_STRING_INLINE(pat);
132   var patLength = pat.length;
133   var index = subLength - patLength;
134   if (%_ArgumentsLength() > 1) {
135     var position = ToNumber(%_Arguments(1));
136     if (!NUMBER_IS_NAN(position)) {
137       position = TO_INTEGER(position);
138       if (position < 0) {
139         position = 0;
140       }
141       if (position + patLength < subLength) {
142         index = position;
143       }
144     }
145   }
146   if (index < 0) {
147     return -1;
148   }
149   return %StringLastIndexOf(sub, pat, index);
150 }
151
152
153 // ECMA-262 section 15.5.4.9
154 //
155 // This function is implementation specific.  For now, we do not
156 // do anything locale specific.
157 function StringLocaleCompare(other) {
158   CHECK_OBJECT_COERCIBLE(this, "String.prototype.localeCompare");
159
160   return %StringLocaleCompare(TO_STRING_INLINE(this),
161                               TO_STRING_INLINE(other));
162 }
163
164
165 // ECMA-262 section 15.5.4.10
166 function StringMatch(regexp) {
167   CHECK_OBJECT_COERCIBLE(this, "String.prototype.match");
168
169   var subject = TO_STRING_INLINE(this);
170   if (IS_REGEXP(regexp)) {
171     // Emulate RegExp.prototype.exec's side effect in step 5, even though
172     // value is discarded.
173     var lastIndex = regexp.lastIndex;
174     TO_INTEGER_FOR_SIDE_EFFECT(lastIndex);
175     if (!regexp.global) return RegExpExecNoTests(regexp, subject, 0);
176     %_Log('regexp', 'regexp-match,%0S,%1r', [subject, regexp]);
177     // lastMatchInfo is defined in regexp.js.
178     var result = %StringMatch(subject, regexp, lastMatchInfo);
179     if (result !== null) lastMatchInfoOverride = null;
180     regexp.lastIndex = 0;
181     return result;
182   }
183   // Non-regexp argument.
184   regexp = new $RegExp(regexp);
185   return RegExpExecNoTests(regexp, subject, 0);
186 }
187
188
189 // This has the same size as the lastMatchInfo array, and can be used for
190 // functions that expect that structure to be returned.  It is used when the
191 // needle is a string rather than a regexp.  In this case we can't update
192 // lastMatchArray without erroneously affecting the properties on the global
193 // RegExp object.
194 var reusableMatchInfo = [2, "", "", -1, -1];
195
196
197 // ECMA-262, section 15.5.4.11
198 function StringReplace(search, replace) {
199   CHECK_OBJECT_COERCIBLE(this, "String.prototype.replace");
200
201   var subject = TO_STRING_INLINE(this);
202
203   // Decision tree for dispatch
204   // .. regexp search
205   // .... string replace
206   // ...... non-global search
207   // ........ empty string replace
208   // ........ non-empty string replace (with $-expansion)
209   // ...... global search
210   // ........ no need to circumvent last match info override
211   // ........ need to circument last match info override
212   // .... function replace
213   // ...... global search
214   // ...... non-global search
215   // .. string search
216   // .... special case that replaces with one single character
217   // ...... function replace
218   // ...... string replace (with $-expansion)
219
220   if (IS_REGEXP(search)) {
221     // Emulate RegExp.prototype.exec's side effect in step 5, even if
222     // value is discarded.
223     var lastIndex = search.lastIndex;
224     TO_INTEGER_FOR_SIDE_EFFECT(lastIndex);
225     %_Log('regexp', 'regexp-replace,%0r,%1S', [search, subject]);
226
227     if (!IS_SPEC_FUNCTION(replace)) {
228       replace = TO_STRING_INLINE(replace);
229
230       if (!search.global) {
231         // Non-global regexp search, string replace.
232         var match = DoRegExpExec(search, subject, 0);
233         if (match == null) {
234           search.lastIndex = 0
235           return subject;
236         }
237         if (replace.length == 0) {
238           return %_SubString(subject, 0, match[CAPTURE0]) +
239                  %_SubString(subject, match[CAPTURE1], subject.length)
240         }
241         return ExpandReplacement(replace, subject, lastMatchInfo,
242                                  %_SubString(subject, 0, match[CAPTURE0])) +
243                %_SubString(subject, match[CAPTURE1], subject.length);
244       }
245
246       // Global regexp search, string replace.
247       search.lastIndex = 0;
248       if (lastMatchInfoOverride == null) {
249         return %StringReplaceGlobalRegExpWithString(
250             subject, search, replace, lastMatchInfo);
251       } else {
252         // We use this hack to detect whether StringReplaceRegExpWithString
253         // found at least one hit. In that case we need to remove any
254         // override.
255         var saved_subject = lastMatchInfo[LAST_SUBJECT_INDEX];
256         lastMatchInfo[LAST_SUBJECT_INDEX] = 0;
257         var answer = %StringReplaceGlobalRegExpWithString(
258             subject, search, replace, lastMatchInfo);
259         if (%_IsSmi(lastMatchInfo[LAST_SUBJECT_INDEX])) {
260           lastMatchInfo[LAST_SUBJECT_INDEX] = saved_subject;
261         } else {
262           lastMatchInfoOverride = null;
263         }
264         return answer;
265       }
266     }
267
268     if (search.global) {
269       // Global regexp search, function replace.
270       return StringReplaceGlobalRegExpWithFunction(subject, search, replace);
271     }
272     // Non-global regexp search, function replace.
273     return StringReplaceNonGlobalRegExpWithFunction(subject, search, replace);
274   }
275
276   search = TO_STRING_INLINE(search);
277
278   if (search.length == 1 &&
279       subject.length > 0xFF &&
280       IS_STRING(replace) &&
281       %StringIndexOf(replace, '$', 0) < 0) {
282     // Searching by traversing a cons string tree and replace with cons of
283     // slices works only when the replaced string is a single character, being
284     // replaced by a simple string and only pays off for long strings.
285     return %StringReplaceOneCharWithString(subject, search, replace);
286   }
287   var start = %StringIndexOf(subject, search, 0);
288   if (start < 0) return subject;
289   var end = start + search.length;
290
291   var result = %_SubString(subject, 0, start);
292
293   // Compute the string to replace with.
294   if (IS_SPEC_FUNCTION(replace)) {
295     var receiver = %GetDefaultReceiver(replace);
296     result += %_CallFunction(receiver, search, start, subject, replace);
297   } else {
298     reusableMatchInfo[CAPTURE0] = start;
299     reusableMatchInfo[CAPTURE1] = end;
300     result = ExpandReplacement(TO_STRING_INLINE(replace),
301                                subject,
302                                reusableMatchInfo,
303                                result);
304   }
305
306   return result + %_SubString(subject, end, subject.length);
307 }
308
309
310 // Expand the $-expressions in the string and return a new string with
311 // the result.
312 function ExpandReplacement(string, subject, matchInfo, result) {
313   var length = string.length;
314   var next = %StringIndexOf(string, '$', 0);
315   if (next < 0) {
316     if (length > 0) result += string;
317     return result;
318   }
319
320   if (next > 0) result += %_SubString(string, 0, next);
321
322   while (true) {
323     var expansion = '$';
324     var position = next + 1;
325     if (position < length) {
326       var peek = %_StringCharCodeAt(string, position);
327       if (peek == 36) {         // $$
328         ++position;
329         result += '$';
330       } else if (peek == 38) {  // $& - match
331         ++position;
332         result +=
333           %_SubString(subject, matchInfo[CAPTURE0], matchInfo[CAPTURE1]);
334       } else if (peek == 96) {  // $` - prefix
335         ++position;
336         result += %_SubString(subject, 0, matchInfo[CAPTURE0]);
337       } else if (peek == 39) {  // $' - suffix
338         ++position;
339         result += %_SubString(subject, matchInfo[CAPTURE1], subject.length);
340       } else if (peek >= 48 && peek <= 57) {
341         // Valid indices are $1 .. $9, $01 .. $09 and $10 .. $99
342         var scaled_index = (peek - 48) << 1;
343         var advance = 1;
344         var number_of_captures = NUMBER_OF_CAPTURES(matchInfo);
345         if (position + 1 < string.length) {
346           var next = %_StringCharCodeAt(string, position + 1);
347           if (next >= 48 && next <= 57) {
348             var new_scaled_index = scaled_index * 10 + ((next - 48) << 1);
349             if (new_scaled_index < number_of_captures) {
350               scaled_index = new_scaled_index;
351               advance = 2;
352             }
353           }
354         }
355         if (scaled_index != 0 && scaled_index < number_of_captures) {
356           var start = matchInfo[CAPTURE(scaled_index)];
357           if (start >= 0) {
358             result +=
359               %_SubString(subject, start, matchInfo[CAPTURE(scaled_index + 1)]);
360           }
361           position += advance;
362         } else {
363           result += '$';
364         }
365       } else {
366         result += '$';
367       }
368     } else {
369       result += '$';
370     }
371
372     // Go the the next $ in the string.
373     next = %StringIndexOf(string, '$', position);
374
375     // Return if there are no more $ characters in the string. If we
376     // haven't reached the end, we need to append the suffix.
377     if (next < 0) {
378       if (position < length) {
379         result += %_SubString(string, position, length);
380       }
381       return result;
382     }
383
384     // Append substring between the previous and the next $ character.
385     if (next > position) {
386       result += %_SubString(string, position, next);
387     }
388   }
389   return result;
390 }
391
392
393 // Compute the string of a given regular expression capture.
394 function CaptureString(string, lastCaptureInfo, index) {
395   // Scale the index.
396   var scaled = index << 1;
397   // Compute start and end.
398   var start = lastCaptureInfo[CAPTURE(scaled)];
399   // If start isn't valid, return undefined.
400   if (start < 0) return;
401   var end = lastCaptureInfo[CAPTURE(scaled + 1)];
402   return %_SubString(string, start, end);
403 }
404
405
406 // TODO(lrn): This array will survive indefinitely if replace is never
407 // called again. However, it will be empty, since the contents are cleared
408 // in the finally block.
409 var reusableReplaceArray = new InternalArray(16);
410
411 // Helper function for replacing regular expressions with the result of a
412 // function application in String.prototype.replace.
413 function StringReplaceGlobalRegExpWithFunction(subject, regexp, replace) {
414   var resultArray = reusableReplaceArray;
415   if (resultArray) {
416     reusableReplaceArray = null;
417   } else {
418     // Inside a nested replace (replace called from the replacement function
419     // of another replace) or we have failed to set the reusable array
420     // back due to an exception in a replacement function. Create a new
421     // array to use in the future, or until the original is written back.
422     resultArray = new InternalArray(16);
423   }
424   var res = %RegExpExecMultiple(regexp,
425                                 subject,
426                                 lastMatchInfo,
427                                 resultArray);
428   regexp.lastIndex = 0;
429   if (IS_NULL(res)) {
430     // No matches at all.
431     reusableReplaceArray = resultArray;
432     return subject;
433   }
434   var len = res.length;
435   if (NUMBER_OF_CAPTURES(lastMatchInfo) == 2) {
436     // If the number of captures is two then there are no explicit captures in
437     // the regexp, just the implicit capture that captures the whole match.  In
438     // this case we can simplify quite a bit and end up with something faster.
439     // The builder will consist of some integers that indicate slices of the
440     // input string and some replacements that were returned from the replace
441     // function.
442     var match_start = 0;
443     var override = new InternalPackedArray(null, 0, subject);
444     var receiver = %GetDefaultReceiver(replace);
445     for (var i = 0; i < len; i++) {
446       var elem = res[i];
447       if (%_IsSmi(elem)) {
448         // Integers represent slices of the original string.  Use these to
449         // get the offsets we need for the override array (so things like
450         // RegExp.leftContext work during the callback function.
451         if (elem > 0) {
452           match_start = (elem >> 11) + (elem & 0x7ff);
453         } else {
454           match_start = res[++i] - elem;
455         }
456       } else {
457         override[0] = elem;
458         override[1] = match_start;
459         lastMatchInfoOverride = override;
460         var func_result =
461             %_CallFunction(receiver, elem, match_start, subject, replace);
462         // Overwrite the i'th element in the results with the string we got
463         // back from the callback function.
464         res[i] = TO_STRING_INLINE(func_result);
465         match_start += elem.length;
466       }
467     }
468   } else {
469     var receiver = %GetDefaultReceiver(replace);
470     for (var i = 0; i < len; i++) {
471       var elem = res[i];
472       if (!%_IsSmi(elem)) {
473         // elem must be an Array.
474         // Use the apply argument as backing for global RegExp properties.
475         lastMatchInfoOverride = elem;
476         var func_result = %Apply(replace, receiver, elem, 0, elem.length);
477         // Overwrite the i'th element in the results with the string we got
478         // back from the callback function.
479         res[i] = TO_STRING_INLINE(func_result);
480       }
481     }
482   }
483   var result = %StringBuilderConcat(res, res.length, subject);
484   resultArray.length = 0;
485   reusableReplaceArray = resultArray;
486   return result;
487 }
488
489
490 function StringReplaceNonGlobalRegExpWithFunction(subject, regexp, replace) {
491   var matchInfo = DoRegExpExec(regexp, subject, 0);
492   if (IS_NULL(matchInfo)) {
493     regexp.lastIndex = 0;
494     return subject;
495   }
496   var index = matchInfo[CAPTURE0];
497   var result = %_SubString(subject, 0, index);
498   var endOfMatch = matchInfo[CAPTURE1];
499   // Compute the parameter list consisting of the match, captures, index,
500   // and subject for the replace function invocation.
501   // The number of captures plus one for the match.
502   var m = NUMBER_OF_CAPTURES(matchInfo) >> 1;
503   var replacement;
504   var receiver = %GetDefaultReceiver(replace);
505   if (m == 1) {
506     // No captures, only the match, which is always valid.
507     var s = %_SubString(subject, index, endOfMatch);
508     // Don't call directly to avoid exposing the built-in global object.
509     replacement = %_CallFunction(receiver, s, index, subject, replace);
510   } else {
511     var parameters = new InternalArray(m + 2);
512     for (var j = 0; j < m; j++) {
513       parameters[j] = CaptureString(subject, matchInfo, j);
514     }
515     parameters[j] = index;
516     parameters[j + 1] = subject;
517
518     replacement = %Apply(replace, receiver, parameters, 0, j + 2);
519   }
520
521   result += replacement;  // The add method converts to string if necessary.
522   // Can't use matchInfo any more from here, since the function could
523   // overwrite it.
524   return result + %_SubString(subject, endOfMatch, subject.length);
525 }
526
527
528 // ECMA-262 section 15.5.4.12
529 function StringSearch(re) {
530   CHECK_OBJECT_COERCIBLE(this, "String.prototype.search");
531
532   var regexp;
533   if (IS_STRING(re)) {
534     regexp = %_GetFromCache(STRING_TO_REGEXP_CACHE_ID, re);
535   } else if (IS_REGEXP(re)) {
536     regexp = re;
537   } else {
538     regexp = new $RegExp(re);
539   }
540   var match = DoRegExpExec(regexp, TO_STRING_INLINE(this), 0);
541   if (match) {
542     return match[CAPTURE0];
543   }
544   return -1;
545 }
546
547
548 // ECMA-262 section 15.5.4.13
549 function StringSlice(start, end) {
550   CHECK_OBJECT_COERCIBLE(this, "String.prototype.slice");
551
552   var s = TO_STRING_INLINE(this);
553   var s_len = s.length;
554   var start_i = TO_INTEGER(start);
555   var end_i = s_len;
556   if (!IS_UNDEFINED(end)) {
557     end_i = TO_INTEGER(end);
558   }
559
560   if (start_i < 0) {
561     start_i += s_len;
562     if (start_i < 0) {
563       start_i = 0;
564     }
565   } else {
566     if (start_i > s_len) {
567       return '';
568     }
569   }
570
571   if (end_i < 0) {
572     end_i += s_len;
573     if (end_i < 0) {
574       return '';
575     }
576   } else {
577     if (end_i > s_len) {
578       end_i = s_len;
579     }
580   }
581
582   if (end_i <= start_i) {
583     return '';
584   }
585
586   return %_SubString(s, start_i, end_i);
587 }
588
589
590 // ECMA-262 section 15.5.4.14
591 function StringSplit(separator, limit) {
592   CHECK_OBJECT_COERCIBLE(this, "String.prototype.split");
593
594   var subject = TO_STRING_INLINE(this);
595   limit = (IS_UNDEFINED(limit)) ? 0xffffffff : TO_UINT32(limit);
596
597   var length = subject.length;
598   if (!IS_REGEXP(separator)) {
599     var separator_string = TO_STRING_INLINE(separator);
600
601     if (limit === 0) return [];
602
603     // ECMA-262 says that if separator is undefined, the result should
604     // be an array of size 1 containing the entire string.
605     if (IS_UNDEFINED(separator)) return [subject];
606
607     var separator_length = separator_string.length;
608
609     // If the separator string is empty then return the elements in the subject.
610     if (separator_length === 0) return %StringToArray(subject, limit);
611
612     var result = %StringSplit(subject, separator_string, limit);
613
614     return result;
615   }
616
617   if (limit === 0) return [];
618
619   // Separator is a regular expression.
620   return StringSplitOnRegExp(subject, separator, limit, length);
621 }
622
623
624 var ArrayPushBuiltin = $Array.prototype.push;
625
626 function StringSplitOnRegExp(subject, separator, limit, length) {
627   %_Log('regexp', 'regexp-split,%0S,%1r', [subject, separator]);
628
629   if (length === 0) {
630     if (DoRegExpExec(separator, subject, 0, 0) != null) {
631       return [];
632     }
633     return [subject];
634   }
635
636   var currentIndex = 0;
637   var startIndex = 0;
638   var startMatch = 0;
639   var result = [];
640
641   outer_loop:
642   while (true) {
643
644     if (startIndex === length) {
645       %_CallFunction(result, %_SubString(subject, currentIndex, length),
646                      ArrayPushBuiltin);
647       break;
648     }
649
650     var matchInfo = DoRegExpExec(separator, subject, startIndex);
651     if (matchInfo == null || length === (startMatch = matchInfo[CAPTURE0])) {
652       %_CallFunction(result, %_SubString(subject, currentIndex, length),
653                      ArrayPushBuiltin);
654       break;
655     }
656     var endIndex = matchInfo[CAPTURE1];
657
658     // We ignore a zero-length match at the currentIndex.
659     if (startIndex === endIndex && endIndex === currentIndex) {
660       startIndex++;
661       continue;
662     }
663
664     %_CallFunction(result, %_SubString(subject, currentIndex, startMatch),
665                    ArrayPushBuiltin);
666
667     if (result.length === limit) break;
668
669     var matchinfo_len = NUMBER_OF_CAPTURES(matchInfo) + REGEXP_FIRST_CAPTURE;
670     for (var i = REGEXP_FIRST_CAPTURE + 2; i < matchinfo_len; ) {
671       var start = matchInfo[i++];
672       var end = matchInfo[i++];
673       if (end != -1) {
674         %_CallFunction(result, %_SubString(subject, start, end),
675                        ArrayPushBuiltin);
676       } else {
677         %_CallFunction(result, UNDEFINED, ArrayPushBuiltin);
678       }
679       if (result.length === limit) break outer_loop;
680     }
681
682     startIndex = currentIndex = endIndex;
683   }
684   return result;
685 }
686
687
688 // ECMA-262 section 15.5.4.15
689 function StringSubstring(start, end) {
690   CHECK_OBJECT_COERCIBLE(this, "String.prototype.subString");
691
692   var s = TO_STRING_INLINE(this);
693   var s_len = s.length;
694
695   var start_i = TO_INTEGER(start);
696   if (start_i < 0) {
697     start_i = 0;
698   } else if (start_i > s_len) {
699     start_i = s_len;
700   }
701
702   var end_i = s_len;
703   if (!IS_UNDEFINED(end)) {
704     end_i = TO_INTEGER(end);
705     if (end_i > s_len) {
706       end_i = s_len;
707     } else {
708       if (end_i < 0) end_i = 0;
709       if (start_i > end_i) {
710         var tmp = end_i;
711         end_i = start_i;
712         start_i = tmp;
713       }
714     }
715   }
716
717   return %_SubString(s, start_i, end_i);
718 }
719
720
721 // This is not a part of ECMA-262.
722 function StringSubstr(start, n) {
723   CHECK_OBJECT_COERCIBLE(this, "String.prototype.substr");
724
725   var s = TO_STRING_INLINE(this);
726   var len;
727
728   // Correct n: If not given, set to string length; if explicitly
729   // set to undefined, zero, or negative, returns empty string.
730   if (IS_UNDEFINED(n)) {
731     len = s.length;
732   } else {
733     len = TO_INTEGER(n);
734     if (len <= 0) return '';
735   }
736
737   // Correct start: If not given (or undefined), set to zero; otherwise
738   // convert to integer and handle negative case.
739   if (IS_UNDEFINED(start)) {
740     start = 0;
741   } else {
742     start = TO_INTEGER(start);
743     // If positive, and greater than or equal to the string length,
744     // return empty string.
745     if (start >= s.length) return '';
746     // If negative and absolute value is larger than the string length,
747     // use zero.
748     if (start < 0) {
749       start += s.length;
750       if (start < 0) start = 0;
751     }
752   }
753
754   var end = start + len;
755   if (end > s.length) end = s.length;
756
757   return %_SubString(s, start, end);
758 }
759
760
761 // ECMA-262, 15.5.4.16
762 function StringToLowerCase() {
763   CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLowerCase");
764
765   return %StringToLowerCase(TO_STRING_INLINE(this));
766 }
767
768
769 // ECMA-262, 15.5.4.17
770 function StringToLocaleLowerCase() {
771   CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLocaleLowerCase");
772
773   return %StringToLowerCase(TO_STRING_INLINE(this));
774 }
775
776
777 // ECMA-262, 15.5.4.18
778 function StringToUpperCase() {
779   CHECK_OBJECT_COERCIBLE(this, "String.prototype.toUpperCase");
780
781   return %StringToUpperCase(TO_STRING_INLINE(this));
782 }
783
784
785 // ECMA-262, 15.5.4.19
786 function StringToLocaleUpperCase() {
787   CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLocaleUpperCase");
788
789   return %StringToUpperCase(TO_STRING_INLINE(this));
790 }
791
792 // ES5, 15.5.4.20
793 function StringTrim() {
794   CHECK_OBJECT_COERCIBLE(this, "String.prototype.trim");
795
796   return %StringTrim(TO_STRING_INLINE(this), true, true);
797 }
798
799 function StringTrimLeft() {
800   CHECK_OBJECT_COERCIBLE(this, "String.prototype.trimLeft");
801
802   return %StringTrim(TO_STRING_INLINE(this), true, false);
803 }
804
805 function StringTrimRight() {
806   CHECK_OBJECT_COERCIBLE(this, "String.prototype.trimRight");
807
808   return %StringTrim(TO_STRING_INLINE(this), false, true);
809 }
810
811
812 // ECMA-262, section 15.5.3.2
813 function StringFromCharCode(code) {
814   var n = %_ArgumentsLength();
815   if (n == 1) {
816     if (!%_IsSmi(code)) code = ToNumber(code);
817     return %_StringCharFromCode(code & 0xffff);
818   }
819
820   var one_byte = %NewString(n, NEW_ONE_BYTE_STRING);
821   var i;
822   for (i = 0; i < n; i++) {
823     var code = %_Arguments(i);
824     if (!%_IsSmi(code)) code = ToNumber(code) & 0xffff;
825     if (code < 0) code = code & 0xffff;
826     if (code > 0xff) break;
827     %_OneByteSeqStringSetChar(one_byte, i, code);
828   }
829   if (i == n) return one_byte;
830   one_byte = %TruncateString(one_byte, i);
831
832   var two_byte = %NewString(n - i, NEW_TWO_BYTE_STRING);
833   for (var j = 0; i < n; i++, j++) {
834     var code = %_Arguments(i);
835     if (!%_IsSmi(code)) code = ToNumber(code) & 0xffff;
836     %_TwoByteSeqStringSetChar(two_byte, j, code);
837   }
838   return one_byte + two_byte;
839 }
840
841
842 // Helper function for very basic XSS protection.
843 function HtmlEscape(str) {
844   return TO_STRING_INLINE(str).replace(/</g, "&lt;")
845                               .replace(/>/g, "&gt;")
846                               .replace(/"/g, "&quot;")
847                               .replace(/'/g, "&#039;");
848 }
849
850
851 // Compatibility support for KJS.
852 // Tested by mozilla/js/tests/js1_5/Regress/regress-276103.js.
853 function StringLink(s) {
854   return "<a href=\"" + HtmlEscape(s) + "\">" + this + "</a>";
855 }
856
857
858 function StringAnchor(name) {
859   return "<a name=\"" + HtmlEscape(name) + "\">" + this + "</a>";
860 }
861
862
863 function StringFontcolor(color) {
864   return "<font color=\"" + HtmlEscape(color) + "\">" + this + "</font>";
865 }
866
867
868 function StringFontsize(size) {
869   return "<font size=\"" + HtmlEscape(size) + "\">" + this + "</font>";
870 }
871
872
873 function StringBig() {
874   return "<big>" + this + "</big>";
875 }
876
877
878 function StringBlink() {
879   return "<blink>" + this + "</blink>";
880 }
881
882
883 function StringBold() {
884   return "<b>" + this + "</b>";
885 }
886
887
888 function StringFixed() {
889   return "<tt>" + this + "</tt>";
890 }
891
892
893 function StringItalics() {
894   return "<i>" + this + "</i>";
895 }
896
897
898 function StringSmall() {
899   return "<small>" + this + "</small>";
900 }
901
902
903 function StringStrike() {
904   return "<strike>" + this + "</strike>";
905 }
906
907
908 function StringSub() {
909   return "<sub>" + this + "</sub>";
910 }
911
912
913 function StringSup() {
914   return "<sup>" + this + "</sup>";
915 }
916
917 // -------------------------------------------------------------------
918
919 function SetUpString() {
920   %CheckIsBootstrapping();
921
922   // Set the String function and constructor.
923   %SetCode($String, StringConstructor);
924   %FunctionSetPrototype($String, new $String());
925
926   // Set up the constructor property on the String prototype object.
927   %SetProperty($String.prototype, "constructor", $String, DONT_ENUM);
928
929   // Set up the non-enumerable functions on the String object.
930   InstallFunctions($String, DONT_ENUM, $Array(
931     "fromCharCode", StringFromCharCode
932   ));
933
934   // Set up the non-enumerable functions on the String prototype object.
935   InstallFunctions($String.prototype, DONT_ENUM, $Array(
936     "valueOf", StringValueOf,
937     "toString", StringToString,
938     "charAt", StringCharAt,
939     "charCodeAt", StringCharCodeAt,
940     "concat", StringConcat,
941     "indexOf", StringIndexOf,
942     "lastIndexOf", StringLastIndexOf,
943     "localeCompare", StringLocaleCompare,
944     "match", StringMatch,
945     "replace", StringReplace,
946     "search", StringSearch,
947     "slice", StringSlice,
948     "split", StringSplit,
949     "substring", StringSubstring,
950     "substr", StringSubstr,
951     "toLowerCase", StringToLowerCase,
952     "toLocaleLowerCase", StringToLocaleLowerCase,
953     "toUpperCase", StringToUpperCase,
954     "toLocaleUpperCase", StringToLocaleUpperCase,
955     "trim", StringTrim,
956     "trimLeft", StringTrimLeft,
957     "trimRight", StringTrimRight,
958     "link", StringLink,
959     "anchor", StringAnchor,
960     "fontcolor", StringFontcolor,
961     "fontsize", StringFontsize,
962     "big", StringBig,
963     "blink", StringBlink,
964     "bold", StringBold,
965     "fixed", StringFixed,
966     "italics", StringItalics,
967     "small", StringSmall,
968     "strike", StringStrike,
969     "sub", StringSub,
970     "sup", StringSup
971   ));
972 }
973
974 SetUpString();