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