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