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