[es6] Move builtin constructors for primitives to strict mode.
[platform/upstream/v8.git] / 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 (function(global, utils) {
6
7 %CheckIsBootstrapping();
8
9 // -------------------------------------------------------------------
10 // Imports
11
12 var ArrayIndexOf;
13 var ArrayJoin;
14 var GlobalRegExp = global.RegExp;
15 var GlobalString = global.String;
16 var InternalArray = utils.InternalArray;
17 var InternalPackedArray = utils.InternalPackedArray;
18 var RegExpExec;
19 var RegExpExecNoTests;
20 var RegExpLastMatchInfo;
21 var SymbolToString;
22 var ToNumber;
23 var ToString;
24
25 utils.Import(function(from) {
26   ArrayIndexOf = from.ArrayIndexOf;
27   ArrayJoin = from.ArrayJoin;
28   RegExpExec = from.RegExpExec;
29   RegExpExecNoTests = from.RegExpExecNoTests;
30   RegExpLastMatchInfo = from.RegExpLastMatchInfo;
31   SymbolToString = from.SymbolToString;
32   ToNumber = from.ToNumber;
33   ToString = from.ToString;
34 });
35
36 //-------------------------------------------------------------------
37
38 function StringConstructor(x) {
39   // TODO(bmeurer): Move this to toplevel.
40   "use strict";
41   if (%_ArgumentsLength() == 0) x = '';
42   if (%_IsConstructCall()) {
43     %_SetValueOf(this, TO_STRING_INLINE(x));
44   } else {
45     return IS_SYMBOL(x) ?
46         %_CallFunction(x, SymbolToString) : TO_STRING_INLINE(x);
47   }
48 }
49
50
51 // ECMA-262 section 15.5.4.2
52 function StringToString() {
53   if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) {
54     throw MakeTypeError(kNotGeneric, 'String.prototype.toString');
55   }
56   return %_ValueOf(this);
57 }
58
59
60 // ECMA-262 section 15.5.4.3
61 function StringValueOf() {
62   if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) {
63     throw MakeTypeError(kNotGeneric, 'String.prototype.valueOf');
64   }
65   return %_ValueOf(this);
66 }
67
68
69 // ECMA-262, section 15.5.4.4
70 function StringCharAtJS(pos) {
71   CHECK_OBJECT_COERCIBLE(this, "String.prototype.charAt");
72
73   var result = %_StringCharAt(this, pos);
74   if (%_IsSmi(result)) {
75     result = %_StringCharAt(TO_STRING_INLINE(this), TO_INTEGER(pos));
76   }
77   return result;
78 }
79
80
81 // ECMA-262 section 15.5.4.5
82 function StringCharCodeAtJS(pos) {
83   CHECK_OBJECT_COERCIBLE(this, "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(other /* and more */) {  // length == 1
95   CHECK_OBJECT_COERCIBLE(this, "String.prototype.concat");
96   var len = %_ArgumentsLength();
97   var this_as_string = TO_STRING_INLINE(this);
98   if (len === 1) {
99     return this_as_string + TO_STRING_INLINE(other);
100   }
101   var parts = new InternalArray(len + 1);
102   parts[0] = this_as_string;
103   for (var i = 0; i < len; i++) {
104     var part = %_Arguments(i);
105     parts[i + 1] = TO_STRING_INLINE(part);
106   }
107   return %StringBuilderConcat(parts, len + 1, "");
108 }
109
110
111 // ECMA-262 section 15.5.4.7
112 function StringIndexOfJS(pattern /* position */) {  // length == 1
113   CHECK_OBJECT_COERCIBLE(this, "String.prototype.indexOf");
114
115   var subject = TO_STRING_INLINE(this);
116   pattern = TO_STRING_INLINE(pattern);
117   var index = 0;
118   if (%_ArgumentsLength() > 1) {
119     index = %_Arguments(1);  // position
120     index = TO_INTEGER(index);
121     if (index < 0) index = 0;
122     if (index > subject.length) index = subject.length;
123   }
124   return %StringIndexOf(subject, pattern, index);
125 }
126
127
128 // ECMA-262 section 15.5.4.8
129 function StringLastIndexOfJS(pat /* position */) {  // length == 1
130   CHECK_OBJECT_COERCIBLE(this, "String.prototype.lastIndexOf");
131
132   var sub = TO_STRING_INLINE(this);
133   var subLength = sub.length;
134   var pat = TO_STRING_INLINE(pat);
135   var patLength = pat.length;
136   var index = subLength - patLength;
137   if (%_ArgumentsLength() > 1) {
138     var position = ToNumber(%_Arguments(1));
139     if (!NUMBER_IS_NAN(position)) {
140       position = TO_INTEGER(position);
141       if (position < 0) {
142         position = 0;
143       }
144       if (position + patLength < subLength) {
145         index = position;
146       }
147     }
148   }
149   if (index < 0) {
150     return -1;
151   }
152   return %StringLastIndexOf(sub, pat, index);
153 }
154
155
156 // ECMA-262 section 15.5.4.9
157 //
158 // This function is implementation specific.  For now, we do not
159 // do anything locale specific.
160 function StringLocaleCompareJS(other) {
161   CHECK_OBJECT_COERCIBLE(this, "String.prototype.localeCompare");
162
163   return %StringLocaleCompare(TO_STRING_INLINE(this),
164                               TO_STRING_INLINE(other));
165 }
166
167
168 // ECMA-262 section 15.5.4.10
169 function StringMatchJS(regexp) {
170   CHECK_OBJECT_COERCIBLE(this, "String.prototype.match");
171
172   var subject = TO_STRING_INLINE(this);
173   if (IS_REGEXP(regexp)) {
174     // Emulate RegExp.prototype.exec's side effect in step 5, even though
175     // value is discarded.
176     var lastIndex = regexp.lastIndex;
177     TO_INTEGER_FOR_SIDE_EFFECT(lastIndex);
178     if (!regexp.global) return RegExpExecNoTests(regexp, subject, 0);
179     var result = %StringMatch(subject, regexp, RegExpLastMatchInfo);
180     if (result !== null) $regexpLastMatchInfoOverride = null;
181     regexp.lastIndex = 0;
182     return result;
183   }
184   // Non-regexp argument.
185   regexp = new GlobalRegExp(regexp);
186   return RegExpExecNoTests(regexp, subject, 0);
187 }
188
189
190 // ECMA-262 v6, section 21.1.3.12
191 //
192 // For now we do nothing, as proper normalization requires big tables.
193 // If Intl is enabled, then i18n.js will override it and provide the the
194 // proper functionality.
195 function StringNormalizeJS() {
196   CHECK_OBJECT_COERCIBLE(this, "String.prototype.normalize");
197   var s = TO_STRING_INLINE(this);
198
199   var formArg = %_Arguments(0);
200   var form = IS_UNDEFINED(formArg) ? 'NFC' : TO_STRING_INLINE(formArg);
201
202   var NORMALIZATION_FORMS = ['NFC', 'NFD', 'NFKC', 'NFKD'];
203   var normalizationForm =
204       %_CallFunction(NORMALIZATION_FORMS, form, ArrayIndexOf);
205   if (normalizationForm === -1) {
206     throw MakeRangeError(kNormalizationForm,
207                          %_CallFunction(NORMALIZATION_FORMS, ', ', ArrayJoin));
208   }
209
210   return s;
211 }
212
213
214 // This has the same size as the RegExpLastMatchInfo array, and can be used
215 // for functions that expect that structure to be returned.  It is used when
216 // the needle is a string rather than a regexp.  In this case we can't update
217 // lastMatchArray without erroneously affecting the properties on the global
218 // RegExp object.
219 var reusableMatchInfo = [2, "", "", -1, -1];
220
221
222 // ECMA-262, section 15.5.4.11
223 function StringReplace(search, replace) {
224   CHECK_OBJECT_COERCIBLE(this, "String.prototype.replace");
225
226   var subject = TO_STRING_INLINE(this);
227
228   // Decision tree for dispatch
229   // .. regexp search
230   // .... string replace
231   // ...... non-global search
232   // ........ empty string replace
233   // ........ non-empty string replace (with $-expansion)
234   // ...... global search
235   // ........ no need to circumvent last match info override
236   // ........ need to circument last match info override
237   // .... function replace
238   // ...... global search
239   // ...... non-global search
240   // .. string search
241   // .... special case that replaces with one single character
242   // ...... function replace
243   // ...... string replace (with $-expansion)
244
245   if (IS_REGEXP(search)) {
246     // Emulate RegExp.prototype.exec's side effect in step 5, even if
247     // value is discarded.
248     var lastIndex = search.lastIndex;
249     TO_INTEGER_FOR_SIDE_EFFECT(lastIndex);
250
251     if (!IS_CALLABLE(replace)) {
252       replace = TO_STRING_INLINE(replace);
253
254       if (!search.global) {
255         // Non-global regexp search, string replace.
256         var match = RegExpExec(search, subject, 0);
257         if (match == null) {
258           search.lastIndex = 0
259           return subject;
260         }
261         if (replace.length == 0) {
262           return %_SubString(subject, 0, match[CAPTURE0]) +
263                  %_SubString(subject, match[CAPTURE1], subject.length)
264         }
265         return ExpandReplacement(replace, subject, RegExpLastMatchInfo,
266                                  %_SubString(subject, 0, match[CAPTURE0])) +
267                %_SubString(subject, match[CAPTURE1], subject.length);
268       }
269
270       // Global regexp search, string replace.
271       search.lastIndex = 0;
272       if ($regexpLastMatchInfoOverride == null) {
273         return %StringReplaceGlobalRegExpWithString(
274             subject, search, replace, RegExpLastMatchInfo);
275       } else {
276         // We use this hack to detect whether StringReplaceRegExpWithString
277         // found at least one hit. In that case we need to remove any
278         // override.
279         var saved_subject = RegExpLastMatchInfo[LAST_SUBJECT_INDEX];
280         RegExpLastMatchInfo[LAST_SUBJECT_INDEX] = 0;
281         var answer = %StringReplaceGlobalRegExpWithString(
282             subject, search, replace, RegExpLastMatchInfo);
283         if (%_IsSmi(RegExpLastMatchInfo[LAST_SUBJECT_INDEX])) {
284           RegExpLastMatchInfo[LAST_SUBJECT_INDEX] = saved_subject;
285         } else {
286           $regexpLastMatchInfoOverride = null;
287         }
288         return answer;
289       }
290     }
291
292     if (search.global) {
293       // Global regexp search, function replace.
294       return StringReplaceGlobalRegExpWithFunction(subject, search, replace);
295     }
296     // Non-global regexp search, function replace.
297     return StringReplaceNonGlobalRegExpWithFunction(subject, search, replace);
298   }
299
300   search = TO_STRING_INLINE(search);
301
302   if (search.length == 1 &&
303       subject.length > 0xFF &&
304       IS_STRING(replace) &&
305       %StringIndexOf(replace, '$', 0) < 0) {
306     // Searching by traversing a cons string tree and replace with cons of
307     // slices works only when the replaced string is a single character, being
308     // replaced by a simple string and only pays off for long strings.
309     return %StringReplaceOneCharWithString(subject, search, replace);
310   }
311   var start = %StringIndexOf(subject, search, 0);
312   if (start < 0) return subject;
313   var end = start + search.length;
314
315   var result = %_SubString(subject, 0, start);
316
317   // Compute the string to replace with.
318   if (IS_CALLABLE(replace)) {
319     result += replace(search, start, subject);
320   } else {
321     reusableMatchInfo[CAPTURE0] = start;
322     reusableMatchInfo[CAPTURE1] = end;
323     result = ExpandReplacement(TO_STRING_INLINE(replace),
324                                subject,
325                                reusableMatchInfo,
326                                result);
327   }
328
329   return result + %_SubString(subject, end, subject.length);
330 }
331
332
333 // Expand the $-expressions in the string and return a new string with
334 // the result.
335 function ExpandReplacement(string, subject, matchInfo, result) {
336   var length = string.length;
337   var next = %StringIndexOf(string, '$', 0);
338   if (next < 0) {
339     if (length > 0) result += string;
340     return result;
341   }
342
343   if (next > 0) result += %_SubString(string, 0, next);
344
345   while (true) {
346     var expansion = '$';
347     var position = next + 1;
348     if (position < length) {
349       var peek = %_StringCharCodeAt(string, position);
350       if (peek == 36) {         // $$
351         ++position;
352         result += '$';
353       } else if (peek == 38) {  // $& - match
354         ++position;
355         result +=
356           %_SubString(subject, matchInfo[CAPTURE0], matchInfo[CAPTURE1]);
357       } else if (peek == 96) {  // $` - prefix
358         ++position;
359         result += %_SubString(subject, 0, matchInfo[CAPTURE0]);
360       } else if (peek == 39) {  // $' - suffix
361         ++position;
362         result += %_SubString(subject, matchInfo[CAPTURE1], subject.length);
363       } else if (peek >= 48 && peek <= 57) {
364         // Valid indices are $1 .. $9, $01 .. $09 and $10 .. $99
365         var scaled_index = (peek - 48) << 1;
366         var advance = 1;
367         var number_of_captures = NUMBER_OF_CAPTURES(matchInfo);
368         if (position + 1 < string.length) {
369           var next = %_StringCharCodeAt(string, position + 1);
370           if (next >= 48 && next <= 57) {
371             var new_scaled_index = scaled_index * 10 + ((next - 48) << 1);
372             if (new_scaled_index < number_of_captures) {
373               scaled_index = new_scaled_index;
374               advance = 2;
375             }
376           }
377         }
378         if (scaled_index != 0 && scaled_index < number_of_captures) {
379           var start = matchInfo[CAPTURE(scaled_index)];
380           if (start >= 0) {
381             result +=
382               %_SubString(subject, start, matchInfo[CAPTURE(scaled_index + 1)]);
383           }
384           position += advance;
385         } else {
386           result += '$';
387         }
388       } else {
389         result += '$';
390       }
391     } else {
392       result += '$';
393     }
394
395     // Go the the next $ in the string.
396     next = %StringIndexOf(string, '$', position);
397
398     // Return if there are no more $ characters in the string. If we
399     // haven't reached the end, we need to append the suffix.
400     if (next < 0) {
401       if (position < length) {
402         result += %_SubString(string, position, length);
403       }
404       return result;
405     }
406
407     // Append substring between the previous and the next $ character.
408     if (next > position) {
409       result += %_SubString(string, position, next);
410     }
411   }
412   return result;
413 }
414
415
416 // Compute the string of a given regular expression capture.
417 function CaptureString(string, lastCaptureInfo, index) {
418   // Scale the index.
419   var scaled = index << 1;
420   // Compute start and end.
421   var start = lastCaptureInfo[CAPTURE(scaled)];
422   // If start isn't valid, return undefined.
423   if (start < 0) return;
424   var end = lastCaptureInfo[CAPTURE(scaled + 1)];
425   return %_SubString(string, start, end);
426 }
427
428
429 // TODO(lrn): This array will survive indefinitely if replace is never
430 // called again. However, it will be empty, since the contents are cleared
431 // in the finally block.
432 var reusableReplaceArray = new InternalArray(4);
433
434 // Helper function for replacing regular expressions with the result of a
435 // function application in String.prototype.replace.
436 function StringReplaceGlobalRegExpWithFunction(subject, regexp, replace) {
437   var resultArray = reusableReplaceArray;
438   if (resultArray) {
439     reusableReplaceArray = null;
440   } else {
441     // Inside a nested replace (replace called from the replacement function
442     // of another replace) or we have failed to set the reusable array
443     // back due to an exception in a replacement function. Create a new
444     // array to use in the future, or until the original is written back.
445     resultArray = new InternalArray(16);
446   }
447   var res = %RegExpExecMultiple(regexp,
448                                 subject,
449                                 RegExpLastMatchInfo,
450                                 resultArray);
451   regexp.lastIndex = 0;
452   if (IS_NULL(res)) {
453     // No matches at all.
454     reusableReplaceArray = resultArray;
455     return subject;
456   }
457   var len = res.length;
458   if (NUMBER_OF_CAPTURES(RegExpLastMatchInfo) == 2) {
459     // If the number of captures is two then there are no explicit captures in
460     // the regexp, just the implicit capture that captures the whole match.  In
461     // this case we can simplify quite a bit and end up with something faster.
462     // The builder will consist of some integers that indicate slices of the
463     // input string and some replacements that were returned from the replace
464     // function.
465     var match_start = 0;
466     var override = new InternalPackedArray(null, 0, subject);
467     for (var i = 0; i < len; i++) {
468       var elem = res[i];
469       if (%_IsSmi(elem)) {
470         // Integers represent slices of the original string.  Use these to
471         // get the offsets we need for the override array (so things like
472         // RegExp.leftContext work during the callback function.
473         if (elem > 0) {
474           match_start = (elem >> 11) + (elem & 0x7ff);
475         } else {
476           match_start = res[++i] - elem;
477         }
478       } else {
479         override[0] = elem;
480         override[1] = match_start;
481         $regexpLastMatchInfoOverride = override;
482         var func_result = replace(elem, match_start, subject);
483         // Overwrite the i'th element in the results with the string we got
484         // back from the callback function.
485         res[i] = TO_STRING_INLINE(func_result);
486         match_start += elem.length;
487       }
488     }
489   } else {
490     for (var i = 0; i < len; i++) {
491       var elem = res[i];
492       if (!%_IsSmi(elem)) {
493         // elem must be an Array.
494         // Use the apply argument as backing for global RegExp properties.
495         $regexpLastMatchInfoOverride = elem;
496         var func_result = %Apply(replace, UNDEFINED, elem, 0, elem.length);
497         // Overwrite the i'th element in the results with the string we got
498         // back from the callback function.
499         res[i] = TO_STRING_INLINE(func_result);
500       }
501     }
502   }
503   var result = %StringBuilderConcat(res, res.length, subject);
504   resultArray.length = 0;
505   reusableReplaceArray = resultArray;
506   return result;
507 }
508
509
510 function StringReplaceNonGlobalRegExpWithFunction(subject, regexp, replace) {
511   var matchInfo = RegExpExec(regexp, subject, 0);
512   if (IS_NULL(matchInfo)) {
513     regexp.lastIndex = 0;
514     return subject;
515   }
516   var index = matchInfo[CAPTURE0];
517   var result = %_SubString(subject, 0, index);
518   var endOfMatch = matchInfo[CAPTURE1];
519   // Compute the parameter list consisting of the match, captures, index,
520   // and subject for the replace function invocation.
521   // The number of captures plus one for the match.
522   var m = NUMBER_OF_CAPTURES(matchInfo) >> 1;
523   var replacement;
524   if (m == 1) {
525     // No captures, only the match, which is always valid.
526     var s = %_SubString(subject, index, endOfMatch);
527     // Don't call directly to avoid exposing the built-in global object.
528     replacement = replace(s, index, subject);
529   } else {
530     var parameters = new InternalArray(m + 2);
531     for (var j = 0; j < m; j++) {
532       parameters[j] = CaptureString(subject, matchInfo, j);
533     }
534     parameters[j] = index;
535     parameters[j + 1] = subject;
536
537     replacement = %Apply(replace, UNDEFINED, parameters, 0, j + 2);
538   }
539
540   result += replacement;  // The add method converts to string if necessary.
541   // Can't use matchInfo any more from here, since the function could
542   // overwrite it.
543   return result + %_SubString(subject, endOfMatch, subject.length);
544 }
545
546
547 // ECMA-262 section 15.5.4.12
548 function StringSearch(re) {
549   CHECK_OBJECT_COERCIBLE(this, "String.prototype.search");
550
551   var regexp;
552   if (IS_REGEXP(re)) {
553     regexp = re;
554   } else {
555     regexp = new GlobalRegExp(re);
556   }
557   var match = RegExpExec(regexp, TO_STRING_INLINE(this), 0);
558   if (match) {
559     return match[CAPTURE0];
560   }
561   return -1;
562 }
563
564
565 // ECMA-262 section 15.5.4.13
566 function StringSlice(start, end) {
567   CHECK_OBJECT_COERCIBLE(this, "String.prototype.slice");
568
569   var s = TO_STRING_INLINE(this);
570   var s_len = s.length;
571   var start_i = TO_INTEGER(start);
572   var end_i = s_len;
573   if (!IS_UNDEFINED(end)) {
574     end_i = TO_INTEGER(end);
575   }
576
577   if (start_i < 0) {
578     start_i += s_len;
579     if (start_i < 0) {
580       start_i = 0;
581     }
582   } else {
583     if (start_i > s_len) {
584       return '';
585     }
586   }
587
588   if (end_i < 0) {
589     end_i += s_len;
590     if (end_i < 0) {
591       return '';
592     }
593   } else {
594     if (end_i > s_len) {
595       end_i = s_len;
596     }
597   }
598
599   if (end_i <= start_i) {
600     return '';
601   }
602
603   return %_SubString(s, start_i, end_i);
604 }
605
606
607 // ECMA-262 section 15.5.4.14
608 function StringSplitJS(separator, limit) {
609   CHECK_OBJECT_COERCIBLE(this, "String.prototype.split");
610
611   var subject = TO_STRING_INLINE(this);
612   limit = (IS_UNDEFINED(limit)) ? 0xffffffff : TO_UINT32(limit);
613
614   var length = subject.length;
615   if (!IS_REGEXP(separator)) {
616     var separator_string = TO_STRING_INLINE(separator);
617
618     if (limit === 0) return [];
619
620     // ECMA-262 says that if separator is undefined, the result should
621     // be an array of size 1 containing the entire string.
622     if (IS_UNDEFINED(separator)) return [subject];
623
624     var separator_length = separator_string.length;
625
626     // If the separator string is empty then return the elements in the subject.
627     if (separator_length === 0) return %StringToArray(subject, limit);
628
629     var result = %StringSplit(subject, separator_string, limit);
630
631     return result;
632   }
633
634   if (limit === 0) return [];
635
636   // Separator is a regular expression.
637   return StringSplitOnRegExp(subject, separator, limit, length);
638 }
639
640
641 function StringSplitOnRegExp(subject, separator, limit, length) {
642   if (length === 0) {
643     if (RegExpExec(separator, subject, 0, 0) != null) {
644       return [];
645     }
646     return [subject];
647   }
648
649   var currentIndex = 0;
650   var startIndex = 0;
651   var startMatch = 0;
652   var result = new InternalArray();
653
654   outer_loop:
655   while (true) {
656
657     if (startIndex === length) {
658       result[result.length] = %_SubString(subject, currentIndex, length);
659       break;
660     }
661
662     var matchInfo = RegExpExec(separator, subject, startIndex);
663     if (matchInfo == null || length === (startMatch = matchInfo[CAPTURE0])) {
664       result[result.length] = %_SubString(subject, currentIndex, length);
665       break;
666     }
667     var endIndex = matchInfo[CAPTURE1];
668
669     // We ignore a zero-length match at the currentIndex.
670     if (startIndex === endIndex && endIndex === currentIndex) {
671       startIndex++;
672       continue;
673     }
674
675     result[result.length] = %_SubString(subject, currentIndex, startMatch);
676
677     if (result.length === limit) break;
678
679     var matchinfo_len = NUMBER_OF_CAPTURES(matchInfo) + REGEXP_FIRST_CAPTURE;
680     for (var i = REGEXP_FIRST_CAPTURE + 2; i < matchinfo_len; ) {
681       var start = matchInfo[i++];
682       var end = matchInfo[i++];
683       if (end != -1) {
684         result[result.length] = %_SubString(subject, start, end);
685       } else {
686         result[result.length] = UNDEFINED;
687       }
688       if (result.length === limit) break outer_loop;
689     }
690
691     startIndex = currentIndex = endIndex;
692   }
693   var array_result = [];
694   %MoveArrayContents(result, array_result);
695   return array_result;
696 }
697
698
699 // ECMA-262 section 15.5.4.15
700 function StringSubstring(start, end) {
701   CHECK_OBJECT_COERCIBLE(this, "String.prototype.subString");
702
703   var s = TO_STRING_INLINE(this);
704   var s_len = s.length;
705
706   var start_i = TO_INTEGER(start);
707   if (start_i < 0) {
708     start_i = 0;
709   } else if (start_i > s_len) {
710     start_i = s_len;
711   }
712
713   var end_i = s_len;
714   if (!IS_UNDEFINED(end)) {
715     end_i = TO_INTEGER(end);
716     if (end_i > s_len) {
717       end_i = s_len;
718     } else {
719       if (end_i < 0) end_i = 0;
720       if (start_i > end_i) {
721         var tmp = end_i;
722         end_i = start_i;
723         start_i = tmp;
724       }
725     }
726   }
727
728   return %_SubString(s, start_i, end_i);
729 }
730
731
732 // ES6 draft, revision 26 (2014-07-18), section B.2.3.1
733 function StringSubstr(start, n) {
734   CHECK_OBJECT_COERCIBLE(this, "String.prototype.substr");
735
736   var s = TO_STRING_INLINE(this);
737   var len;
738
739   // Correct n: If not given, set to string length; if explicitly
740   // set to undefined, zero, or negative, returns empty string.
741   if (IS_UNDEFINED(n)) {
742     len = s.length;
743   } else {
744     len = TO_INTEGER(n);
745     if (len <= 0) return '';
746   }
747
748   // Correct start: If not given (or undefined), set to zero; otherwise
749   // convert to integer and handle negative case.
750   if (IS_UNDEFINED(start)) {
751     start = 0;
752   } else {
753     start = TO_INTEGER(start);
754     // If positive, and greater than or equal to the string length,
755     // return empty string.
756     if (start >= s.length) return '';
757     // If negative and absolute value is larger than the string length,
758     // use zero.
759     if (start < 0) {
760       start += s.length;
761       if (start < 0) start = 0;
762     }
763   }
764
765   var end = start + len;
766   if (end > s.length) end = s.length;
767
768   return %_SubString(s, start, end);
769 }
770
771
772 // ECMA-262, 15.5.4.16
773 function StringToLowerCaseJS() {
774   CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLowerCase");
775
776   return %StringToLowerCase(TO_STRING_INLINE(this));
777 }
778
779
780 // ECMA-262, 15.5.4.17
781 function StringToLocaleLowerCase() {
782   CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLocaleLowerCase");
783
784   return %StringToLowerCase(TO_STRING_INLINE(this));
785 }
786
787
788 // ECMA-262, 15.5.4.18
789 function StringToUpperCaseJS() {
790   CHECK_OBJECT_COERCIBLE(this, "String.prototype.toUpperCase");
791
792   return %StringToUpperCase(TO_STRING_INLINE(this));
793 }
794
795
796 // ECMA-262, 15.5.4.19
797 function StringToLocaleUpperCase() {
798   CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLocaleUpperCase");
799
800   return %StringToUpperCase(TO_STRING_INLINE(this));
801 }
802
803 // ES5, 15.5.4.20
804 function StringTrimJS() {
805   CHECK_OBJECT_COERCIBLE(this, "String.prototype.trim");
806
807   return %StringTrim(TO_STRING_INLINE(this), true, true);
808 }
809
810 function StringTrimLeft() {
811   CHECK_OBJECT_COERCIBLE(this, "String.prototype.trimLeft");
812
813   return %StringTrim(TO_STRING_INLINE(this), true, false);
814 }
815
816 function StringTrimRight() {
817   CHECK_OBJECT_COERCIBLE(this, "String.prototype.trimRight");
818
819   return %StringTrim(TO_STRING_INLINE(this), false, true);
820 }
821
822
823 // ECMA-262, section 15.5.3.2
824 function StringFromCharCode(code) {
825   var n = %_ArgumentsLength();
826   if (n == 1) {
827     if (!%_IsSmi(code)) code = ToNumber(code);
828     return %_StringCharFromCode(code & 0xffff);
829   }
830
831   var one_byte = %NewString(n, NEW_ONE_BYTE_STRING);
832   var i;
833   for (i = 0; i < n; i++) {
834     var code = %_Arguments(i);
835     if (!%_IsSmi(code)) code = ToNumber(code) & 0xffff;
836     if (code < 0) code = code & 0xffff;
837     if (code > 0xff) break;
838     %_OneByteSeqStringSetChar(i, code, one_byte);
839   }
840   if (i == n) return one_byte;
841   one_byte = %TruncateString(one_byte, i);
842
843   var two_byte = %NewString(n - i, NEW_TWO_BYTE_STRING);
844   for (var j = 0; i < n; i++, j++) {
845     var code = %_Arguments(i);
846     if (!%_IsSmi(code)) code = ToNumber(code) & 0xffff;
847     %_TwoByteSeqStringSetChar(j, code, two_byte);
848   }
849   return one_byte + two_byte;
850 }
851
852
853 // ES6 draft, revision 26 (2014-07-18), section B.2.3.2.1
854 function HtmlEscape(str) {
855   return %_CallFunction(TO_STRING_INLINE(str), /"/g, "&quot;", StringReplace);
856 }
857
858
859 // ES6 draft, revision 26 (2014-07-18), section B.2.3.2
860 function StringAnchor(name) {
861   CHECK_OBJECT_COERCIBLE(this, "String.prototype.anchor");
862   return "<a name=\"" + HtmlEscape(name) + "\">" + TO_STRING_INLINE(this) +
863          "</a>";
864 }
865
866
867 // ES6 draft, revision 26 (2014-07-18), section B.2.3.3
868 function StringBig() {
869   CHECK_OBJECT_COERCIBLE(this, "String.prototype.big");
870   return "<big>" + TO_STRING_INLINE(this) + "</big>";
871 }
872
873
874 // ES6 draft, revision 26 (2014-07-18), section B.2.3.4
875 function StringBlink() {
876   CHECK_OBJECT_COERCIBLE(this, "String.prototype.blink");
877   return "<blink>" + TO_STRING_INLINE(this) + "</blink>";
878 }
879
880
881 // ES6 draft, revision 26 (2014-07-18), section B.2.3.5
882 function StringBold() {
883   CHECK_OBJECT_COERCIBLE(this, "String.prototype.bold");
884   return "<b>" + TO_STRING_INLINE(this) + "</b>";
885 }
886
887
888 // ES6 draft, revision 26 (2014-07-18), section B.2.3.6
889 function StringFixed() {
890   CHECK_OBJECT_COERCIBLE(this, "String.prototype.fixed");
891   return "<tt>" + TO_STRING_INLINE(this) + "</tt>";
892 }
893
894
895 // ES6 draft, revision 26 (2014-07-18), section B.2.3.7
896 function StringFontcolor(color) {
897   CHECK_OBJECT_COERCIBLE(this, "String.prototype.fontcolor");
898   return "<font color=\"" + HtmlEscape(color) + "\">" + TO_STRING_INLINE(this) +
899          "</font>";
900 }
901
902
903 // ES6 draft, revision 26 (2014-07-18), section B.2.3.8
904 function StringFontsize(size) {
905   CHECK_OBJECT_COERCIBLE(this, "String.prototype.fontsize");
906   return "<font size=\"" + HtmlEscape(size) + "\">" + TO_STRING_INLINE(this) +
907          "</font>";
908 }
909
910
911 // ES6 draft, revision 26 (2014-07-18), section B.2.3.9
912 function StringItalics() {
913   CHECK_OBJECT_COERCIBLE(this, "String.prototype.italics");
914   return "<i>" + TO_STRING_INLINE(this) + "</i>";
915 }
916
917
918 // ES6 draft, revision 26 (2014-07-18), section B.2.3.10
919 function StringLink(s) {
920   CHECK_OBJECT_COERCIBLE(this, "String.prototype.link");
921   return "<a href=\"" + HtmlEscape(s) + "\">" + TO_STRING_INLINE(this) + "</a>";
922 }
923
924
925 // ES6 draft, revision 26 (2014-07-18), section B.2.3.11
926 function StringSmall() {
927   CHECK_OBJECT_COERCIBLE(this, "String.prototype.small");
928   return "<small>" + TO_STRING_INLINE(this) + "</small>";
929 }
930
931
932 // ES6 draft, revision 26 (2014-07-18), section B.2.3.12
933 function StringStrike() {
934   CHECK_OBJECT_COERCIBLE(this, "String.prototype.strike");
935   return "<strike>" + TO_STRING_INLINE(this) + "</strike>";
936 }
937
938
939 // ES6 draft, revision 26 (2014-07-18), section B.2.3.13
940 function StringSub() {
941   CHECK_OBJECT_COERCIBLE(this, "String.prototype.sub");
942   return "<sub>" + TO_STRING_INLINE(this) + "</sub>";
943 }
944
945
946 // ES6 draft, revision 26 (2014-07-18), section B.2.3.14
947 function StringSup() {
948   CHECK_OBJECT_COERCIBLE(this, "String.prototype.sup");
949   return "<sup>" + TO_STRING_INLINE(this) + "</sup>";
950 }
951
952 // ES6 draft 01-20-14, section 21.1.3.13
953 function StringRepeat(count) {
954   CHECK_OBJECT_COERCIBLE(this, "String.prototype.repeat");
955
956   var s = TO_STRING_INLINE(this);
957   var n = $toInteger(count);
958   // The maximum string length is stored in a smi, so a longer repeat
959   // must result in a range error.
960   if (n < 0 || n > %_MaxSmi()) throw MakeRangeError(kInvalidCountValue);
961
962   var r = "";
963   while (true) {
964     if (n & 1) r += s;
965     n >>= 1;
966     if (n === 0) return r;
967     s += s;
968   }
969 }
970
971
972 // ES6 draft 04-05-14, section 21.1.3.18
973 function StringStartsWith(searchString /* position */) {  // length == 1
974   CHECK_OBJECT_COERCIBLE(this, "String.prototype.startsWith");
975
976   var s = TO_STRING_INLINE(this);
977
978   if (IS_REGEXP(searchString)) {
979     throw MakeTypeError(kFirstArgumentNotRegExp, "String.prototype.startsWith");
980   }
981
982   var ss = TO_STRING_INLINE(searchString);
983   var pos = 0;
984   if (%_ArgumentsLength() > 1) {
985     var arg = %_Arguments(1);  // position
986     if (!IS_UNDEFINED(arg)) {
987       pos = $toInteger(arg);
988     }
989   }
990
991   var s_len = s.length;
992   if (pos < 0) pos = 0;
993   if (pos > s_len) pos = s_len;
994   var ss_len = ss.length;
995
996   if (ss_len + pos > s_len) {
997     return false;
998   }
999
1000   for (var i = 0; i < ss_len; i++) {
1001     if (%_StringCharCodeAt(s, pos + i) !== %_StringCharCodeAt(ss, i)) {
1002       return false;
1003     }
1004   }
1005
1006   return true;
1007 }
1008
1009
1010 // ES6 draft 04-05-14, section 21.1.3.7
1011 function StringEndsWith(searchString /* position */) {  // length == 1
1012   CHECK_OBJECT_COERCIBLE(this, "String.prototype.endsWith");
1013
1014   var s = TO_STRING_INLINE(this);
1015
1016   if (IS_REGEXP(searchString)) {
1017     throw MakeTypeError(kFirstArgumentNotRegExp, "String.prototype.endsWith");
1018   }
1019
1020   var ss = TO_STRING_INLINE(searchString);
1021   var s_len = s.length;
1022   var pos = s_len;
1023   if (%_ArgumentsLength() > 1) {
1024     var arg = %_Arguments(1);  // position
1025     if (!IS_UNDEFINED(arg)) {
1026       pos = $toInteger(arg);
1027     }
1028   }
1029
1030   if (pos < 0) pos = 0;
1031   if (pos > s_len) pos = s_len;
1032   var ss_len = ss.length;
1033   pos = pos - ss_len;
1034
1035   if (pos < 0) {
1036     return false;
1037   }
1038
1039   for (var i = 0; i < ss_len; i++) {
1040     if (%_StringCharCodeAt(s, pos + i) !== %_StringCharCodeAt(ss, i)) {
1041       return false;
1042     }
1043   }
1044
1045   return true;
1046 }
1047
1048
1049 // ES6 draft 04-05-14, section 21.1.3.6
1050 function StringIncludes(searchString /* position */) {  // length == 1
1051   CHECK_OBJECT_COERCIBLE(this, "String.prototype.includes");
1052
1053   var string = TO_STRING_INLINE(this);
1054
1055   if (IS_REGEXP(searchString)) {
1056     throw MakeTypeError(kFirstArgumentNotRegExp, "String.prototype.includes");
1057   }
1058
1059   searchString = TO_STRING_INLINE(searchString);
1060   var pos = 0;
1061   if (%_ArgumentsLength() > 1) {
1062     pos = %_Arguments(1);  // position
1063     pos = TO_INTEGER(pos);
1064   }
1065
1066   var stringLength = string.length;
1067   if (pos < 0) pos = 0;
1068   if (pos > stringLength) pos = stringLength;
1069   var searchStringLength = searchString.length;
1070
1071   if (searchStringLength + pos > stringLength) {
1072     return false;
1073   }
1074
1075   return %StringIndexOf(string, searchString, pos) !== -1;
1076 }
1077
1078
1079 // ES6 Draft 05-22-2014, section 21.1.3.3
1080 function StringCodePointAt(pos) {
1081   CHECK_OBJECT_COERCIBLE(this, "String.prototype.codePointAt");
1082
1083   var string = TO_STRING_INLINE(this);
1084   var size = string.length;
1085   pos = TO_INTEGER(pos);
1086   if (pos < 0 || pos >= size) {
1087     return UNDEFINED;
1088   }
1089   var first = %_StringCharCodeAt(string, pos);
1090   if (first < 0xD800 || first > 0xDBFF || pos + 1 == size) {
1091     return first;
1092   }
1093   var second = %_StringCharCodeAt(string, pos + 1);
1094   if (second < 0xDC00 || second > 0xDFFF) {
1095     return first;
1096   }
1097   return (first - 0xD800) * 0x400 + second + 0x2400;
1098 }
1099
1100
1101 // ES6 Draft 05-22-2014, section 21.1.2.2
1102 function StringFromCodePoint(_) {  // length = 1
1103   var code;
1104   var length = %_ArgumentsLength();
1105   var index;
1106   var result = "";
1107   for (index = 0; index < length; index++) {
1108     code = %_Arguments(index);
1109     if (!%_IsSmi(code)) {
1110       code = ToNumber(code);
1111     }
1112     if (code < 0 || code > 0x10FFFF || code !== TO_INTEGER(code)) {
1113       throw MakeRangeError(kInvalidCodePoint, code);
1114     }
1115     if (code <= 0xFFFF) {
1116       result += %_StringCharFromCode(code);
1117     } else {
1118       code -= 0x10000;
1119       result += %_StringCharFromCode((code >>> 10) & 0x3FF | 0xD800);
1120       result += %_StringCharFromCode(code & 0x3FF | 0xDC00);
1121     }
1122   }
1123   return result;
1124 }
1125
1126
1127 // -------------------------------------------------------------------
1128 // String methods related to templates
1129
1130 // ES6 Draft 03-17-2015, section 21.1.2.4
1131 function StringRaw(callSite) {
1132   // TODO(caitp): Use rest parameters when implemented
1133   var numberOfSubstitutions = %_ArgumentsLength();
1134   var cooked = TO_OBJECT(callSite);
1135   var raw = TO_OBJECT(cooked.raw);
1136   var literalSegments = $toLength(raw.length);
1137   if (literalSegments <= 0) return "";
1138
1139   var result = ToString(raw[0]);
1140
1141   for (var i = 1; i < literalSegments; ++i) {
1142     if (i < numberOfSubstitutions) {
1143       result += ToString(%_Arguments(i));
1144     }
1145     result += ToString(raw[i]);
1146   }
1147
1148   return result;
1149 }
1150
1151 // -------------------------------------------------------------------
1152
1153 // Set the String function and constructor.
1154 %SetCode(GlobalString, StringConstructor);
1155 %FunctionSetPrototype(GlobalString, new GlobalString());
1156
1157 // Set up the constructor property on the String prototype object.
1158 %AddNamedProperty(
1159     GlobalString.prototype, "constructor", GlobalString, DONT_ENUM);
1160
1161 // Set up the non-enumerable functions on the String object.
1162 utils.InstallFunctions(GlobalString, DONT_ENUM, [
1163   "fromCharCode", StringFromCharCode,
1164   "fromCodePoint", StringFromCodePoint,
1165   "raw", StringRaw
1166 ]);
1167
1168 // Set up the non-enumerable functions on the String prototype object.
1169 utils.InstallFunctions(GlobalString.prototype, DONT_ENUM, [
1170   "valueOf", StringValueOf,
1171   "toString", StringToString,
1172   "charAt", StringCharAtJS,
1173   "charCodeAt", StringCharCodeAtJS,
1174   "codePointAt", StringCodePointAt,
1175   "concat", StringConcat,
1176   "endsWith", StringEndsWith,
1177   "includes", StringIncludes,
1178   "indexOf", StringIndexOfJS,
1179   "lastIndexOf", StringLastIndexOfJS,
1180   "localeCompare", StringLocaleCompareJS,
1181   "match", StringMatchJS,
1182   "normalize", StringNormalizeJS,
1183   "repeat", StringRepeat,
1184   "replace", StringReplace,
1185   "search", StringSearch,
1186   "slice", StringSlice,
1187   "split", StringSplitJS,
1188   "substring", StringSubstring,
1189   "substr", StringSubstr,
1190   "startsWith", StringStartsWith,
1191   "toLowerCase", StringToLowerCaseJS,
1192   "toLocaleLowerCase", StringToLocaleLowerCase,
1193   "toUpperCase", StringToUpperCaseJS,
1194   "toLocaleUpperCase", StringToLocaleUpperCase,
1195   "trim", StringTrimJS,
1196   "trimLeft", StringTrimLeft,
1197   "trimRight", StringTrimRight,
1198
1199   "link", StringLink,
1200   "anchor", StringAnchor,
1201   "fontcolor", StringFontcolor,
1202   "fontsize", StringFontsize,
1203   "big", StringBig,
1204   "blink", StringBlink,
1205   "bold", StringBold,
1206   "fixed", StringFixed,
1207   "italics", StringItalics,
1208   "small", StringSmall,
1209   "strike", StringStrike,
1210   "sub", StringSub,
1211   "sup", StringSup
1212 ]);
1213
1214 // -------------------------------------------------------------------
1215 // Exports
1216
1217 utils.Export(function(to) {
1218   to.StringCharAt = StringCharAtJS;
1219   to.StringIndexOf = StringIndexOfJS;
1220   to.StringLastIndexOf = StringLastIndexOfJS;
1221   to.StringMatch = StringMatchJS;
1222   to.StringReplace = StringReplace;
1223   to.StringSlice = StringSlice;
1224   to.StringSplit = StringSplitJS;
1225   to.StringSubstr = StringSubstr;
1226   to.StringSubstring = StringSubstring;
1227 });
1228
1229 })