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 // -------------------------------------------------------------------
14 var GlobalRegExp = global.RegExp;
15 var GlobalString = global.String;
16 var InternalArray = utils.InternalArray;
17 var InternalPackedArray = utils.InternalPackedArray;
19 var RegExpExecNoTests;
20 var RegExpLastMatchInfo;
24 utils.Import(function(from) {
25 ArrayIndexOf = from.ArrayIndexOf;
26 ArrayJoin = from.ArrayJoin;
27 RegExpExec = from.RegExpExec;
28 RegExpExecNoTests = from.RegExpExecNoTests;
29 RegExpLastMatchInfo = from.RegExpLastMatchInfo;
30 ToNumber = from.ToNumber;
31 ToString = from.ToString;
34 //-------------------------------------------------------------------
36 // ECMA-262 section 15.5.4.2
37 function StringToString() {
38 if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) {
39 throw MakeTypeError(kNotGeneric, 'String.prototype.toString');
41 return %_ValueOf(this);
45 // ECMA-262 section 15.5.4.3
46 function StringValueOf() {
47 if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) {
48 throw MakeTypeError(kNotGeneric, 'String.prototype.valueOf');
50 return %_ValueOf(this);
54 // ECMA-262, section 15.5.4.4
55 function StringCharAtJS(pos) {
56 CHECK_OBJECT_COERCIBLE(this, "String.prototype.charAt");
58 var result = %_StringCharAt(this, pos);
59 if (%_IsSmi(result)) {
60 result = %_StringCharAt(TO_STRING_INLINE(this), TO_INTEGER(pos));
66 // ECMA-262 section 15.5.4.5
67 function StringCharCodeAtJS(pos) {
68 CHECK_OBJECT_COERCIBLE(this, "String.prototype.charCodeAt");
70 var result = %_StringCharCodeAt(this, pos);
71 if (!%_IsSmi(result)) {
72 result = %_StringCharCodeAt(TO_STRING_INLINE(this), TO_INTEGER(pos));
78 // ECMA-262, section 15.5.4.6
79 function StringConcat(other /* and more */) { // length == 1
80 CHECK_OBJECT_COERCIBLE(this, "String.prototype.concat");
81 var len = %_ArgumentsLength();
82 var this_as_string = TO_STRING_INLINE(this);
84 return this_as_string + TO_STRING_INLINE(other);
86 var parts = new InternalArray(len + 1);
87 parts[0] = this_as_string;
88 for (var i = 0; i < len; i++) {
89 var part = %_Arguments(i);
90 parts[i + 1] = TO_STRING_INLINE(part);
92 return %StringBuilderConcat(parts, len + 1, "");
96 // ECMA-262 section 15.5.4.7
97 function StringIndexOfJS(pattern /* position */) { // length == 1
98 CHECK_OBJECT_COERCIBLE(this, "String.prototype.indexOf");
100 var subject = TO_STRING_INLINE(this);
101 pattern = TO_STRING_INLINE(pattern);
103 if (%_ArgumentsLength() > 1) {
104 index = %_Arguments(1); // position
105 index = TO_INTEGER(index);
106 if (index < 0) index = 0;
107 if (index > subject.length) index = subject.length;
109 return %StringIndexOf(subject, pattern, index);
113 // ECMA-262 section 15.5.4.8
114 function StringLastIndexOfJS(pat /* position */) { // length == 1
115 CHECK_OBJECT_COERCIBLE(this, "String.prototype.lastIndexOf");
117 var sub = TO_STRING_INLINE(this);
118 var subLength = sub.length;
119 var pat = TO_STRING_INLINE(pat);
120 var patLength = pat.length;
121 var index = subLength - patLength;
122 if (%_ArgumentsLength() > 1) {
123 var position = ToNumber(%_Arguments(1));
124 if (!NUMBER_IS_NAN(position)) {
125 position = TO_INTEGER(position);
129 if (position + patLength < subLength) {
137 return %StringLastIndexOf(sub, pat, index);
141 // ECMA-262 section 15.5.4.9
143 // This function is implementation specific. For now, we do not
144 // do anything locale specific.
145 function StringLocaleCompareJS(other) {
146 CHECK_OBJECT_COERCIBLE(this, "String.prototype.localeCompare");
148 return %StringLocaleCompare(TO_STRING_INLINE(this),
149 TO_STRING_INLINE(other));
153 // ECMA-262 section 15.5.4.10
154 function StringMatchJS(regexp) {
155 CHECK_OBJECT_COERCIBLE(this, "String.prototype.match");
157 var subject = TO_STRING_INLINE(this);
158 if (IS_REGEXP(regexp)) {
159 // Emulate RegExp.prototype.exec's side effect in step 5, even though
160 // value is discarded.
161 var lastIndex = regexp.lastIndex;
162 TO_INTEGER_FOR_SIDE_EFFECT(lastIndex);
163 if (!regexp.global) return RegExpExecNoTests(regexp, subject, 0);
164 var result = %StringMatch(subject, regexp, RegExpLastMatchInfo);
165 if (result !== null) $regexpLastMatchInfoOverride = null;
166 regexp.lastIndex = 0;
169 // Non-regexp argument.
170 regexp = new GlobalRegExp(regexp);
171 return RegExpExecNoTests(regexp, subject, 0);
175 // ECMA-262 v6, section 21.1.3.12
177 // For now we do nothing, as proper normalization requires big tables.
178 // If Intl is enabled, then i18n.js will override it and provide the the
179 // proper functionality.
180 function StringNormalizeJS() {
181 CHECK_OBJECT_COERCIBLE(this, "String.prototype.normalize");
182 var s = TO_STRING_INLINE(this);
184 var formArg = %_Arguments(0);
185 var form = IS_UNDEFINED(formArg) ? 'NFC' : TO_STRING_INLINE(formArg);
187 var NORMALIZATION_FORMS = ['NFC', 'NFD', 'NFKC', 'NFKD'];
188 var normalizationForm =
189 %_CallFunction(NORMALIZATION_FORMS, form, ArrayIndexOf);
190 if (normalizationForm === -1) {
191 throw MakeRangeError(kNormalizationForm,
192 %_CallFunction(NORMALIZATION_FORMS, ', ', ArrayJoin));
199 // This has the same size as the RegExpLastMatchInfo array, and can be used
200 // for functions that expect that structure to be returned. It is used when
201 // the needle is a string rather than a regexp. In this case we can't update
202 // lastMatchArray without erroneously affecting the properties on the global
204 var reusableMatchInfo = [2, "", "", -1, -1];
207 // ECMA-262, section 15.5.4.11
208 function StringReplace(search, replace) {
209 CHECK_OBJECT_COERCIBLE(this, "String.prototype.replace");
211 var subject = TO_STRING_INLINE(this);
213 // Decision tree for dispatch
215 // .... string replace
216 // ...... non-global search
217 // ........ empty string replace
218 // ........ non-empty string replace (with $-expansion)
219 // ...... global search
220 // ........ no need to circumvent last match info override
221 // ........ need to circument last match info override
222 // .... function replace
223 // ...... global search
224 // ...... non-global search
226 // .... special case that replaces with one single character
227 // ...... function replace
228 // ...... string replace (with $-expansion)
230 if (IS_REGEXP(search)) {
231 // Emulate RegExp.prototype.exec's side effect in step 5, even if
232 // value is discarded.
233 var lastIndex = search.lastIndex;
234 TO_INTEGER_FOR_SIDE_EFFECT(lastIndex);
236 if (!IS_CALLABLE(replace)) {
237 replace = TO_STRING_INLINE(replace);
239 if (!search.global) {
240 // Non-global regexp search, string replace.
241 var match = RegExpExec(search, subject, 0);
246 if (replace.length == 0) {
247 return %_SubString(subject, 0, match[CAPTURE0]) +
248 %_SubString(subject, match[CAPTURE1], subject.length)
250 return ExpandReplacement(replace, subject, RegExpLastMatchInfo,
251 %_SubString(subject, 0, match[CAPTURE0])) +
252 %_SubString(subject, match[CAPTURE1], subject.length);
255 // Global regexp search, string replace.
256 search.lastIndex = 0;
257 if ($regexpLastMatchInfoOverride == null) {
258 return %StringReplaceGlobalRegExpWithString(
259 subject, search, replace, RegExpLastMatchInfo);
261 // We use this hack to detect whether StringReplaceRegExpWithString
262 // found at least one hit. In that case we need to remove any
264 var saved_subject = RegExpLastMatchInfo[LAST_SUBJECT_INDEX];
265 RegExpLastMatchInfo[LAST_SUBJECT_INDEX] = 0;
266 var answer = %StringReplaceGlobalRegExpWithString(
267 subject, search, replace, RegExpLastMatchInfo);
268 if (%_IsSmi(RegExpLastMatchInfo[LAST_SUBJECT_INDEX])) {
269 RegExpLastMatchInfo[LAST_SUBJECT_INDEX] = saved_subject;
271 $regexpLastMatchInfoOverride = null;
278 // Global regexp search, function replace.
279 return StringReplaceGlobalRegExpWithFunction(subject, search, replace);
281 // Non-global regexp search, function replace.
282 return StringReplaceNonGlobalRegExpWithFunction(subject, search, replace);
285 search = TO_STRING_INLINE(search);
287 if (search.length == 1 &&
288 subject.length > 0xFF &&
289 IS_STRING(replace) &&
290 %StringIndexOf(replace, '$', 0) < 0) {
291 // Searching by traversing a cons string tree and replace with cons of
292 // slices works only when the replaced string is a single character, being
293 // replaced by a simple string and only pays off for long strings.
294 return %StringReplaceOneCharWithString(subject, search, replace);
296 var start = %StringIndexOf(subject, search, 0);
297 if (start < 0) return subject;
298 var end = start + search.length;
300 var result = %_SubString(subject, 0, start);
302 // Compute the string to replace with.
303 if (IS_CALLABLE(replace)) {
304 result += replace(search, start, subject);
306 reusableMatchInfo[CAPTURE0] = start;
307 reusableMatchInfo[CAPTURE1] = end;
308 result = ExpandReplacement(TO_STRING_INLINE(replace),
314 return result + %_SubString(subject, end, subject.length);
318 // Expand the $-expressions in the string and return a new string with
320 function ExpandReplacement(string, subject, matchInfo, result) {
321 var length = string.length;
322 var next = %StringIndexOf(string, '$', 0);
324 if (length > 0) result += string;
328 if (next > 0) result += %_SubString(string, 0, next);
332 var position = next + 1;
333 if (position < length) {
334 var peek = %_StringCharCodeAt(string, position);
335 if (peek == 36) { // $$
338 } else if (peek == 38) { // $& - match
341 %_SubString(subject, matchInfo[CAPTURE0], matchInfo[CAPTURE1]);
342 } else if (peek == 96) { // $` - prefix
344 result += %_SubString(subject, 0, matchInfo[CAPTURE0]);
345 } else if (peek == 39) { // $' - suffix
347 result += %_SubString(subject, matchInfo[CAPTURE1], subject.length);
348 } else if (peek >= 48 && peek <= 57) {
349 // Valid indices are $1 .. $9, $01 .. $09 and $10 .. $99
350 var scaled_index = (peek - 48) << 1;
352 var number_of_captures = NUMBER_OF_CAPTURES(matchInfo);
353 if (position + 1 < string.length) {
354 var next = %_StringCharCodeAt(string, position + 1);
355 if (next >= 48 && next <= 57) {
356 var new_scaled_index = scaled_index * 10 + ((next - 48) << 1);
357 if (new_scaled_index < number_of_captures) {
358 scaled_index = new_scaled_index;
363 if (scaled_index != 0 && scaled_index < number_of_captures) {
364 var start = matchInfo[CAPTURE(scaled_index)];
367 %_SubString(subject, start, matchInfo[CAPTURE(scaled_index + 1)]);
380 // Go the the next $ in the string.
381 next = %StringIndexOf(string, '$', position);
383 // Return if there are no more $ characters in the string. If we
384 // haven't reached the end, we need to append the suffix.
386 if (position < length) {
387 result += %_SubString(string, position, length);
392 // Append substring between the previous and the next $ character.
393 if (next > position) {
394 result += %_SubString(string, position, next);
401 // Compute the string of a given regular expression capture.
402 function CaptureString(string, lastCaptureInfo, index) {
404 var scaled = index << 1;
405 // Compute start and end.
406 var start = lastCaptureInfo[CAPTURE(scaled)];
407 // If start isn't valid, return undefined.
408 if (start < 0) return;
409 var end = lastCaptureInfo[CAPTURE(scaled + 1)];
410 return %_SubString(string, start, end);
414 // TODO(lrn): This array will survive indefinitely if replace is never
415 // called again. However, it will be empty, since the contents are cleared
416 // in the finally block.
417 var reusableReplaceArray = new InternalArray(4);
419 // Helper function for replacing regular expressions with the result of a
420 // function application in String.prototype.replace.
421 function StringReplaceGlobalRegExpWithFunction(subject, regexp, replace) {
422 var resultArray = reusableReplaceArray;
424 reusableReplaceArray = null;
426 // Inside a nested replace (replace called from the replacement function
427 // of another replace) or we have failed to set the reusable array
428 // back due to an exception in a replacement function. Create a new
429 // array to use in the future, or until the original is written back.
430 resultArray = new InternalArray(16);
432 var res = %RegExpExecMultiple(regexp,
436 regexp.lastIndex = 0;
438 // No matches at all.
439 reusableReplaceArray = resultArray;
442 var len = res.length;
443 if (NUMBER_OF_CAPTURES(RegExpLastMatchInfo) == 2) {
444 // If the number of captures is two then there are no explicit captures in
445 // the regexp, just the implicit capture that captures the whole match. In
446 // this case we can simplify quite a bit and end up with something faster.
447 // The builder will consist of some integers that indicate slices of the
448 // input string and some replacements that were returned from the replace
451 var override = new InternalPackedArray(null, 0, subject);
452 for (var i = 0; i < len; i++) {
455 // Integers represent slices of the original string. Use these to
456 // get the offsets we need for the override array (so things like
457 // RegExp.leftContext work during the callback function.
459 match_start = (elem >> 11) + (elem & 0x7ff);
461 match_start = res[++i] - elem;
465 override[1] = match_start;
466 $regexpLastMatchInfoOverride = override;
467 var func_result = replace(elem, match_start, subject);
468 // Overwrite the i'th element in the results with the string we got
469 // back from the callback function.
470 res[i] = TO_STRING_INLINE(func_result);
471 match_start += elem.length;
475 for (var i = 0; i < len; i++) {
477 if (!%_IsSmi(elem)) {
478 // elem must be an Array.
479 // Use the apply argument as backing for global RegExp properties.
480 $regexpLastMatchInfoOverride = elem;
481 var func_result = %Apply(replace, UNDEFINED, elem, 0, elem.length);
482 // Overwrite the i'th element in the results with the string we got
483 // back from the callback function.
484 res[i] = TO_STRING_INLINE(func_result);
488 var result = %StringBuilderConcat(res, res.length, subject);
489 resultArray.length = 0;
490 reusableReplaceArray = resultArray;
495 function StringReplaceNonGlobalRegExpWithFunction(subject, regexp, replace) {
496 var matchInfo = RegExpExec(regexp, subject, 0);
497 if (IS_NULL(matchInfo)) {
498 regexp.lastIndex = 0;
501 var index = matchInfo[CAPTURE0];
502 var result = %_SubString(subject, 0, index);
503 var endOfMatch = matchInfo[CAPTURE1];
504 // Compute the parameter list consisting of the match, captures, index,
505 // and subject for the replace function invocation.
506 // The number of captures plus one for the match.
507 var m = NUMBER_OF_CAPTURES(matchInfo) >> 1;
510 // No captures, only the match, which is always valid.
511 var s = %_SubString(subject, index, endOfMatch);
512 // Don't call directly to avoid exposing the built-in global object.
513 replacement = replace(s, index, subject);
515 var parameters = new InternalArray(m + 2);
516 for (var j = 0; j < m; j++) {
517 parameters[j] = CaptureString(subject, matchInfo, j);
519 parameters[j] = index;
520 parameters[j + 1] = subject;
522 replacement = %Apply(replace, UNDEFINED, parameters, 0, j + 2);
525 result += replacement; // The add method converts to string if necessary.
526 // Can't use matchInfo any more from here, since the function could
528 return result + %_SubString(subject, endOfMatch, subject.length);
532 // ECMA-262 section 15.5.4.12
533 function StringSearch(re) {
534 CHECK_OBJECT_COERCIBLE(this, "String.prototype.search");
540 regexp = new GlobalRegExp(re);
542 var match = RegExpExec(regexp, TO_STRING_INLINE(this), 0);
544 return match[CAPTURE0];
550 // ECMA-262 section 15.5.4.13
551 function StringSlice(start, end) {
552 CHECK_OBJECT_COERCIBLE(this, "String.prototype.slice");
554 var s = TO_STRING_INLINE(this);
555 var s_len = s.length;
556 var start_i = TO_INTEGER(start);
558 if (!IS_UNDEFINED(end)) {
559 end_i = TO_INTEGER(end);
568 if (start_i > s_len) {
584 if (end_i <= start_i) {
588 return %_SubString(s, start_i, end_i);
592 // ECMA-262 section 15.5.4.14
593 function StringSplitJS(separator, limit) {
594 CHECK_OBJECT_COERCIBLE(this, "String.prototype.split");
596 var subject = TO_STRING_INLINE(this);
597 limit = (IS_UNDEFINED(limit)) ? 0xffffffff : TO_UINT32(limit);
599 var length = subject.length;
600 if (!IS_REGEXP(separator)) {
601 var separator_string = TO_STRING_INLINE(separator);
603 if (limit === 0) return [];
605 // ECMA-262 says that if separator is undefined, the result should
606 // be an array of size 1 containing the entire string.
607 if (IS_UNDEFINED(separator)) return [subject];
609 var separator_length = separator_string.length;
611 // If the separator string is empty then return the elements in the subject.
612 if (separator_length === 0) return %StringToArray(subject, limit);
614 var result = %StringSplit(subject, separator_string, limit);
619 if (limit === 0) return [];
621 // Separator is a regular expression.
622 return StringSplitOnRegExp(subject, separator, limit, length);
626 function StringSplitOnRegExp(subject, separator, limit, length) {
628 if (RegExpExec(separator, subject, 0, 0) != null) {
634 var currentIndex = 0;
637 var result = new InternalArray();
642 if (startIndex === length) {
643 result[result.length] = %_SubString(subject, currentIndex, length);
647 var matchInfo = RegExpExec(separator, subject, startIndex);
648 if (matchInfo == null || length === (startMatch = matchInfo[CAPTURE0])) {
649 result[result.length] = %_SubString(subject, currentIndex, length);
652 var endIndex = matchInfo[CAPTURE1];
654 // We ignore a zero-length match at the currentIndex.
655 if (startIndex === endIndex && endIndex === currentIndex) {
660 result[result.length] = %_SubString(subject, currentIndex, startMatch);
662 if (result.length === limit) break;
664 var matchinfo_len = NUMBER_OF_CAPTURES(matchInfo) + REGEXP_FIRST_CAPTURE;
665 for (var i = REGEXP_FIRST_CAPTURE + 2; i < matchinfo_len; ) {
666 var start = matchInfo[i++];
667 var end = matchInfo[i++];
669 result[result.length] = %_SubString(subject, start, end);
671 result[result.length] = UNDEFINED;
673 if (result.length === limit) break outer_loop;
676 startIndex = currentIndex = endIndex;
678 var array_result = [];
679 %MoveArrayContents(result, array_result);
684 // ECMA-262 section 15.5.4.15
685 function StringSubstring(start, end) {
686 CHECK_OBJECT_COERCIBLE(this, "String.prototype.subString");
688 var s = TO_STRING_INLINE(this);
689 var s_len = s.length;
691 var start_i = TO_INTEGER(start);
694 } else if (start_i > s_len) {
699 if (!IS_UNDEFINED(end)) {
700 end_i = TO_INTEGER(end);
704 if (end_i < 0) end_i = 0;
705 if (start_i > end_i) {
713 return %_SubString(s, start_i, end_i);
717 // ES6 draft, revision 26 (2014-07-18), section B.2.3.1
718 function StringSubstr(start, n) {
719 CHECK_OBJECT_COERCIBLE(this, "String.prototype.substr");
721 var s = TO_STRING_INLINE(this);
724 // Correct n: If not given, set to string length; if explicitly
725 // set to undefined, zero, or negative, returns empty string.
726 if (IS_UNDEFINED(n)) {
730 if (len <= 0) return '';
733 // Correct start: If not given (or undefined), set to zero; otherwise
734 // convert to integer and handle negative case.
735 if (IS_UNDEFINED(start)) {
738 start = TO_INTEGER(start);
739 // If positive, and greater than or equal to the string length,
740 // return empty string.
741 if (start >= s.length) return '';
742 // If negative and absolute value is larger than the string length,
746 if (start < 0) start = 0;
750 var end = start + len;
751 if (end > s.length) end = s.length;
753 return %_SubString(s, start, end);
757 // ECMA-262, 15.5.4.16
758 function StringToLowerCaseJS() {
759 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLowerCase");
761 return %StringToLowerCase(TO_STRING_INLINE(this));
765 // ECMA-262, 15.5.4.17
766 function StringToLocaleLowerCase() {
767 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLocaleLowerCase");
769 return %StringToLowerCase(TO_STRING_INLINE(this));
773 // ECMA-262, 15.5.4.18
774 function StringToUpperCaseJS() {
775 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toUpperCase");
777 return %StringToUpperCase(TO_STRING_INLINE(this));
781 // ECMA-262, 15.5.4.19
782 function StringToLocaleUpperCase() {
783 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLocaleUpperCase");
785 return %StringToUpperCase(TO_STRING_INLINE(this));
789 function StringTrimJS() {
790 CHECK_OBJECT_COERCIBLE(this, "String.prototype.trim");
792 return %StringTrim(TO_STRING_INLINE(this), true, true);
795 function StringTrimLeft() {
796 CHECK_OBJECT_COERCIBLE(this, "String.prototype.trimLeft");
798 return %StringTrim(TO_STRING_INLINE(this), true, false);
801 function StringTrimRight() {
802 CHECK_OBJECT_COERCIBLE(this, "String.prototype.trimRight");
804 return %StringTrim(TO_STRING_INLINE(this), false, true);
808 // ECMA-262, section 15.5.3.2
809 function StringFromCharCode(code) {
810 var n = %_ArgumentsLength();
812 if (!%_IsSmi(code)) code = ToNumber(code);
813 return %_StringCharFromCode(code & 0xffff);
816 var one_byte = %NewString(n, NEW_ONE_BYTE_STRING);
818 for (i = 0; i < n; i++) {
819 var code = %_Arguments(i);
820 if (!%_IsSmi(code)) code = ToNumber(code) & 0xffff;
821 if (code < 0) code = code & 0xffff;
822 if (code > 0xff) break;
823 %_OneByteSeqStringSetChar(i, code, one_byte);
825 if (i == n) return one_byte;
826 one_byte = %TruncateString(one_byte, i);
828 var two_byte = %NewString(n - i, NEW_TWO_BYTE_STRING);
829 for (var j = 0; i < n; i++, j++) {
830 var code = %_Arguments(i);
831 if (!%_IsSmi(code)) code = ToNumber(code) & 0xffff;
832 %_TwoByteSeqStringSetChar(j, code, two_byte);
834 return one_byte + two_byte;
838 // ES6 draft, revision 26 (2014-07-18), section B.2.3.2.1
839 function HtmlEscape(str) {
840 return %_CallFunction(TO_STRING_INLINE(str), /"/g, """, StringReplace);
844 // ES6 draft, revision 26 (2014-07-18), section B.2.3.2
845 function StringAnchor(name) {
846 CHECK_OBJECT_COERCIBLE(this, "String.prototype.anchor");
847 return "<a name=\"" + HtmlEscape(name) + "\">" + TO_STRING_INLINE(this) +
852 // ES6 draft, revision 26 (2014-07-18), section B.2.3.3
853 function StringBig() {
854 CHECK_OBJECT_COERCIBLE(this, "String.prototype.big");
855 return "<big>" + TO_STRING_INLINE(this) + "</big>";
859 // ES6 draft, revision 26 (2014-07-18), section B.2.3.4
860 function StringBlink() {
861 CHECK_OBJECT_COERCIBLE(this, "String.prototype.blink");
862 return "<blink>" + TO_STRING_INLINE(this) + "</blink>";
866 // ES6 draft, revision 26 (2014-07-18), section B.2.3.5
867 function StringBold() {
868 CHECK_OBJECT_COERCIBLE(this, "String.prototype.bold");
869 return "<b>" + TO_STRING_INLINE(this) + "</b>";
873 // ES6 draft, revision 26 (2014-07-18), section B.2.3.6
874 function StringFixed() {
875 CHECK_OBJECT_COERCIBLE(this, "String.prototype.fixed");
876 return "<tt>" + TO_STRING_INLINE(this) + "</tt>";
880 // ES6 draft, revision 26 (2014-07-18), section B.2.3.7
881 function StringFontcolor(color) {
882 CHECK_OBJECT_COERCIBLE(this, "String.prototype.fontcolor");
883 return "<font color=\"" + HtmlEscape(color) + "\">" + TO_STRING_INLINE(this) +
888 // ES6 draft, revision 26 (2014-07-18), section B.2.3.8
889 function StringFontsize(size) {
890 CHECK_OBJECT_COERCIBLE(this, "String.prototype.fontsize");
891 return "<font size=\"" + HtmlEscape(size) + "\">" + TO_STRING_INLINE(this) +
896 // ES6 draft, revision 26 (2014-07-18), section B.2.3.9
897 function StringItalics() {
898 CHECK_OBJECT_COERCIBLE(this, "String.prototype.italics");
899 return "<i>" + TO_STRING_INLINE(this) + "</i>";
903 // ES6 draft, revision 26 (2014-07-18), section B.2.3.10
904 function StringLink(s) {
905 CHECK_OBJECT_COERCIBLE(this, "String.prototype.link");
906 return "<a href=\"" + HtmlEscape(s) + "\">" + TO_STRING_INLINE(this) + "</a>";
910 // ES6 draft, revision 26 (2014-07-18), section B.2.3.11
911 function StringSmall() {
912 CHECK_OBJECT_COERCIBLE(this, "String.prototype.small");
913 return "<small>" + TO_STRING_INLINE(this) + "</small>";
917 // ES6 draft, revision 26 (2014-07-18), section B.2.3.12
918 function StringStrike() {
919 CHECK_OBJECT_COERCIBLE(this, "String.prototype.strike");
920 return "<strike>" + TO_STRING_INLINE(this) + "</strike>";
924 // ES6 draft, revision 26 (2014-07-18), section B.2.3.13
925 function StringSub() {
926 CHECK_OBJECT_COERCIBLE(this, "String.prototype.sub");
927 return "<sub>" + TO_STRING_INLINE(this) + "</sub>";
931 // ES6 draft, revision 26 (2014-07-18), section B.2.3.14
932 function StringSup() {
933 CHECK_OBJECT_COERCIBLE(this, "String.prototype.sup");
934 return "<sup>" + TO_STRING_INLINE(this) + "</sup>";
937 // ES6 draft 01-20-14, section 21.1.3.13
938 function StringRepeat(count) {
939 CHECK_OBJECT_COERCIBLE(this, "String.prototype.repeat");
941 var s = TO_STRING_INLINE(this);
942 var n = $toInteger(count);
943 // The maximum string length is stored in a smi, so a longer repeat
944 // must result in a range error.
945 if (n < 0 || n > %_MaxSmi()) throw MakeRangeError(kInvalidCountValue);
951 if (n === 0) return r;
957 // ES6 draft 04-05-14, section 21.1.3.18
958 function StringStartsWith(searchString /* position */) { // length == 1
959 CHECK_OBJECT_COERCIBLE(this, "String.prototype.startsWith");
961 var s = TO_STRING_INLINE(this);
963 if (IS_REGEXP(searchString)) {
964 throw MakeTypeError(kFirstArgumentNotRegExp, "String.prototype.startsWith");
967 var ss = TO_STRING_INLINE(searchString);
969 if (%_ArgumentsLength() > 1) {
970 var arg = %_Arguments(1); // position
971 if (!IS_UNDEFINED(arg)) {
972 pos = $toInteger(arg);
976 var s_len = s.length;
977 if (pos < 0) pos = 0;
978 if (pos > s_len) pos = s_len;
979 var ss_len = ss.length;
981 if (ss_len + pos > s_len) {
985 for (var i = 0; i < ss_len; i++) {
986 if (%_StringCharCodeAt(s, pos + i) !== %_StringCharCodeAt(ss, i)) {
995 // ES6 draft 04-05-14, section 21.1.3.7
996 function StringEndsWith(searchString /* position */) { // length == 1
997 CHECK_OBJECT_COERCIBLE(this, "String.prototype.endsWith");
999 var s = TO_STRING_INLINE(this);
1001 if (IS_REGEXP(searchString)) {
1002 throw MakeTypeError(kFirstArgumentNotRegExp, "String.prototype.endsWith");
1005 var ss = TO_STRING_INLINE(searchString);
1006 var s_len = s.length;
1008 if (%_ArgumentsLength() > 1) {
1009 var arg = %_Arguments(1); // position
1010 if (!IS_UNDEFINED(arg)) {
1011 pos = $toInteger(arg);
1015 if (pos < 0) pos = 0;
1016 if (pos > s_len) pos = s_len;
1017 var ss_len = ss.length;
1024 for (var i = 0; i < ss_len; i++) {
1025 if (%_StringCharCodeAt(s, pos + i) !== %_StringCharCodeAt(ss, i)) {
1034 // ES6 draft 04-05-14, section 21.1.3.6
1035 function StringIncludes(searchString /* position */) { // length == 1
1036 CHECK_OBJECT_COERCIBLE(this, "String.prototype.includes");
1038 var string = TO_STRING_INLINE(this);
1040 if (IS_REGEXP(searchString)) {
1041 throw MakeTypeError(kFirstArgumentNotRegExp, "String.prototype.includes");
1044 searchString = TO_STRING_INLINE(searchString);
1046 if (%_ArgumentsLength() > 1) {
1047 pos = %_Arguments(1); // position
1048 pos = TO_INTEGER(pos);
1051 var stringLength = string.length;
1052 if (pos < 0) pos = 0;
1053 if (pos > stringLength) pos = stringLength;
1054 var searchStringLength = searchString.length;
1056 if (searchStringLength + pos > stringLength) {
1060 return %StringIndexOf(string, searchString, pos) !== -1;
1064 // ES6 Draft 05-22-2014, section 21.1.3.3
1065 function StringCodePointAt(pos) {
1066 CHECK_OBJECT_COERCIBLE(this, "String.prototype.codePointAt");
1068 var string = TO_STRING_INLINE(this);
1069 var size = string.length;
1070 pos = TO_INTEGER(pos);
1071 if (pos < 0 || pos >= size) {
1074 var first = %_StringCharCodeAt(string, pos);
1075 if (first < 0xD800 || first > 0xDBFF || pos + 1 == size) {
1078 var second = %_StringCharCodeAt(string, pos + 1);
1079 if (second < 0xDC00 || second > 0xDFFF) {
1082 return (first - 0xD800) * 0x400 + second + 0x2400;
1086 // ES6 Draft 05-22-2014, section 21.1.2.2
1087 function StringFromCodePoint(_) { // length = 1
1089 var length = %_ArgumentsLength();
1092 for (index = 0; index < length; index++) {
1093 code = %_Arguments(index);
1094 if (!%_IsSmi(code)) {
1095 code = ToNumber(code);
1097 if (code < 0 || code > 0x10FFFF || code !== TO_INTEGER(code)) {
1098 throw MakeRangeError(kInvalidCodePoint, code);
1100 if (code <= 0xFFFF) {
1101 result += %_StringCharFromCode(code);
1104 result += %_StringCharFromCode((code >>> 10) & 0x3FF | 0xD800);
1105 result += %_StringCharFromCode(code & 0x3FF | 0xDC00);
1112 // -------------------------------------------------------------------
1113 // String methods related to templates
1115 // ES6 Draft 03-17-2015, section 21.1.2.4
1116 function StringRaw(callSite) {
1117 // TODO(caitp): Use rest parameters when implemented
1118 var numberOfSubstitutions = %_ArgumentsLength();
1119 var cooked = TO_OBJECT(callSite);
1120 var raw = TO_OBJECT(cooked.raw);
1121 var literalSegments = $toLength(raw.length);
1122 if (literalSegments <= 0) return "";
1124 var result = ToString(raw[0]);
1126 for (var i = 1; i < literalSegments; ++i) {
1127 if (i < numberOfSubstitutions) {
1128 result += ToString(%_Arguments(i));
1130 result += ToString(raw[i]);
1136 // -------------------------------------------------------------------
1138 // Set the String function and constructor.
1139 %FunctionSetPrototype(GlobalString, new GlobalString());
1141 // Set up the constructor property on the String prototype object.
1143 GlobalString.prototype, "constructor", GlobalString, DONT_ENUM);
1145 // Set up the non-enumerable functions on the String object.
1146 utils.InstallFunctions(GlobalString, DONT_ENUM, [
1147 "fromCharCode", StringFromCharCode,
1148 "fromCodePoint", StringFromCodePoint,
1152 // Set up the non-enumerable functions on the String prototype object.
1153 utils.InstallFunctions(GlobalString.prototype, DONT_ENUM, [
1154 "valueOf", StringValueOf,
1155 "toString", StringToString,
1156 "charAt", StringCharAtJS,
1157 "charCodeAt", StringCharCodeAtJS,
1158 "codePointAt", StringCodePointAt,
1159 "concat", StringConcat,
1160 "endsWith", StringEndsWith,
1161 "includes", StringIncludes,
1162 "indexOf", StringIndexOfJS,
1163 "lastIndexOf", StringLastIndexOfJS,
1164 "localeCompare", StringLocaleCompareJS,
1165 "match", StringMatchJS,
1166 "normalize", StringNormalizeJS,
1167 "repeat", StringRepeat,
1168 "replace", StringReplace,
1169 "search", StringSearch,
1170 "slice", StringSlice,
1171 "split", StringSplitJS,
1172 "substring", StringSubstring,
1173 "substr", StringSubstr,
1174 "startsWith", StringStartsWith,
1175 "toLowerCase", StringToLowerCaseJS,
1176 "toLocaleLowerCase", StringToLocaleLowerCase,
1177 "toUpperCase", StringToUpperCaseJS,
1178 "toLocaleUpperCase", StringToLocaleUpperCase,
1179 "trim", StringTrimJS,
1180 "trimLeft", StringTrimLeft,
1181 "trimRight", StringTrimRight,
1184 "anchor", StringAnchor,
1185 "fontcolor", StringFontcolor,
1186 "fontsize", StringFontsize,
1188 "blink", StringBlink,
1190 "fixed", StringFixed,
1191 "italics", StringItalics,
1192 "small", StringSmall,
1193 "strike", StringStrike,
1198 // -------------------------------------------------------------------
1201 utils.Export(function(to) {
1202 to.StringCharAt = StringCharAtJS;
1203 to.StringIndexOf = StringIndexOfJS;
1204 to.StringLastIndexOf = StringLastIndexOfJS;
1205 to.StringMatch = StringMatchJS;
1206 to.StringReplace = StringReplace;
1207 to.StringSlice = StringSlice;
1208 to.StringSplit = StringSplitJS;
1209 to.StringSubstr = StringSubstr;
1210 to.StringSubstring = StringSubstring;