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
11function 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
23function 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
32function 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
41function 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
53function 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
65function 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
84function 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
101function 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.
132function 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
141function 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
163var 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.
171function 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.
190var reusableMatchInfo = [2, "", "", -1, -1];
191
192
193// ECMA-262, section 15.5.4.11
194function 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.
307function 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.
389function 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.
404var reusableReplaceArray = new InternalArray(16);
405
406// Helper function for replacing regular expressions with the result of a
407// function application in String.prototype.replace.
408function 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
485function 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
524function 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
544function 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
586function 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
619function 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
678function 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
711function 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
751function 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
759function 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
767function 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
775function 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
782function StringTrimJS() {
783  CHECK_OBJECT_COERCIBLE(this, "String.prototype.trim");
784
785  return %StringTrim(TO_STRING_INLINE(this), true, true);
786}
787
788function StringTrimLeft() {
789  CHECK_OBJECT_COERCIBLE(this, "String.prototype.trimLeft");
790
791  return %StringTrim(TO_STRING_INLINE(this), true, false);
792}
793
794function 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
802function 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
832function 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
838function 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
845function 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
852function 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
859function 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
866function 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
873function 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
880function 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
887function 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
894function 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
901function 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
908function 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
915function 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
922function StringSup() {
923  CHECK_OBJECT_COERCIBLE(this, "String.prototype.sup");
924  return "<sup>" + this + "</sup>";
925}
926
927// -------------------------------------------------------------------
928
929function 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
985SetUpString();
986