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