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;
23 utils.Import(function(from) {
24 ArrayIndexOf = from.ArrayIndexOf;
25 ArrayJoin = from.ArrayJoin;
26 RegExpExec = from.RegExpExec;
27 RegExpExecNoTests = from.RegExpExecNoTests;
28 RegExpLastMatchInfo = from.RegExpLastMatchInfo;
29 ToNumber = from.ToNumber;
32 //-------------------------------------------------------------------
34 // ECMA-262 section 15.5.4.2
35 function StringToString() {
36 if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) {
37 throw MakeTypeError(kNotGeneric, 'String.prototype.toString');
39 return %_ValueOf(this);
43 // ECMA-262 section 15.5.4.3
44 function StringValueOf() {
45 if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) {
46 throw MakeTypeError(kNotGeneric, 'String.prototype.valueOf');
48 return %_ValueOf(this);
52 // ECMA-262, section 15.5.4.4
53 function StringCharAtJS(pos) {
54 CHECK_OBJECT_COERCIBLE(this, "String.prototype.charAt");
56 var result = %_StringCharAt(this, pos);
57 if (%_IsSmi(result)) {
58 result = %_StringCharAt(TO_STRING(this), TO_INTEGER(pos));
64 // ECMA-262 section 15.5.4.5
65 function StringCharCodeAtJS(pos) {
66 CHECK_OBJECT_COERCIBLE(this, "String.prototype.charCodeAt");
68 var result = %_StringCharCodeAt(this, pos);
69 if (!%_IsSmi(result)) {
70 result = %_StringCharCodeAt(TO_STRING(this), TO_INTEGER(pos));
76 // ECMA-262, section 15.5.4.6
77 function StringConcat(other /* and more */) { // length == 1
78 CHECK_OBJECT_COERCIBLE(this, "String.prototype.concat");
79 var len = %_ArgumentsLength();
80 var this_as_string = TO_STRING(this);
82 return this_as_string + TO_STRING(other);
84 var parts = new InternalArray(len + 1);
85 parts[0] = this_as_string;
86 for (var i = 0; i < len; i++) {
87 var part = %_Arguments(i);
88 parts[i + 1] = TO_STRING(part);
90 return %StringBuilderConcat(parts, len + 1, "");
94 // ECMA-262 section 15.5.4.7
95 function StringIndexOfJS(pattern /* position */) { // length == 1
96 CHECK_OBJECT_COERCIBLE(this, "String.prototype.indexOf");
98 var subject = TO_STRING(this);
99 pattern = TO_STRING(pattern);
101 if (%_ArgumentsLength() > 1) {
102 index = %_Arguments(1); // position
103 index = TO_INTEGER(index);
104 if (index < 0) index = 0;
105 if (index > subject.length) index = subject.length;
107 return %StringIndexOf(subject, pattern, index);
111 // ECMA-262 section 15.5.4.8
112 function StringLastIndexOfJS(pat /* position */) { // length == 1
113 CHECK_OBJECT_COERCIBLE(this, "String.prototype.lastIndexOf");
115 var sub = TO_STRING(this);
116 var subLength = sub.length;
117 var pat = TO_STRING(pat);
118 var patLength = pat.length;
119 var index = subLength - patLength;
120 if (%_ArgumentsLength() > 1) {
121 var position = ToNumber(%_Arguments(1));
122 if (!NUMBER_IS_NAN(position)) {
123 position = TO_INTEGER(position);
127 if (position + patLength < subLength) {
135 return %StringLastIndexOf(sub, pat, index);
139 // ECMA-262 section 15.5.4.9
141 // This function is implementation specific. For now, we do not
142 // do anything locale specific.
143 function StringLocaleCompareJS(other) {
144 CHECK_OBJECT_COERCIBLE(this, "String.prototype.localeCompare");
146 return %StringLocaleCompare(TO_STRING(this), TO_STRING(other));
150 // ECMA-262 section 15.5.4.10
151 function StringMatchJS(regexp) {
152 CHECK_OBJECT_COERCIBLE(this, "String.prototype.match");
154 var subject = TO_STRING(this);
155 if (IS_REGEXP(regexp)) {
156 // Emulate RegExp.prototype.exec's side effect in step 5, even though
157 // value is discarded.
158 var lastIndex = regexp.lastIndex;
159 TO_INTEGER_FOR_SIDE_EFFECT(lastIndex);
160 if (!regexp.global) return RegExpExecNoTests(regexp, subject, 0);
161 var result = %StringMatch(subject, regexp, RegExpLastMatchInfo);
162 if (result !== null) $regexpLastMatchInfoOverride = null;
163 regexp.lastIndex = 0;
166 // Non-regexp argument.
167 regexp = new GlobalRegExp(regexp);
168 return RegExpExecNoTests(regexp, subject, 0);
172 // ECMA-262 v6, section 21.1.3.12
174 // For now we do nothing, as proper normalization requires big tables.
175 // If Intl is enabled, then i18n.js will override it and provide the the
176 // proper functionality.
177 function StringNormalizeJS() {
178 CHECK_OBJECT_COERCIBLE(this, "String.prototype.normalize");
179 var s = TO_STRING(this);
181 var formArg = %_Arguments(0);
182 var form = IS_UNDEFINED(formArg) ? 'NFC' : TO_STRING(formArg);
184 var NORMALIZATION_FORMS = ['NFC', 'NFD', 'NFKC', 'NFKD'];
185 var normalizationForm =
186 %_CallFunction(NORMALIZATION_FORMS, form, ArrayIndexOf);
187 if (normalizationForm === -1) {
188 throw MakeRangeError(kNormalizationForm,
189 %_CallFunction(NORMALIZATION_FORMS, ', ', ArrayJoin));
196 // This has the same size as the RegExpLastMatchInfo array, and can be used
197 // for functions that expect that structure to be returned. It is used when
198 // the needle is a string rather than a regexp. In this case we can't update
199 // lastMatchArray without erroneously affecting the properties on the global
201 var reusableMatchInfo = [2, "", "", -1, -1];
204 // ECMA-262, section 15.5.4.11
205 function StringReplace(search, replace) {
206 CHECK_OBJECT_COERCIBLE(this, "String.prototype.replace");
208 var subject = TO_STRING(this);
210 // Decision tree for dispatch
212 // .... string replace
213 // ...... non-global search
214 // ........ empty string replace
215 // ........ non-empty string replace (with $-expansion)
216 // ...... global search
217 // ........ no need to circumvent last match info override
218 // ........ need to circument last match info override
219 // .... function replace
220 // ...... global search
221 // ...... non-global search
223 // .... special case that replaces with one single character
224 // ...... function replace
225 // ...... string replace (with $-expansion)
227 if (IS_REGEXP(search)) {
228 // Emulate RegExp.prototype.exec's side effect in step 5, even if
229 // value is discarded.
230 var lastIndex = search.lastIndex;
231 TO_INTEGER_FOR_SIDE_EFFECT(lastIndex);
233 if (!IS_CALLABLE(replace)) {
234 replace = TO_STRING(replace);
236 if (!search.global) {
237 // Non-global regexp search, string replace.
238 var match = RegExpExec(search, subject, 0);
243 if (replace.length == 0) {
244 return %_SubString(subject, 0, match[CAPTURE0]) +
245 %_SubString(subject, match[CAPTURE1], subject.length)
247 return ExpandReplacement(replace, subject, RegExpLastMatchInfo,
248 %_SubString(subject, 0, match[CAPTURE0])) +
249 %_SubString(subject, match[CAPTURE1], subject.length);
252 // Global regexp search, string replace.
253 search.lastIndex = 0;
254 if ($regexpLastMatchInfoOverride == null) {
255 return %StringReplaceGlobalRegExpWithString(
256 subject, search, replace, RegExpLastMatchInfo);
258 // We use this hack to detect whether StringReplaceRegExpWithString
259 // found at least one hit. In that case we need to remove any
261 var saved_subject = RegExpLastMatchInfo[LAST_SUBJECT_INDEX];
262 RegExpLastMatchInfo[LAST_SUBJECT_INDEX] = 0;
263 var answer = %StringReplaceGlobalRegExpWithString(
264 subject, search, replace, RegExpLastMatchInfo);
265 if (%_IsSmi(RegExpLastMatchInfo[LAST_SUBJECT_INDEX])) {
266 RegExpLastMatchInfo[LAST_SUBJECT_INDEX] = saved_subject;
268 $regexpLastMatchInfoOverride = null;
275 // Global regexp search, function replace.
276 return StringReplaceGlobalRegExpWithFunction(subject, search, replace);
278 // Non-global regexp search, function replace.
279 return StringReplaceNonGlobalRegExpWithFunction(subject, search, replace);
282 search = TO_STRING(search);
284 if (search.length == 1 &&
285 subject.length > 0xFF &&
286 IS_STRING(replace) &&
287 %StringIndexOf(replace, '$', 0) < 0) {
288 // Searching by traversing a cons string tree and replace with cons of
289 // slices works only when the replaced string is a single character, being
290 // replaced by a simple string and only pays off for long strings.
291 return %StringReplaceOneCharWithString(subject, search, replace);
293 var start = %StringIndexOf(subject, search, 0);
294 if (start < 0) return subject;
295 var end = start + search.length;
297 var result = %_SubString(subject, 0, start);
299 // Compute the string to replace with.
300 if (IS_CALLABLE(replace)) {
301 result += replace(search, start, subject);
303 reusableMatchInfo[CAPTURE0] = start;
304 reusableMatchInfo[CAPTURE1] = end;
305 result = ExpandReplacement(TO_STRING(replace),
311 return result + %_SubString(subject, end, subject.length);
315 // Expand the $-expressions in the string and return a new string with
317 function ExpandReplacement(string, subject, matchInfo, result) {
318 var length = string.length;
319 var next = %StringIndexOf(string, '$', 0);
321 if (length > 0) result += string;
325 if (next > 0) result += %_SubString(string, 0, next);
329 var position = next + 1;
330 if (position < length) {
331 var peek = %_StringCharCodeAt(string, position);
332 if (peek == 36) { // $$
335 } else if (peek == 38) { // $& - match
338 %_SubString(subject, matchInfo[CAPTURE0], matchInfo[CAPTURE1]);
339 } else if (peek == 96) { // $` - prefix
341 result += %_SubString(subject, 0, matchInfo[CAPTURE0]);
342 } else if (peek == 39) { // $' - suffix
344 result += %_SubString(subject, matchInfo[CAPTURE1], subject.length);
345 } else if (peek >= 48 && peek <= 57) {
346 // Valid indices are $1 .. $9, $01 .. $09 and $10 .. $99
347 var scaled_index = (peek - 48) << 1;
349 var number_of_captures = NUMBER_OF_CAPTURES(matchInfo);
350 if (position + 1 < string.length) {
351 var next = %_StringCharCodeAt(string, position + 1);
352 if (next >= 48 && next <= 57) {
353 var new_scaled_index = scaled_index * 10 + ((next - 48) << 1);
354 if (new_scaled_index < number_of_captures) {
355 scaled_index = new_scaled_index;
360 if (scaled_index != 0 && scaled_index < number_of_captures) {
361 var start = matchInfo[CAPTURE(scaled_index)];
364 %_SubString(subject, start, matchInfo[CAPTURE(scaled_index + 1)]);
377 // Go the the next $ in the string.
378 next = %StringIndexOf(string, '$', position);
380 // Return if there are no more $ characters in the string. If we
381 // haven't reached the end, we need to append the suffix.
383 if (position < length) {
384 result += %_SubString(string, position, length);
389 // Append substring between the previous and the next $ character.
390 if (next > position) {
391 result += %_SubString(string, position, next);
398 // Compute the string of a given regular expression capture.
399 function CaptureString(string, lastCaptureInfo, index) {
401 var scaled = index << 1;
402 // Compute start and end.
403 var start = lastCaptureInfo[CAPTURE(scaled)];
404 // If start isn't valid, return undefined.
405 if (start < 0) return;
406 var end = lastCaptureInfo[CAPTURE(scaled + 1)];
407 return %_SubString(string, start, end);
411 // TODO(lrn): This array will survive indefinitely if replace is never
412 // called again. However, it will be empty, since the contents are cleared
413 // in the finally block.
414 var reusableReplaceArray = new InternalArray(4);
416 // Helper function for replacing regular expressions with the result of a
417 // function application in String.prototype.replace.
418 function StringReplaceGlobalRegExpWithFunction(subject, regexp, replace) {
419 var resultArray = reusableReplaceArray;
421 reusableReplaceArray = null;
423 // Inside a nested replace (replace called from the replacement function
424 // of another replace) or we have failed to set the reusable array
425 // back due to an exception in a replacement function. Create a new
426 // array to use in the future, or until the original is written back.
427 resultArray = new InternalArray(16);
429 var res = %RegExpExecMultiple(regexp,
433 regexp.lastIndex = 0;
435 // No matches at all.
436 reusableReplaceArray = resultArray;
439 var len = res.length;
440 if (NUMBER_OF_CAPTURES(RegExpLastMatchInfo) == 2) {
441 // If the number of captures is two then there are no explicit captures in
442 // the regexp, just the implicit capture that captures the whole match. In
443 // this case we can simplify quite a bit and end up with something faster.
444 // The builder will consist of some integers that indicate slices of the
445 // input string and some replacements that were returned from the replace
448 var override = new InternalPackedArray(null, 0, subject);
449 for (var i = 0; i < len; i++) {
452 // Integers represent slices of the original string. Use these to
453 // get the offsets we need for the override array (so things like
454 // RegExp.leftContext work during the callback function.
456 match_start = (elem >> 11) + (elem & 0x7ff);
458 match_start = res[++i] - elem;
462 override[1] = match_start;
463 $regexpLastMatchInfoOverride = override;
464 var func_result = replace(elem, match_start, subject);
465 // Overwrite the i'th element in the results with the string we got
466 // back from the callback function.
467 res[i] = TO_STRING(func_result);
468 match_start += elem.length;
472 for (var i = 0; i < len; i++) {
474 if (!%_IsSmi(elem)) {
475 // elem must be an Array.
476 // Use the apply argument as backing for global RegExp properties.
477 $regexpLastMatchInfoOverride = elem;
478 var func_result = %Apply(replace, UNDEFINED, elem, 0, elem.length);
479 // Overwrite the i'th element in the results with the string we got
480 // back from the callback function.
481 res[i] = TO_STRING(func_result);
485 var result = %StringBuilderConcat(res, res.length, subject);
486 resultArray.length = 0;
487 reusableReplaceArray = resultArray;
492 function StringReplaceNonGlobalRegExpWithFunction(subject, regexp, replace) {
493 var matchInfo = RegExpExec(regexp, subject, 0);
494 if (IS_NULL(matchInfo)) {
495 regexp.lastIndex = 0;
498 var index = matchInfo[CAPTURE0];
499 var result = %_SubString(subject, 0, index);
500 var endOfMatch = matchInfo[CAPTURE1];
501 // Compute the parameter list consisting of the match, captures, index,
502 // and subject for the replace function invocation.
503 // The number of captures plus one for the match.
504 var m = NUMBER_OF_CAPTURES(matchInfo) >> 1;
507 // No captures, only the match, which is always valid.
508 var s = %_SubString(subject, index, endOfMatch);
509 // Don't call directly to avoid exposing the built-in global object.
510 replacement = replace(s, index, subject);
512 var parameters = new InternalArray(m + 2);
513 for (var j = 0; j < m; j++) {
514 parameters[j] = CaptureString(subject, matchInfo, j);
516 parameters[j] = index;
517 parameters[j + 1] = subject;
519 replacement = %Apply(replace, UNDEFINED, parameters, 0, j + 2);
522 result += replacement; // The add method converts to string if necessary.
523 // Can't use matchInfo any more from here, since the function could
525 return result + %_SubString(subject, endOfMatch, subject.length);
529 // ECMA-262 section 15.5.4.12
530 function StringSearch(re) {
531 CHECK_OBJECT_COERCIBLE(this, "String.prototype.search");
537 regexp = new GlobalRegExp(re);
539 var match = RegExpExec(regexp, TO_STRING(this), 0);
541 return match[CAPTURE0];
547 // ECMA-262 section 15.5.4.13
548 function StringSlice(start, end) {
549 CHECK_OBJECT_COERCIBLE(this, "String.prototype.slice");
551 var s = TO_STRING(this);
552 var s_len = s.length;
553 var start_i = TO_INTEGER(start);
555 if (!IS_UNDEFINED(end)) {
556 end_i = TO_INTEGER(end);
565 if (start_i > s_len) {
581 if (end_i <= start_i) {
585 return %_SubString(s, start_i, end_i);
589 // ECMA-262 section 15.5.4.14
590 function StringSplitJS(separator, limit) {
591 CHECK_OBJECT_COERCIBLE(this, "String.prototype.split");
593 var subject = TO_STRING(this);
594 limit = (IS_UNDEFINED(limit)) ? 0xffffffff : TO_UINT32(limit);
596 var length = subject.length;
597 if (!IS_REGEXP(separator)) {
598 var separator_string = TO_STRING(separator);
600 if (limit === 0) return [];
602 // ECMA-262 says that if separator is undefined, the result should
603 // be an array of size 1 containing the entire string.
604 if (IS_UNDEFINED(separator)) return [subject];
606 var separator_length = separator_string.length;
608 // If the separator string is empty then return the elements in the subject.
609 if (separator_length === 0) return %StringToArray(subject, limit);
611 var result = %StringSplit(subject, separator_string, limit);
616 if (limit === 0) return [];
618 // Separator is a regular expression.
619 return StringSplitOnRegExp(subject, separator, limit, length);
623 function StringSplitOnRegExp(subject, separator, limit, length) {
625 if (RegExpExec(separator, subject, 0, 0) != null) {
631 var currentIndex = 0;
634 var result = new InternalArray();
639 if (startIndex === length) {
640 result[result.length] = %_SubString(subject, currentIndex, length);
644 var matchInfo = RegExpExec(separator, subject, startIndex);
645 if (matchInfo == null || length === (startMatch = matchInfo[CAPTURE0])) {
646 result[result.length] = %_SubString(subject, currentIndex, length);
649 var endIndex = matchInfo[CAPTURE1];
651 // We ignore a zero-length match at the currentIndex.
652 if (startIndex === endIndex && endIndex === currentIndex) {
657 result[result.length] = %_SubString(subject, currentIndex, startMatch);
659 if (result.length === limit) break;
661 var matchinfo_len = NUMBER_OF_CAPTURES(matchInfo) + REGEXP_FIRST_CAPTURE;
662 for (var i = REGEXP_FIRST_CAPTURE + 2; i < matchinfo_len; ) {
663 var start = matchInfo[i++];
664 var end = matchInfo[i++];
666 result[result.length] = %_SubString(subject, start, end);
668 result[result.length] = UNDEFINED;
670 if (result.length === limit) break outer_loop;
673 startIndex = currentIndex = endIndex;
675 var array_result = [];
676 %MoveArrayContents(result, array_result);
681 // ECMA-262 section 15.5.4.15
682 function StringSubstring(start, end) {
683 CHECK_OBJECT_COERCIBLE(this, "String.prototype.subString");
685 var s = TO_STRING(this);
686 var s_len = s.length;
688 var start_i = TO_INTEGER(start);
691 } else if (start_i > s_len) {
696 if (!IS_UNDEFINED(end)) {
697 end_i = TO_INTEGER(end);
701 if (end_i < 0) end_i = 0;
702 if (start_i > end_i) {
710 return %_SubString(s, start_i, end_i);
714 // ES6 draft, revision 26 (2014-07-18), section B.2.3.1
715 function StringSubstr(start, n) {
716 CHECK_OBJECT_COERCIBLE(this, "String.prototype.substr");
718 var s = TO_STRING(this);
721 // Correct n: If not given, set to string length; if explicitly
722 // set to undefined, zero, or negative, returns empty string.
723 if (IS_UNDEFINED(n)) {
727 if (len <= 0) return '';
730 // Correct start: If not given (or undefined), set to zero; otherwise
731 // convert to integer and handle negative case.
732 if (IS_UNDEFINED(start)) {
735 start = TO_INTEGER(start);
736 // If positive, and greater than or equal to the string length,
737 // return empty string.
738 if (start >= s.length) return '';
739 // If negative and absolute value is larger than the string length,
743 if (start < 0) start = 0;
747 var end = start + len;
748 if (end > s.length) end = s.length;
750 return %_SubString(s, start, end);
754 // ECMA-262, 15.5.4.16
755 function StringToLowerCaseJS() {
756 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLowerCase");
758 return %StringToLowerCase(TO_STRING(this));
762 // ECMA-262, 15.5.4.17
763 function StringToLocaleLowerCase() {
764 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLocaleLowerCase");
766 return %StringToLowerCase(TO_STRING(this));
770 // ECMA-262, 15.5.4.18
771 function StringToUpperCaseJS() {
772 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toUpperCase");
774 return %StringToUpperCase(TO_STRING(this));
778 // ECMA-262, 15.5.4.19
779 function StringToLocaleUpperCase() {
780 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLocaleUpperCase");
782 return %StringToUpperCase(TO_STRING(this));
786 function StringTrimJS() {
787 CHECK_OBJECT_COERCIBLE(this, "String.prototype.trim");
789 return %StringTrim(TO_STRING(this), true, true);
792 function StringTrimLeft() {
793 CHECK_OBJECT_COERCIBLE(this, "String.prototype.trimLeft");
795 return %StringTrim(TO_STRING(this), true, false);
798 function StringTrimRight() {
799 CHECK_OBJECT_COERCIBLE(this, "String.prototype.trimRight");
801 return %StringTrim(TO_STRING(this), false, true);
805 // ECMA-262, section 15.5.3.2
806 function StringFromCharCode(code) {
807 var n = %_ArgumentsLength();
809 if (!%_IsSmi(code)) code = ToNumber(code);
810 return %_StringCharFromCode(code & 0xffff);
813 var one_byte = %NewString(n, NEW_ONE_BYTE_STRING);
815 for (i = 0; i < n; i++) {
816 var code = %_Arguments(i);
817 if (!%_IsSmi(code)) code = ToNumber(code) & 0xffff;
818 if (code < 0) code = code & 0xffff;
819 if (code > 0xff) break;
820 %_OneByteSeqStringSetChar(i, code, one_byte);
822 if (i == n) return one_byte;
823 one_byte = %TruncateString(one_byte, i);
825 var two_byte = %NewString(n - i, NEW_TWO_BYTE_STRING);
826 for (var j = 0; i < n; i++, j++) {
827 var code = %_Arguments(i);
828 if (!%_IsSmi(code)) code = ToNumber(code) & 0xffff;
829 %_TwoByteSeqStringSetChar(j, code, two_byte);
831 return one_byte + two_byte;
835 // ES6 draft, revision 26 (2014-07-18), section B.2.3.2.1
836 function HtmlEscape(str) {
837 return %_CallFunction(TO_STRING(str), /"/g, """, StringReplace);
841 // ES6 draft, revision 26 (2014-07-18), section B.2.3.2
842 function StringAnchor(name) {
843 CHECK_OBJECT_COERCIBLE(this, "String.prototype.anchor");
844 return "<a name=\"" + HtmlEscape(name) + "\">" + TO_STRING(this) +
849 // ES6 draft, revision 26 (2014-07-18), section B.2.3.3
850 function StringBig() {
851 CHECK_OBJECT_COERCIBLE(this, "String.prototype.big");
852 return "<big>" + TO_STRING(this) + "</big>";
856 // ES6 draft, revision 26 (2014-07-18), section B.2.3.4
857 function StringBlink() {
858 CHECK_OBJECT_COERCIBLE(this, "String.prototype.blink");
859 return "<blink>" + TO_STRING(this) + "</blink>";
863 // ES6 draft, revision 26 (2014-07-18), section B.2.3.5
864 function StringBold() {
865 CHECK_OBJECT_COERCIBLE(this, "String.prototype.bold");
866 return "<b>" + TO_STRING(this) + "</b>";
870 // ES6 draft, revision 26 (2014-07-18), section B.2.3.6
871 function StringFixed() {
872 CHECK_OBJECT_COERCIBLE(this, "String.prototype.fixed");
873 return "<tt>" + TO_STRING(this) + "</tt>";
877 // ES6 draft, revision 26 (2014-07-18), section B.2.3.7
878 function StringFontcolor(color) {
879 CHECK_OBJECT_COERCIBLE(this, "String.prototype.fontcolor");
880 return "<font color=\"" + HtmlEscape(color) + "\">" + TO_STRING(this) +
885 // ES6 draft, revision 26 (2014-07-18), section B.2.3.8
886 function StringFontsize(size) {
887 CHECK_OBJECT_COERCIBLE(this, "String.prototype.fontsize");
888 return "<font size=\"" + HtmlEscape(size) + "\">" + TO_STRING(this) +
893 // ES6 draft, revision 26 (2014-07-18), section B.2.3.9
894 function StringItalics() {
895 CHECK_OBJECT_COERCIBLE(this, "String.prototype.italics");
896 return "<i>" + TO_STRING(this) + "</i>";
900 // ES6 draft, revision 26 (2014-07-18), section B.2.3.10
901 function StringLink(s) {
902 CHECK_OBJECT_COERCIBLE(this, "String.prototype.link");
903 return "<a href=\"" + HtmlEscape(s) + "\">" + TO_STRING(this) + "</a>";
907 // ES6 draft, revision 26 (2014-07-18), section B.2.3.11
908 function StringSmall() {
909 CHECK_OBJECT_COERCIBLE(this, "String.prototype.small");
910 return "<small>" + TO_STRING(this) + "</small>";
914 // ES6 draft, revision 26 (2014-07-18), section B.2.3.12
915 function StringStrike() {
916 CHECK_OBJECT_COERCIBLE(this, "String.prototype.strike");
917 return "<strike>" + TO_STRING(this) + "</strike>";
921 // ES6 draft, revision 26 (2014-07-18), section B.2.3.13
922 function StringSub() {
923 CHECK_OBJECT_COERCIBLE(this, "String.prototype.sub");
924 return "<sub>" + TO_STRING(this) + "</sub>";
928 // ES6 draft, revision 26 (2014-07-18), section B.2.3.14
929 function StringSup() {
930 CHECK_OBJECT_COERCIBLE(this, "String.prototype.sup");
931 return "<sup>" + TO_STRING(this) + "</sup>";
934 // ES6 draft 01-20-14, section 21.1.3.13
935 function StringRepeat(count) {
936 CHECK_OBJECT_COERCIBLE(this, "String.prototype.repeat");
938 var s = TO_STRING(this);
939 var n = $toInteger(count);
940 // The maximum string length is stored in a smi, so a longer repeat
941 // must result in a range error.
942 if (n < 0 || n > %_MaxSmi()) throw MakeRangeError(kInvalidCountValue);
948 if (n === 0) return r;
954 // ES6 draft 04-05-14, section 21.1.3.18
955 function StringStartsWith(searchString /* position */) { // length == 1
956 CHECK_OBJECT_COERCIBLE(this, "String.prototype.startsWith");
958 var s = TO_STRING(this);
960 if (IS_REGEXP(searchString)) {
961 throw MakeTypeError(kFirstArgumentNotRegExp, "String.prototype.startsWith");
964 var ss = TO_STRING(searchString);
966 if (%_ArgumentsLength() > 1) {
967 var arg = %_Arguments(1); // position
968 if (!IS_UNDEFINED(arg)) {
969 pos = $toInteger(arg);
973 var s_len = s.length;
974 if (pos < 0) pos = 0;
975 if (pos > s_len) pos = s_len;
976 var ss_len = ss.length;
978 if (ss_len + pos > s_len) {
982 for (var i = 0; i < ss_len; i++) {
983 if (%_StringCharCodeAt(s, pos + i) !== %_StringCharCodeAt(ss, i)) {
992 // ES6 draft 04-05-14, section 21.1.3.7
993 function StringEndsWith(searchString /* position */) { // length == 1
994 CHECK_OBJECT_COERCIBLE(this, "String.prototype.endsWith");
996 var s = TO_STRING(this);
998 if (IS_REGEXP(searchString)) {
999 throw MakeTypeError(kFirstArgumentNotRegExp, "String.prototype.endsWith");
1002 var ss = TO_STRING(searchString);
1003 var s_len = s.length;
1005 if (%_ArgumentsLength() > 1) {
1006 var arg = %_Arguments(1); // position
1007 if (!IS_UNDEFINED(arg)) {
1008 pos = $toInteger(arg);
1012 if (pos < 0) pos = 0;
1013 if (pos > s_len) pos = s_len;
1014 var ss_len = ss.length;
1021 for (var i = 0; i < ss_len; i++) {
1022 if (%_StringCharCodeAt(s, pos + i) !== %_StringCharCodeAt(ss, i)) {
1031 // ES6 draft 04-05-14, section 21.1.3.6
1032 function StringIncludes(searchString /* position */) { // length == 1
1033 CHECK_OBJECT_COERCIBLE(this, "String.prototype.includes");
1035 var string = TO_STRING(this);
1037 if (IS_REGEXP(searchString)) {
1038 throw MakeTypeError(kFirstArgumentNotRegExp, "String.prototype.includes");
1041 searchString = TO_STRING(searchString);
1043 if (%_ArgumentsLength() > 1) {
1044 pos = %_Arguments(1); // position
1045 pos = TO_INTEGER(pos);
1048 var stringLength = string.length;
1049 if (pos < 0) pos = 0;
1050 if (pos > stringLength) pos = stringLength;
1051 var searchStringLength = searchString.length;
1053 if (searchStringLength + pos > stringLength) {
1057 return %StringIndexOf(string, searchString, pos) !== -1;
1061 // ES6 Draft 05-22-2014, section 21.1.3.3
1062 function StringCodePointAt(pos) {
1063 CHECK_OBJECT_COERCIBLE(this, "String.prototype.codePointAt");
1065 var string = TO_STRING(this);
1066 var size = string.length;
1067 pos = TO_INTEGER(pos);
1068 if (pos < 0 || pos >= size) {
1071 var first = %_StringCharCodeAt(string, pos);
1072 if (first < 0xD800 || first > 0xDBFF || pos + 1 == size) {
1075 var second = %_StringCharCodeAt(string, pos + 1);
1076 if (second < 0xDC00 || second > 0xDFFF) {
1079 return (first - 0xD800) * 0x400 + second + 0x2400;
1083 // ES6 Draft 05-22-2014, section 21.1.2.2
1084 function StringFromCodePoint(_) { // length = 1
1086 var length = %_ArgumentsLength();
1089 for (index = 0; index < length; index++) {
1090 code = %_Arguments(index);
1091 if (!%_IsSmi(code)) {
1092 code = ToNumber(code);
1094 if (code < 0 || code > 0x10FFFF || code !== TO_INTEGER(code)) {
1095 throw MakeRangeError(kInvalidCodePoint, code);
1097 if (code <= 0xFFFF) {
1098 result += %_StringCharFromCode(code);
1101 result += %_StringCharFromCode((code >>> 10) & 0x3FF | 0xD800);
1102 result += %_StringCharFromCode(code & 0x3FF | 0xDC00);
1109 // -------------------------------------------------------------------
1110 // String methods related to templates
1112 // ES6 Draft 03-17-2015, section 21.1.2.4
1113 function StringRaw(callSite) {
1114 // TODO(caitp): Use rest parameters when implemented
1115 var numberOfSubstitutions = %_ArgumentsLength();
1116 var cooked = TO_OBJECT(callSite);
1117 var raw = TO_OBJECT(cooked.raw);
1118 var literalSegments = $toLength(raw.length);
1119 if (literalSegments <= 0) return "";
1121 var result = TO_STRING(raw[0]);
1123 for (var i = 1; i < literalSegments; ++i) {
1124 if (i < numberOfSubstitutions) {
1125 result += TO_STRING(%_Arguments(i));
1127 result += TO_STRING(raw[i]);
1133 // -------------------------------------------------------------------
1135 // Set the String function and constructor.
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.StringSlice = StringSlice;
1205 to.StringSplit = StringSplitJS;
1206 to.StringSubstr = StringSubstr;
1207 to.StringSubstring = StringSubstring;