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.
5 (function(global, utils) {
7 %CheckIsBootstrapping();
9 // -------------------------------------------------------------------
12 var GlobalRegExp = global.RegExp;
13 var GlobalString = global.String;
14 var InternalArray = utils.InternalArray;
15 var InternalPackedArray = utils.InternalPackedArray;
22 var RegExpExecNoTests;
23 var RegExpLastMatchInfo;
25 utils.Import(function(from) {
26 ArrayIndexOf = from.ArrayIndexOf;
27 ArrayJoin = from.ArrayJoin;
28 MathMax = from.MathMax;
29 MathMin = from.MathMin;
30 RegExpExec = from.RegExpExec;
31 RegExpExecNoTests = from.RegExpExecNoTests;
32 RegExpLastMatchInfo = from.RegExpLastMatchInfo;
35 //-------------------------------------------------------------------
37 function StringConstructor(x) {
38 if (%_ArgumentsLength() == 0) x = '';
39 if (%_IsConstructCall()) {
40 %_SetValueOf(this, TO_STRING_INLINE(x));
43 %_CallFunction(x, $symbolToString) : TO_STRING_INLINE(x);
48 // ECMA-262 section 15.5.4.2
49 function StringToString() {
50 if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) {
51 throw MakeTypeError(kNotGeneric, 'String.prototype.toString');
53 return %_ValueOf(this);
57 // ECMA-262 section 15.5.4.3
58 function StringValueOf() {
59 if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) {
60 throw MakeTypeError(kNotGeneric, 'String.prototype.valueOf');
62 return %_ValueOf(this);
66 // ECMA-262, section 15.5.4.4
67 function StringCharAtJS(pos) {
68 CHECK_OBJECT_COERCIBLE(this, "String.prototype.charAt");
70 var result = %_StringCharAt(this, pos);
71 if (%_IsSmi(result)) {
72 result = %_StringCharAt(TO_STRING_INLINE(this), TO_INTEGER(pos));
78 // ECMA-262 section 15.5.4.5
79 function StringCharCodeAtJS(pos) {
80 CHECK_OBJECT_COERCIBLE(this, "String.prototype.charCodeAt");
82 var result = %_StringCharCodeAt(this, pos);
83 if (!%_IsSmi(result)) {
84 result = %_StringCharCodeAt(TO_STRING_INLINE(this), TO_INTEGER(pos));
90 // ECMA-262, section 15.5.4.6
91 function StringConcat(other /* and more */) { // length == 1
92 CHECK_OBJECT_COERCIBLE(this, "String.prototype.concat");
93 var len = %_ArgumentsLength();
94 var this_as_string = TO_STRING_INLINE(this);
96 return this_as_string + TO_STRING_INLINE(other);
98 var parts = new InternalArray(len + 1);
99 parts[0] = this_as_string;
100 for (var i = 0; i < len; i++) {
101 var part = %_Arguments(i);
102 parts[i + 1] = TO_STRING_INLINE(part);
104 return %StringBuilderConcat(parts, len + 1, "");
108 // ECMA-262 section 15.5.4.7
109 function StringIndexOfJS(pattern /* position */) { // length == 1
110 CHECK_OBJECT_COERCIBLE(this, "String.prototype.indexOf");
112 var subject = TO_STRING_INLINE(this);
113 pattern = TO_STRING_INLINE(pattern);
115 if (%_ArgumentsLength() > 1) {
116 index = %_Arguments(1); // position
117 index = TO_INTEGER(index);
118 if (index < 0) index = 0;
119 if (index > subject.length) index = subject.length;
121 return %StringIndexOf(subject, pattern, index);
125 // ECMA-262 section 15.5.4.8
126 function StringLastIndexOfJS(pat /* position */) { // length == 1
127 CHECK_OBJECT_COERCIBLE(this, "String.prototype.lastIndexOf");
129 var sub = TO_STRING_INLINE(this);
130 var subLength = sub.length;
131 var pat = TO_STRING_INLINE(pat);
132 var patLength = pat.length;
133 var index = subLength - patLength;
134 if (%_ArgumentsLength() > 1) {
135 var position = $toNumber(%_Arguments(1));
136 if (!NUMBER_IS_NAN(position)) {
137 position = TO_INTEGER(position);
141 if (position + patLength < subLength) {
149 return %StringLastIndexOf(sub, pat, index);
153 // ECMA-262 section 15.5.4.9
155 // This function is implementation specific. For now, we do not
156 // do anything locale specific.
157 function StringLocaleCompareJS(other) {
158 CHECK_OBJECT_COERCIBLE(this, "String.prototype.localeCompare");
160 return %StringLocaleCompare(TO_STRING_INLINE(this),
161 TO_STRING_INLINE(other));
165 // ECMA-262 section 15.5.4.10
166 function StringMatchJS(regexp) {
167 CHECK_OBJECT_COERCIBLE(this, "String.prototype.match");
169 var subject = TO_STRING_INLINE(this);
170 if (IS_REGEXP(regexp)) {
171 // Emulate RegExp.prototype.exec's side effect in step 5, even though
172 // value is discarded.
173 var lastIndex = regexp.lastIndex;
174 TO_INTEGER_FOR_SIDE_EFFECT(lastIndex);
175 if (!regexp.global) return RegExpExecNoTests(regexp, subject, 0);
176 var result = %StringMatch(subject, regexp, RegExpLastMatchInfo);
177 if (result !== null) $regexpLastMatchInfoOverride = null;
178 regexp.lastIndex = 0;
181 // Non-regexp argument.
182 regexp = new GlobalRegExp(regexp);
183 return RegExpExecNoTests(regexp, subject, 0);
187 // ECMA-262 v6, section 21.1.3.12
189 // For now we do nothing, as proper normalization requires big tables.
190 // If Intl is enabled, then i18n.js will override it and provide the the
191 // proper functionality.
192 function StringNormalizeJS(form) {
193 CHECK_OBJECT_COERCIBLE(this, "String.prototype.normalize");
195 var form = IS_UNDEFINED(form) ? 'NFC' : TO_STRING_INLINE(form);
197 var NORMALIZATION_FORMS = ['NFC', 'NFD', 'NFKC', 'NFKD'];
198 var normalizationForm =
199 %_CallFunction(NORMALIZATION_FORMS, form, ArrayIndexOf);
200 if (normalizationForm === -1) {
201 throw MakeRangeError(kNormalizationForm,
202 %_CallFunction(NORMALIZATION_FORMS, ', ', ArrayJoin));
205 return %_ValueOf(this);
209 // This has the same size as the RegExpLastMatchInfo array, and can be used
210 // for functions that expect that structure to be returned. It is used when
211 // the needle is a string rather than a regexp. In this case we can't update
212 // lastMatchArray without erroneously affecting the properties on the global
214 var reusableMatchInfo = [2, "", "", -1, -1];
217 // ECMA-262, section 15.5.4.11
218 function StringReplace(search, replace) {
219 CHECK_OBJECT_COERCIBLE(this, "String.prototype.replace");
221 var subject = TO_STRING_INLINE(this);
223 // Decision tree for dispatch
225 // .... string replace
226 // ...... non-global search
227 // ........ empty string replace
228 // ........ non-empty string replace (with $-expansion)
229 // ...... global search
230 // ........ no need to circumvent last match info override
231 // ........ need to circument last match info override
232 // .... function replace
233 // ...... global search
234 // ...... non-global search
236 // .... special case that replaces with one single character
237 // ...... function replace
238 // ...... string replace (with $-expansion)
240 if (IS_REGEXP(search)) {
241 // Emulate RegExp.prototype.exec's side effect in step 5, even if
242 // value is discarded.
243 var lastIndex = search.lastIndex;
244 TO_INTEGER_FOR_SIDE_EFFECT(lastIndex);
246 if (!IS_SPEC_FUNCTION(replace)) {
247 replace = TO_STRING_INLINE(replace);
249 if (!search.global) {
250 // Non-global regexp search, string replace.
251 var match = RegExpExec(search, subject, 0);
256 if (replace.length == 0) {
257 return %_SubString(subject, 0, match[CAPTURE0]) +
258 %_SubString(subject, match[CAPTURE1], subject.length)
260 return ExpandReplacement(replace, subject, RegExpLastMatchInfo,
261 %_SubString(subject, 0, match[CAPTURE0])) +
262 %_SubString(subject, match[CAPTURE1], subject.length);
265 // Global regexp search, string replace.
266 search.lastIndex = 0;
267 if ($regexpLastMatchInfoOverride == null) {
268 return %StringReplaceGlobalRegExpWithString(
269 subject, search, replace, RegExpLastMatchInfo);
271 // We use this hack to detect whether StringReplaceRegExpWithString
272 // found at least one hit. In that case we need to remove any
274 var saved_subject = RegExpLastMatchInfo[LAST_SUBJECT_INDEX];
275 RegExpLastMatchInfo[LAST_SUBJECT_INDEX] = 0;
276 var answer = %StringReplaceGlobalRegExpWithString(
277 subject, search, replace, RegExpLastMatchInfo);
278 if (%_IsSmi(RegExpLastMatchInfo[LAST_SUBJECT_INDEX])) {
279 RegExpLastMatchInfo[LAST_SUBJECT_INDEX] = saved_subject;
281 $regexpLastMatchInfoOverride = null;
288 // Global regexp search, function replace.
289 return StringReplaceGlobalRegExpWithFunction(subject, search, replace);
291 // Non-global regexp search, function replace.
292 return StringReplaceNonGlobalRegExpWithFunction(subject, search, replace);
295 search = TO_STRING_INLINE(search);
297 if (search.length == 1 &&
298 subject.length > 0xFF &&
299 IS_STRING(replace) &&
300 %StringIndexOf(replace, '$', 0) < 0) {
301 // Searching by traversing a cons string tree and replace with cons of
302 // slices works only when the replaced string is a single character, being
303 // replaced by a simple string and only pays off for long strings.
304 return %StringReplaceOneCharWithString(subject, search, replace);
306 var start = %StringIndexOf(subject, search, 0);
307 if (start < 0) return subject;
308 var end = start + search.length;
310 var result = %_SubString(subject, 0, start);
312 // Compute the string to replace with.
313 if (IS_SPEC_FUNCTION(replace)) {
314 var receiver = UNDEFINED;
315 result += %_CallFunction(receiver, search, start, subject, replace);
317 reusableMatchInfo[CAPTURE0] = start;
318 reusableMatchInfo[CAPTURE1] = end;
319 result = ExpandReplacement(TO_STRING_INLINE(replace),
325 return result + %_SubString(subject, end, subject.length);
329 // Expand the $-expressions in the string and return a new string with
331 function ExpandReplacement(string, subject, matchInfo, result) {
332 var length = string.length;
333 var next = %StringIndexOf(string, '$', 0);
335 if (length > 0) result += string;
339 if (next > 0) result += %_SubString(string, 0, next);
343 var position = next + 1;
344 if (position < length) {
345 var peek = %_StringCharCodeAt(string, position);
346 if (peek == 36) { // $$
349 } else if (peek == 38) { // $& - match
352 %_SubString(subject, matchInfo[CAPTURE0], matchInfo[CAPTURE1]);
353 } else if (peek == 96) { // $` - prefix
355 result += %_SubString(subject, 0, matchInfo[CAPTURE0]);
356 } else if (peek == 39) { // $' - suffix
358 result += %_SubString(subject, matchInfo[CAPTURE1], subject.length);
359 } else if (peek >= 48 && peek <= 57) {
360 // Valid indices are $1 .. $9, $01 .. $09 and $10 .. $99
361 var scaled_index = (peek - 48) << 1;
363 var number_of_captures = NUMBER_OF_CAPTURES(matchInfo);
364 if (position + 1 < string.length) {
365 var next = %_StringCharCodeAt(string, position + 1);
366 if (next >= 48 && next <= 57) {
367 var new_scaled_index = scaled_index * 10 + ((next - 48) << 1);
368 if (new_scaled_index < number_of_captures) {
369 scaled_index = new_scaled_index;
374 if (scaled_index != 0 && scaled_index < number_of_captures) {
375 var start = matchInfo[CAPTURE(scaled_index)];
378 %_SubString(subject, start, matchInfo[CAPTURE(scaled_index + 1)]);
391 // Go the the next $ in the string.
392 next = %StringIndexOf(string, '$', position);
394 // Return if there are no more $ characters in the string. If we
395 // haven't reached the end, we need to append the suffix.
397 if (position < length) {
398 result += %_SubString(string, position, length);
403 // Append substring between the previous and the next $ character.
404 if (next > position) {
405 result += %_SubString(string, position, next);
412 // Compute the string of a given regular expression capture.
413 function CaptureString(string, lastCaptureInfo, index) {
415 var scaled = index << 1;
416 // Compute start and end.
417 var start = lastCaptureInfo[CAPTURE(scaled)];
418 // If start isn't valid, return undefined.
419 if (start < 0) return;
420 var end = lastCaptureInfo[CAPTURE(scaled + 1)];
421 return %_SubString(string, start, end);
425 // TODO(lrn): This array will survive indefinitely if replace is never
426 // called again. However, it will be empty, since the contents are cleared
427 // in the finally block.
428 var reusableReplaceArray = new InternalArray(4);
430 // Helper function for replacing regular expressions with the result of a
431 // function application in String.prototype.replace.
432 function StringReplaceGlobalRegExpWithFunction(subject, regexp, replace) {
433 var resultArray = reusableReplaceArray;
435 reusableReplaceArray = null;
437 // Inside a nested replace (replace called from the replacement function
438 // of another replace) or we have failed to set the reusable array
439 // back due to an exception in a replacement function. Create a new
440 // array to use in the future, or until the original is written back.
441 resultArray = new InternalArray(16);
443 var res = %RegExpExecMultiple(regexp,
447 regexp.lastIndex = 0;
449 // No matches at all.
450 reusableReplaceArray = resultArray;
453 var len = res.length;
454 if (NUMBER_OF_CAPTURES(RegExpLastMatchInfo) == 2) {
455 // If the number of captures is two then there are no explicit captures in
456 // the regexp, just the implicit capture that captures the whole match. In
457 // this case we can simplify quite a bit and end up with something faster.
458 // The builder will consist of some integers that indicate slices of the
459 // input string and some replacements that were returned from the replace
462 var override = new InternalPackedArray(null, 0, subject);
463 for (var i = 0; i < len; i++) {
466 // Integers represent slices of the original string. Use these to
467 // get the offsets we need for the override array (so things like
468 // RegExp.leftContext work during the callback function.
470 match_start = (elem >> 11) + (elem & 0x7ff);
472 match_start = res[++i] - elem;
476 override[1] = match_start;
477 $regexpLastMatchInfoOverride = override;
479 %_CallFunction(UNDEFINED, elem, match_start, subject, replace);
480 // Overwrite the i'th element in the results with the string we got
481 // back from the callback function.
482 res[i] = TO_STRING_INLINE(func_result);
483 match_start += elem.length;
487 for (var i = 0; i < len; i++) {
489 if (!%_IsSmi(elem)) {
490 // elem must be an Array.
491 // Use the apply argument as backing for global RegExp properties.
492 $regexpLastMatchInfoOverride = elem;
493 var func_result = %Apply(replace, UNDEFINED, elem, 0, elem.length);
494 // Overwrite the i'th element in the results with the string we got
495 // back from the callback function.
496 res[i] = TO_STRING_INLINE(func_result);
500 var result = %StringBuilderConcat(res, res.length, subject);
501 resultArray.length = 0;
502 reusableReplaceArray = resultArray;
507 function StringReplaceNonGlobalRegExpWithFunction(subject, regexp, replace) {
508 var matchInfo = RegExpExec(regexp, subject, 0);
509 if (IS_NULL(matchInfo)) {
510 regexp.lastIndex = 0;
513 var index = matchInfo[CAPTURE0];
514 var result = %_SubString(subject, 0, index);
515 var endOfMatch = matchInfo[CAPTURE1];
516 // Compute the parameter list consisting of the match, captures, index,
517 // and subject for the replace function invocation.
518 // The number of captures plus one for the match.
519 var m = NUMBER_OF_CAPTURES(matchInfo) >> 1;
522 // No captures, only the match, which is always valid.
523 var s = %_SubString(subject, index, endOfMatch);
524 // Don't call directly to avoid exposing the built-in global object.
525 replacement = %_CallFunction(UNDEFINED, s, index, subject, replace);
527 var parameters = new InternalArray(m + 2);
528 for (var j = 0; j < m; j++) {
529 parameters[j] = CaptureString(subject, matchInfo, j);
531 parameters[j] = index;
532 parameters[j + 1] = subject;
534 replacement = %Apply(replace, UNDEFINED, parameters, 0, j + 2);
537 result += replacement; // The add method converts to string if necessary.
538 // Can't use matchInfo any more from here, since the function could
540 return result + %_SubString(subject, endOfMatch, subject.length);
544 // ECMA-262 section 15.5.4.12
545 function StringSearch(re) {
546 CHECK_OBJECT_COERCIBLE(this, "String.prototype.search");
550 regexp = %_GetFromCache(STRING_TO_REGEXP_CACHE_ID, re);
551 } else if (IS_REGEXP(re)) {
554 regexp = new GlobalRegExp(re);
556 var match = RegExpExec(regexp, TO_STRING_INLINE(this), 0);
558 return match[CAPTURE0];
564 // ECMA-262 section 15.5.4.13
565 function StringSlice(start, end) {
566 CHECK_OBJECT_COERCIBLE(this, "String.prototype.slice");
568 var s = TO_STRING_INLINE(this);
569 var s_len = s.length;
570 var start_i = TO_INTEGER(start);
572 if (!IS_UNDEFINED(end)) {
573 end_i = TO_INTEGER(end);
582 if (start_i > s_len) {
598 if (end_i <= start_i) {
602 return %_SubString(s, start_i, end_i);
606 // ECMA-262 section 15.5.4.14
607 function StringSplitJS(separator, limit) {
608 CHECK_OBJECT_COERCIBLE(this, "String.prototype.split");
610 var subject = TO_STRING_INLINE(this);
611 limit = (IS_UNDEFINED(limit)) ? 0xffffffff : TO_UINT32(limit);
613 var length = subject.length;
614 if (!IS_REGEXP(separator)) {
615 var separator_string = TO_STRING_INLINE(separator);
617 if (limit === 0) return [];
619 // ECMA-262 says that if separator is undefined, the result should
620 // be an array of size 1 containing the entire string.
621 if (IS_UNDEFINED(separator)) return [subject];
623 var separator_length = separator_string.length;
625 // If the separator string is empty then return the elements in the subject.
626 if (separator_length === 0) return %StringToArray(subject, limit);
628 var result = %StringSplit(subject, separator_string, limit);
633 if (limit === 0) return [];
635 // Separator is a regular expression.
636 return StringSplitOnRegExp(subject, separator, limit, length);
640 function StringSplitOnRegExp(subject, separator, limit, length) {
642 if (RegExpExec(separator, subject, 0, 0) != null) {
648 var currentIndex = 0;
651 var result = new InternalArray();
656 if (startIndex === length) {
657 result[result.length] = %_SubString(subject, currentIndex, length);
661 var matchInfo = RegExpExec(separator, subject, startIndex);
662 if (matchInfo == null || length === (startMatch = matchInfo[CAPTURE0])) {
663 result[result.length] = %_SubString(subject, currentIndex, length);
666 var endIndex = matchInfo[CAPTURE1];
668 // We ignore a zero-length match at the currentIndex.
669 if (startIndex === endIndex && endIndex === currentIndex) {
674 result[result.length] = %_SubString(subject, currentIndex, startMatch);
676 if (result.length === limit) break;
678 var matchinfo_len = NUMBER_OF_CAPTURES(matchInfo) + REGEXP_FIRST_CAPTURE;
679 for (var i = REGEXP_FIRST_CAPTURE + 2; i < matchinfo_len; ) {
680 var start = matchInfo[i++];
681 var end = matchInfo[i++];
683 result[result.length] = %_SubString(subject, start, end);
685 result[result.length] = UNDEFINED;
687 if (result.length === limit) break outer_loop;
690 startIndex = currentIndex = endIndex;
692 var array_result = [];
693 %MoveArrayContents(result, array_result);
698 // ECMA-262 section 15.5.4.15
699 function StringSubstring(start, end) {
700 CHECK_OBJECT_COERCIBLE(this, "String.prototype.subString");
702 var s = TO_STRING_INLINE(this);
703 var s_len = s.length;
705 var start_i = TO_INTEGER(start);
708 } else if (start_i > s_len) {
713 if (!IS_UNDEFINED(end)) {
714 end_i = TO_INTEGER(end);
718 if (end_i < 0) end_i = 0;
719 if (start_i > end_i) {
727 return %_SubString(s, start_i, end_i);
731 // ES6 draft, revision 26 (2014-07-18), section B.2.3.1
732 function StringSubstr(start, n) {
733 CHECK_OBJECT_COERCIBLE(this, "String.prototype.substr");
735 var s = TO_STRING_INLINE(this);
738 // Correct n: If not given, set to string length; if explicitly
739 // set to undefined, zero, or negative, returns empty string.
740 if (IS_UNDEFINED(n)) {
744 if (len <= 0) return '';
747 // Correct start: If not given (or undefined), set to zero; otherwise
748 // convert to integer and handle negative case.
749 if (IS_UNDEFINED(start)) {
752 start = TO_INTEGER(start);
753 // If positive, and greater than or equal to the string length,
754 // return empty string.
755 if (start >= s.length) return '';
756 // If negative and absolute value is larger than the string length,
760 if (start < 0) start = 0;
764 var end = start + len;
765 if (end > s.length) end = s.length;
767 return %_SubString(s, start, end);
771 // ECMA-262, 15.5.4.16
772 function StringToLowerCaseJS() {
773 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLowerCase");
775 return %StringToLowerCase(TO_STRING_INLINE(this));
779 // ECMA-262, 15.5.4.17
780 function StringToLocaleLowerCase() {
781 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLocaleLowerCase");
783 return %StringToLowerCase(TO_STRING_INLINE(this));
787 // ECMA-262, 15.5.4.18
788 function StringToUpperCaseJS() {
789 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toUpperCase");
791 return %StringToUpperCase(TO_STRING_INLINE(this));
795 // ECMA-262, 15.5.4.19
796 function StringToLocaleUpperCase() {
797 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLocaleUpperCase");
799 return %StringToUpperCase(TO_STRING_INLINE(this));
803 function StringTrimJS() {
804 CHECK_OBJECT_COERCIBLE(this, "String.prototype.trim");
806 return %StringTrim(TO_STRING_INLINE(this), true, true);
809 function StringTrimLeft() {
810 CHECK_OBJECT_COERCIBLE(this, "String.prototype.trimLeft");
812 return %StringTrim(TO_STRING_INLINE(this), true, false);
815 function StringTrimRight() {
816 CHECK_OBJECT_COERCIBLE(this, "String.prototype.trimRight");
818 return %StringTrim(TO_STRING_INLINE(this), false, true);
822 // ECMA-262, section 15.5.3.2
823 function StringFromCharCode(code) {
824 var n = %_ArgumentsLength();
826 if (!%_IsSmi(code)) code = $toNumber(code);
827 return %_StringCharFromCode(code & 0xffff);
830 var one_byte = %NewString(n, NEW_ONE_BYTE_STRING);
832 for (i = 0; i < n; i++) {
833 var code = %_Arguments(i);
834 if (!%_IsSmi(code)) code = $toNumber(code) & 0xffff;
835 if (code < 0) code = code & 0xffff;
836 if (code > 0xff) break;
837 %_OneByteSeqStringSetChar(i, code, one_byte);
839 if (i == n) return one_byte;
840 one_byte = %TruncateString(one_byte, i);
842 var two_byte = %NewString(n - i, NEW_TWO_BYTE_STRING);
843 for (var j = 0; i < n; i++, j++) {
844 var code = %_Arguments(i);
845 if (!%_IsSmi(code)) code = $toNumber(code) & 0xffff;
846 %_TwoByteSeqStringSetChar(j, code, two_byte);
848 return one_byte + two_byte;
852 // ES6 draft, revision 26 (2014-07-18), section B.2.3.2.1
853 function HtmlEscape(str) {
854 return %_CallFunction(TO_STRING_INLINE(str), /"/g, """, StringReplace);
858 // ES6 draft, revision 26 (2014-07-18), section B.2.3.2
859 function StringAnchor(name) {
860 CHECK_OBJECT_COERCIBLE(this, "String.prototype.anchor");
861 return "<a name=\"" + HtmlEscape(name) + "\">" + TO_STRING_INLINE(this) +
866 // ES6 draft, revision 26 (2014-07-18), section B.2.3.3
867 function StringBig() {
868 CHECK_OBJECT_COERCIBLE(this, "String.prototype.big");
869 return "<big>" + TO_STRING_INLINE(this) + "</big>";
873 // ES6 draft, revision 26 (2014-07-18), section B.2.3.4
874 function StringBlink() {
875 CHECK_OBJECT_COERCIBLE(this, "String.prototype.blink");
876 return "<blink>" + TO_STRING_INLINE(this) + "</blink>";
880 // ES6 draft, revision 26 (2014-07-18), section B.2.3.5
881 function StringBold() {
882 CHECK_OBJECT_COERCIBLE(this, "String.prototype.bold");
883 return "<b>" + TO_STRING_INLINE(this) + "</b>";
887 // ES6 draft, revision 26 (2014-07-18), section B.2.3.6
888 function StringFixed() {
889 CHECK_OBJECT_COERCIBLE(this, "String.prototype.fixed");
890 return "<tt>" + TO_STRING_INLINE(this) + "</tt>";
894 // ES6 draft, revision 26 (2014-07-18), section B.2.3.7
895 function StringFontcolor(color) {
896 CHECK_OBJECT_COERCIBLE(this, "String.prototype.fontcolor");
897 return "<font color=\"" + HtmlEscape(color) + "\">" + TO_STRING_INLINE(this) +
902 // ES6 draft, revision 26 (2014-07-18), section B.2.3.8
903 function StringFontsize(size) {
904 CHECK_OBJECT_COERCIBLE(this, "String.prototype.fontsize");
905 return "<font size=\"" + HtmlEscape(size) + "\">" + TO_STRING_INLINE(this) +
910 // ES6 draft, revision 26 (2014-07-18), section B.2.3.9
911 function StringItalics() {
912 CHECK_OBJECT_COERCIBLE(this, "String.prototype.italics");
913 return "<i>" + TO_STRING_INLINE(this) + "</i>";
917 // ES6 draft, revision 26 (2014-07-18), section B.2.3.10
918 function StringLink(s) {
919 CHECK_OBJECT_COERCIBLE(this, "String.prototype.link");
920 return "<a href=\"" + HtmlEscape(s) + "\">" + TO_STRING_INLINE(this) + "</a>";
924 // ES6 draft, revision 26 (2014-07-18), section B.2.3.11
925 function StringSmall() {
926 CHECK_OBJECT_COERCIBLE(this, "String.prototype.small");
927 return "<small>" + TO_STRING_INLINE(this) + "</small>";
931 // ES6 draft, revision 26 (2014-07-18), section B.2.3.12
932 function StringStrike() {
933 CHECK_OBJECT_COERCIBLE(this, "String.prototype.strike");
934 return "<strike>" + TO_STRING_INLINE(this) + "</strike>";
938 // ES6 draft, revision 26 (2014-07-18), section B.2.3.13
939 function StringSub() {
940 CHECK_OBJECT_COERCIBLE(this, "String.prototype.sub");
941 return "<sub>" + TO_STRING_INLINE(this) + "</sub>";
945 // ES6 draft, revision 26 (2014-07-18), section B.2.3.14
946 function StringSup() {
947 CHECK_OBJECT_COERCIBLE(this, "String.prototype.sup");
948 return "<sup>" + TO_STRING_INLINE(this) + "</sup>";
951 // ES6 draft 01-20-14, section 21.1.3.13
952 function StringRepeat(count) {
953 CHECK_OBJECT_COERCIBLE(this, "String.prototype.repeat");
955 var s = TO_STRING_INLINE(this);
956 var n = $toInteger(count);
957 // The maximum string length is stored in a smi, so a longer repeat
958 // must result in a range error.
959 if (n < 0 || n > %_MaxSmi()) throw MakeRangeError(kInvalidCountValue);
965 if (n === 0) return r;
971 // ES6 draft 04-05-14, section 21.1.3.18
972 function StringStartsWith(searchString /* position */) { // length == 1
973 CHECK_OBJECT_COERCIBLE(this, "String.prototype.startsWith");
975 var s = TO_STRING_INLINE(this);
977 if (IS_REGEXP(searchString)) {
978 throw MakeTypeError(kFirstArgumentNotRegExp, "String.prototype.startsWith");
981 var ss = TO_STRING_INLINE(searchString);
983 if (%_ArgumentsLength() > 1) {
984 pos = %_Arguments(1); // position
985 pos = $toInteger(pos);
988 var s_len = s.length;
989 var start = MathMin(MathMax(pos, 0), s_len);
990 var ss_len = ss.length;
991 if (ss_len + start > s_len) {
995 return %StringIndexOf(s, ss, start) === start;
999 // ES6 draft 04-05-14, section 21.1.3.7
1000 function StringEndsWith(searchString /* position */) { // length == 1
1001 CHECK_OBJECT_COERCIBLE(this, "String.prototype.endsWith");
1003 var s = TO_STRING_INLINE(this);
1005 if (IS_REGEXP(searchString)) {
1006 throw MakeTypeError(kFirstArgumentNotRegExp, "String.prototype.endsWith");
1009 var ss = TO_STRING_INLINE(searchString);
1010 var s_len = s.length;
1012 if (%_ArgumentsLength() > 1) {
1013 var arg = %_Arguments(1); // position
1014 if (!IS_UNDEFINED(arg)) {
1015 pos = $toInteger(arg);
1019 var end = MathMin(MathMax(pos, 0), s_len);
1020 var ss_len = ss.length;
1021 var start = end - ss_len;
1026 return %StringLastIndexOf(s, ss, start) === start;
1030 // ES6 draft 04-05-14, section 21.1.3.6
1031 function StringIncludes(searchString /* position */) { // length == 1
1032 CHECK_OBJECT_COERCIBLE(this, "String.prototype.includes");
1034 var string = TO_STRING_INLINE(this);
1036 if (IS_REGEXP(searchString)) {
1037 throw MakeTypeError(kFirstArgumentNotRegExp, "String.prototype.includes");
1040 searchString = TO_STRING_INLINE(searchString);
1042 if (%_ArgumentsLength() > 1) {
1043 pos = %_Arguments(1); // position
1044 pos = TO_INTEGER(pos);
1047 var stringLength = string.length;
1048 if (pos < 0) pos = 0;
1049 if (pos > stringLength) pos = stringLength;
1050 var searchStringLength = searchString.length;
1052 if (searchStringLength + pos > stringLength) {
1056 return %StringIndexOf(string, searchString, pos) !== -1;
1060 // ES6 Draft 05-22-2014, section 21.1.3.3
1061 function StringCodePointAt(pos) {
1062 CHECK_OBJECT_COERCIBLE(this, "String.prototype.codePointAt");
1064 var string = TO_STRING_INLINE(this);
1065 var size = string.length;
1066 pos = TO_INTEGER(pos);
1067 if (pos < 0 || pos >= size) {
1070 var first = %_StringCharCodeAt(string, pos);
1071 if (first < 0xD800 || first > 0xDBFF || pos + 1 == size) {
1074 var second = %_StringCharCodeAt(string, pos + 1);
1075 if (second < 0xDC00 || second > 0xDFFF) {
1078 return (first - 0xD800) * 0x400 + second + 0x2400;
1082 // ES6 Draft 05-22-2014, section 21.1.2.2
1083 function StringFromCodePoint(_) { // length = 1
1085 var length = %_ArgumentsLength();
1088 for (index = 0; index < length; index++) {
1089 code = %_Arguments(index);
1090 if (!%_IsSmi(code)) {
1091 code = $toNumber(code);
1093 if (code < 0 || code > 0x10FFFF || code !== TO_INTEGER(code)) {
1094 throw MakeRangeError(kInvalidCodePoint, code);
1096 if (code <= 0xFFFF) {
1097 result += %_StringCharFromCode(code);
1100 result += %_StringCharFromCode((code >>> 10) & 0x3FF | 0xD800);
1101 result += %_StringCharFromCode(code & 0x3FF | 0xDC00);
1108 // -------------------------------------------------------------------
1109 // String methods related to templates
1111 // ES6 Draft 03-17-2015, section 21.1.2.4
1112 function StringRaw(callSite) {
1113 // TODO(caitp): Use rest parameters when implemented
1114 var numberOfSubstitutions = %_ArgumentsLength();
1115 var cooked = $toObject(callSite);
1116 var raw = $toObject(cooked.raw);
1117 var literalSegments = $toLength(raw.length);
1118 if (literalSegments <= 0) return "";
1120 var result = $toString(raw[0]);
1122 for (var i = 1; i < literalSegments; ++i) {
1123 if (i < numberOfSubstitutions) {
1124 result += $toString(%_Arguments(i));
1126 result += $toString(raw[i]);
1132 // -------------------------------------------------------------------
1134 // Set the String function and constructor.
1135 %SetCode(GlobalString, StringConstructor);
1136 %FunctionSetPrototype(GlobalString, new GlobalString());
1138 // Set up the constructor property on the String prototype object.
1140 GlobalString.prototype, "constructor", GlobalString, DONT_ENUM);
1142 // Set up the non-enumerable functions on the String object.
1143 utils.InstallFunctions(GlobalString, DONT_ENUM, [
1144 "fromCharCode", StringFromCharCode,
1145 "fromCodePoint", StringFromCodePoint,
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,
1181 "anchor", StringAnchor,
1182 "fontcolor", StringFontcolor,
1183 "fontsize", StringFontsize,
1185 "blink", StringBlink,
1187 "fixed", StringFixed,
1188 "italics", StringItalics,
1189 "small", StringSmall,
1190 "strike", StringStrike,
1195 // -------------------------------------------------------------------
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.StringSplit = StringSplitJS;
1205 to.StringSubstr = StringSubstr;
1206 to.StringSubstring = StringSubstring;