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;
25 utils.Import(function(from) {
26 ArrayIndexOf = from.ArrayIndexOf;
27 ArrayJoin = from.ArrayJoin;
28 RegExpExec = from.RegExpExec;
29 RegExpExecNoTests = from.RegExpExecNoTests;
30 RegExpLastMatchInfo = from.RegExpLastMatchInfo;
31 SymbolToString = from.SymbolToString;
32 ToNumber = from.ToNumber;
33 ToString = from.ToString;
36 //-------------------------------------------------------------------
38 function StringConstructor(x) {
39 // TODO(bmeurer): Move this to toplevel.
41 if (%_ArgumentsLength() == 0) x = '';
42 if (%_IsConstructCall()) {
43 %_SetValueOf(this, TO_STRING_INLINE(x));
46 %_CallFunction(x, SymbolToString) : TO_STRING_INLINE(x);
51 // ECMA-262 section 15.5.4.2
52 function StringToString() {
53 if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) {
54 throw MakeTypeError(kNotGeneric, 'String.prototype.toString');
56 return %_ValueOf(this);
60 // ECMA-262 section 15.5.4.3
61 function StringValueOf() {
62 if (!IS_STRING(this) && !IS_STRING_WRAPPER(this)) {
63 throw MakeTypeError(kNotGeneric, 'String.prototype.valueOf');
65 return %_ValueOf(this);
69 // ECMA-262, section 15.5.4.4
70 function StringCharAtJS(pos) {
71 CHECK_OBJECT_COERCIBLE(this, "String.prototype.charAt");
73 var result = %_StringCharAt(this, pos);
74 if (%_IsSmi(result)) {
75 result = %_StringCharAt(TO_STRING_INLINE(this), TO_INTEGER(pos));
81 // ECMA-262 section 15.5.4.5
82 function StringCharCodeAtJS(pos) {
83 CHECK_OBJECT_COERCIBLE(this, "String.prototype.charCodeAt");
85 var result = %_StringCharCodeAt(this, pos);
86 if (!%_IsSmi(result)) {
87 result = %_StringCharCodeAt(TO_STRING_INLINE(this), TO_INTEGER(pos));
93 // ECMA-262, section 15.5.4.6
94 function StringConcat(other /* and more */) { // length == 1
95 CHECK_OBJECT_COERCIBLE(this, "String.prototype.concat");
96 var len = %_ArgumentsLength();
97 var this_as_string = TO_STRING_INLINE(this);
99 return this_as_string + TO_STRING_INLINE(other);
101 var parts = new InternalArray(len + 1);
102 parts[0] = this_as_string;
103 for (var i = 0; i < len; i++) {
104 var part = %_Arguments(i);
105 parts[i + 1] = TO_STRING_INLINE(part);
107 return %StringBuilderConcat(parts, len + 1, "");
111 // ECMA-262 section 15.5.4.7
112 function StringIndexOfJS(pattern /* position */) { // length == 1
113 CHECK_OBJECT_COERCIBLE(this, "String.prototype.indexOf");
115 var subject = TO_STRING_INLINE(this);
116 pattern = TO_STRING_INLINE(pattern);
118 if (%_ArgumentsLength() > 1) {
119 index = %_Arguments(1); // position
120 index = TO_INTEGER(index);
121 if (index < 0) index = 0;
122 if (index > subject.length) index = subject.length;
124 return %StringIndexOf(subject, pattern, index);
128 // ECMA-262 section 15.5.4.8
129 function StringLastIndexOfJS(pat /* position */) { // length == 1
130 CHECK_OBJECT_COERCIBLE(this, "String.prototype.lastIndexOf");
132 var sub = TO_STRING_INLINE(this);
133 var subLength = sub.length;
134 var pat = TO_STRING_INLINE(pat);
135 var patLength = pat.length;
136 var index = subLength - patLength;
137 if (%_ArgumentsLength() > 1) {
138 var position = ToNumber(%_Arguments(1));
139 if (!NUMBER_IS_NAN(position)) {
140 position = TO_INTEGER(position);
144 if (position + patLength < subLength) {
152 return %StringLastIndexOf(sub, pat, index);
156 // ECMA-262 section 15.5.4.9
158 // This function is implementation specific. For now, we do not
159 // do anything locale specific.
160 function StringLocaleCompareJS(other) {
161 CHECK_OBJECT_COERCIBLE(this, "String.prototype.localeCompare");
163 return %StringLocaleCompare(TO_STRING_INLINE(this),
164 TO_STRING_INLINE(other));
168 // ECMA-262 section 15.5.4.10
169 function StringMatchJS(regexp) {
170 CHECK_OBJECT_COERCIBLE(this, "String.prototype.match");
172 var subject = TO_STRING_INLINE(this);
173 if (IS_REGEXP(regexp)) {
174 // Emulate RegExp.prototype.exec's side effect in step 5, even though
175 // value is discarded.
176 var lastIndex = regexp.lastIndex;
177 TO_INTEGER_FOR_SIDE_EFFECT(lastIndex);
178 if (!regexp.global) return RegExpExecNoTests(regexp, subject, 0);
179 var result = %StringMatch(subject, regexp, RegExpLastMatchInfo);
180 if (result !== null) $regexpLastMatchInfoOverride = null;
181 regexp.lastIndex = 0;
184 // Non-regexp argument.
185 regexp = new GlobalRegExp(regexp);
186 return RegExpExecNoTests(regexp, subject, 0);
190 // ECMA-262 v6, section 21.1.3.12
192 // For now we do nothing, as proper normalization requires big tables.
193 // If Intl is enabled, then i18n.js will override it and provide the the
194 // proper functionality.
195 function StringNormalizeJS() {
196 CHECK_OBJECT_COERCIBLE(this, "String.prototype.normalize");
197 var s = TO_STRING_INLINE(this);
199 var formArg = %_Arguments(0);
200 var form = IS_UNDEFINED(formArg) ? 'NFC' : TO_STRING_INLINE(formArg);
202 var NORMALIZATION_FORMS = ['NFC', 'NFD', 'NFKC', 'NFKD'];
203 var normalizationForm =
204 %_CallFunction(NORMALIZATION_FORMS, form, ArrayIndexOf);
205 if (normalizationForm === -1) {
206 throw MakeRangeError(kNormalizationForm,
207 %_CallFunction(NORMALIZATION_FORMS, ', ', ArrayJoin));
214 // This has the same size as the RegExpLastMatchInfo array, and can be used
215 // for functions that expect that structure to be returned. It is used when
216 // the needle is a string rather than a regexp. In this case we can't update
217 // lastMatchArray without erroneously affecting the properties on the global
219 var reusableMatchInfo = [2, "", "", -1, -1];
222 // ECMA-262, section 15.5.4.11
223 function StringReplace(search, replace) {
224 CHECK_OBJECT_COERCIBLE(this, "String.prototype.replace");
226 var subject = TO_STRING_INLINE(this);
228 // Decision tree for dispatch
230 // .... string replace
231 // ...... non-global search
232 // ........ empty string replace
233 // ........ non-empty string replace (with $-expansion)
234 // ...... global search
235 // ........ no need to circumvent last match info override
236 // ........ need to circument last match info override
237 // .... function replace
238 // ...... global search
239 // ...... non-global search
241 // .... special case that replaces with one single character
242 // ...... function replace
243 // ...... string replace (with $-expansion)
245 if (IS_REGEXP(search)) {
246 // Emulate RegExp.prototype.exec's side effect in step 5, even if
247 // value is discarded.
248 var lastIndex = search.lastIndex;
249 TO_INTEGER_FOR_SIDE_EFFECT(lastIndex);
251 if (!IS_CALLABLE(replace)) {
252 replace = TO_STRING_INLINE(replace);
254 if (!search.global) {
255 // Non-global regexp search, string replace.
256 var match = RegExpExec(search, subject, 0);
261 if (replace.length == 0) {
262 return %_SubString(subject, 0, match[CAPTURE0]) +
263 %_SubString(subject, match[CAPTURE1], subject.length)
265 return ExpandReplacement(replace, subject, RegExpLastMatchInfo,
266 %_SubString(subject, 0, match[CAPTURE0])) +
267 %_SubString(subject, match[CAPTURE1], subject.length);
270 // Global regexp search, string replace.
271 search.lastIndex = 0;
272 if ($regexpLastMatchInfoOverride == null) {
273 return %StringReplaceGlobalRegExpWithString(
274 subject, search, replace, RegExpLastMatchInfo);
276 // We use this hack to detect whether StringReplaceRegExpWithString
277 // found at least one hit. In that case we need to remove any
279 var saved_subject = RegExpLastMatchInfo[LAST_SUBJECT_INDEX];
280 RegExpLastMatchInfo[LAST_SUBJECT_INDEX] = 0;
281 var answer = %StringReplaceGlobalRegExpWithString(
282 subject, search, replace, RegExpLastMatchInfo);
283 if (%_IsSmi(RegExpLastMatchInfo[LAST_SUBJECT_INDEX])) {
284 RegExpLastMatchInfo[LAST_SUBJECT_INDEX] = saved_subject;
286 $regexpLastMatchInfoOverride = null;
293 // Global regexp search, function replace.
294 return StringReplaceGlobalRegExpWithFunction(subject, search, replace);
296 // Non-global regexp search, function replace.
297 return StringReplaceNonGlobalRegExpWithFunction(subject, search, replace);
300 search = TO_STRING_INLINE(search);
302 if (search.length == 1 &&
303 subject.length > 0xFF &&
304 IS_STRING(replace) &&
305 %StringIndexOf(replace, '$', 0) < 0) {
306 // Searching by traversing a cons string tree and replace with cons of
307 // slices works only when the replaced string is a single character, being
308 // replaced by a simple string and only pays off for long strings.
309 return %StringReplaceOneCharWithString(subject, search, replace);
311 var start = %StringIndexOf(subject, search, 0);
312 if (start < 0) return subject;
313 var end = start + search.length;
315 var result = %_SubString(subject, 0, start);
317 // Compute the string to replace with.
318 if (IS_CALLABLE(replace)) {
319 result += replace(search, start, subject);
321 reusableMatchInfo[CAPTURE0] = start;
322 reusableMatchInfo[CAPTURE1] = end;
323 result = ExpandReplacement(TO_STRING_INLINE(replace),
329 return result + %_SubString(subject, end, subject.length);
333 // Expand the $-expressions in the string and return a new string with
335 function ExpandReplacement(string, subject, matchInfo, result) {
336 var length = string.length;
337 var next = %StringIndexOf(string, '$', 0);
339 if (length > 0) result += string;
343 if (next > 0) result += %_SubString(string, 0, next);
347 var position = next + 1;
348 if (position < length) {
349 var peek = %_StringCharCodeAt(string, position);
350 if (peek == 36) { // $$
353 } else if (peek == 38) { // $& - match
356 %_SubString(subject, matchInfo[CAPTURE0], matchInfo[CAPTURE1]);
357 } else if (peek == 96) { // $` - prefix
359 result += %_SubString(subject, 0, matchInfo[CAPTURE0]);
360 } else if (peek == 39) { // $' - suffix
362 result += %_SubString(subject, matchInfo[CAPTURE1], subject.length);
363 } else if (peek >= 48 && peek <= 57) {
364 // Valid indices are $1 .. $9, $01 .. $09 and $10 .. $99
365 var scaled_index = (peek - 48) << 1;
367 var number_of_captures = NUMBER_OF_CAPTURES(matchInfo);
368 if (position + 1 < string.length) {
369 var next = %_StringCharCodeAt(string, position + 1);
370 if (next >= 48 && next <= 57) {
371 var new_scaled_index = scaled_index * 10 + ((next - 48) << 1);
372 if (new_scaled_index < number_of_captures) {
373 scaled_index = new_scaled_index;
378 if (scaled_index != 0 && scaled_index < number_of_captures) {
379 var start = matchInfo[CAPTURE(scaled_index)];
382 %_SubString(subject, start, matchInfo[CAPTURE(scaled_index + 1)]);
395 // Go the the next $ in the string.
396 next = %StringIndexOf(string, '$', position);
398 // Return if there are no more $ characters in the string. If we
399 // haven't reached the end, we need to append the suffix.
401 if (position < length) {
402 result += %_SubString(string, position, length);
407 // Append substring between the previous and the next $ character.
408 if (next > position) {
409 result += %_SubString(string, position, next);
416 // Compute the string of a given regular expression capture.
417 function CaptureString(string, lastCaptureInfo, index) {
419 var scaled = index << 1;
420 // Compute start and end.
421 var start = lastCaptureInfo[CAPTURE(scaled)];
422 // If start isn't valid, return undefined.
423 if (start < 0) return;
424 var end = lastCaptureInfo[CAPTURE(scaled + 1)];
425 return %_SubString(string, start, end);
429 // TODO(lrn): This array will survive indefinitely if replace is never
430 // called again. However, it will be empty, since the contents are cleared
431 // in the finally block.
432 var reusableReplaceArray = new InternalArray(4);
434 // Helper function for replacing regular expressions with the result of a
435 // function application in String.prototype.replace.
436 function StringReplaceGlobalRegExpWithFunction(subject, regexp, replace) {
437 var resultArray = reusableReplaceArray;
439 reusableReplaceArray = null;
441 // Inside a nested replace (replace called from the replacement function
442 // of another replace) or we have failed to set the reusable array
443 // back due to an exception in a replacement function. Create a new
444 // array to use in the future, or until the original is written back.
445 resultArray = new InternalArray(16);
447 var res = %RegExpExecMultiple(regexp,
451 regexp.lastIndex = 0;
453 // No matches at all.
454 reusableReplaceArray = resultArray;
457 var len = res.length;
458 if (NUMBER_OF_CAPTURES(RegExpLastMatchInfo) == 2) {
459 // If the number of captures is two then there are no explicit captures in
460 // the regexp, just the implicit capture that captures the whole match. In
461 // this case we can simplify quite a bit and end up with something faster.
462 // The builder will consist of some integers that indicate slices of the
463 // input string and some replacements that were returned from the replace
466 var override = new InternalPackedArray(null, 0, subject);
467 for (var i = 0; i < len; i++) {
470 // Integers represent slices of the original string. Use these to
471 // get the offsets we need for the override array (so things like
472 // RegExp.leftContext work during the callback function.
474 match_start = (elem >> 11) + (elem & 0x7ff);
476 match_start = res[++i] - elem;
480 override[1] = match_start;
481 $regexpLastMatchInfoOverride = override;
482 var func_result = replace(elem, match_start, subject);
483 // Overwrite the i'th element in the results with the string we got
484 // back from the callback function.
485 res[i] = TO_STRING_INLINE(func_result);
486 match_start += elem.length;
490 for (var i = 0; i < len; i++) {
492 if (!%_IsSmi(elem)) {
493 // elem must be an Array.
494 // Use the apply argument as backing for global RegExp properties.
495 $regexpLastMatchInfoOverride = elem;
496 var func_result = %Apply(replace, UNDEFINED, elem, 0, elem.length);
497 // Overwrite the i'th element in the results with the string we got
498 // back from the callback function.
499 res[i] = TO_STRING_INLINE(func_result);
503 var result = %StringBuilderConcat(res, res.length, subject);
504 resultArray.length = 0;
505 reusableReplaceArray = resultArray;
510 function StringReplaceNonGlobalRegExpWithFunction(subject, regexp, replace) {
511 var matchInfo = RegExpExec(regexp, subject, 0);
512 if (IS_NULL(matchInfo)) {
513 regexp.lastIndex = 0;
516 var index = matchInfo[CAPTURE0];
517 var result = %_SubString(subject, 0, index);
518 var endOfMatch = matchInfo[CAPTURE1];
519 // Compute the parameter list consisting of the match, captures, index,
520 // and subject for the replace function invocation.
521 // The number of captures plus one for the match.
522 var m = NUMBER_OF_CAPTURES(matchInfo) >> 1;
525 // No captures, only the match, which is always valid.
526 var s = %_SubString(subject, index, endOfMatch);
527 // Don't call directly to avoid exposing the built-in global object.
528 replacement = replace(s, index, subject);
530 var parameters = new InternalArray(m + 2);
531 for (var j = 0; j < m; j++) {
532 parameters[j] = CaptureString(subject, matchInfo, j);
534 parameters[j] = index;
535 parameters[j + 1] = subject;
537 replacement = %Apply(replace, UNDEFINED, parameters, 0, j + 2);
540 result += replacement; // The add method converts to string if necessary.
541 // Can't use matchInfo any more from here, since the function could
543 return result + %_SubString(subject, endOfMatch, subject.length);
547 // ECMA-262 section 15.5.4.12
548 function StringSearch(re) {
549 CHECK_OBJECT_COERCIBLE(this, "String.prototype.search");
555 regexp = new GlobalRegExp(re);
557 var match = RegExpExec(regexp, TO_STRING_INLINE(this), 0);
559 return match[CAPTURE0];
565 // ECMA-262 section 15.5.4.13
566 function StringSlice(start, end) {
567 CHECK_OBJECT_COERCIBLE(this, "String.prototype.slice");
569 var s = TO_STRING_INLINE(this);
570 var s_len = s.length;
571 var start_i = TO_INTEGER(start);
573 if (!IS_UNDEFINED(end)) {
574 end_i = TO_INTEGER(end);
583 if (start_i > s_len) {
599 if (end_i <= start_i) {
603 return %_SubString(s, start_i, end_i);
607 // ECMA-262 section 15.5.4.14
608 function StringSplitJS(separator, limit) {
609 CHECK_OBJECT_COERCIBLE(this, "String.prototype.split");
611 var subject = TO_STRING_INLINE(this);
612 limit = (IS_UNDEFINED(limit)) ? 0xffffffff : TO_UINT32(limit);
614 var length = subject.length;
615 if (!IS_REGEXP(separator)) {
616 var separator_string = TO_STRING_INLINE(separator);
618 if (limit === 0) return [];
620 // ECMA-262 says that if separator is undefined, the result should
621 // be an array of size 1 containing the entire string.
622 if (IS_UNDEFINED(separator)) return [subject];
624 var separator_length = separator_string.length;
626 // If the separator string is empty then return the elements in the subject.
627 if (separator_length === 0) return %StringToArray(subject, limit);
629 var result = %StringSplit(subject, separator_string, limit);
634 if (limit === 0) return [];
636 // Separator is a regular expression.
637 return StringSplitOnRegExp(subject, separator, limit, length);
641 function StringSplitOnRegExp(subject, separator, limit, length) {
643 if (RegExpExec(separator, subject, 0, 0) != null) {
649 var currentIndex = 0;
652 var result = new InternalArray();
657 if (startIndex === length) {
658 result[result.length] = %_SubString(subject, currentIndex, length);
662 var matchInfo = RegExpExec(separator, subject, startIndex);
663 if (matchInfo == null || length === (startMatch = matchInfo[CAPTURE0])) {
664 result[result.length] = %_SubString(subject, currentIndex, length);
667 var endIndex = matchInfo[CAPTURE1];
669 // We ignore a zero-length match at the currentIndex.
670 if (startIndex === endIndex && endIndex === currentIndex) {
675 result[result.length] = %_SubString(subject, currentIndex, startMatch);
677 if (result.length === limit) break;
679 var matchinfo_len = NUMBER_OF_CAPTURES(matchInfo) + REGEXP_FIRST_CAPTURE;
680 for (var i = REGEXP_FIRST_CAPTURE + 2; i < matchinfo_len; ) {
681 var start = matchInfo[i++];
682 var end = matchInfo[i++];
684 result[result.length] = %_SubString(subject, start, end);
686 result[result.length] = UNDEFINED;
688 if (result.length === limit) break outer_loop;
691 startIndex = currentIndex = endIndex;
693 var array_result = [];
694 %MoveArrayContents(result, array_result);
699 // ECMA-262 section 15.5.4.15
700 function StringSubstring(start, end) {
701 CHECK_OBJECT_COERCIBLE(this, "String.prototype.subString");
703 var s = TO_STRING_INLINE(this);
704 var s_len = s.length;
706 var start_i = TO_INTEGER(start);
709 } else if (start_i > s_len) {
714 if (!IS_UNDEFINED(end)) {
715 end_i = TO_INTEGER(end);
719 if (end_i < 0) end_i = 0;
720 if (start_i > end_i) {
728 return %_SubString(s, start_i, end_i);
732 // ES6 draft, revision 26 (2014-07-18), section B.2.3.1
733 function StringSubstr(start, n) {
734 CHECK_OBJECT_COERCIBLE(this, "String.prototype.substr");
736 var s = TO_STRING_INLINE(this);
739 // Correct n: If not given, set to string length; if explicitly
740 // set to undefined, zero, or negative, returns empty string.
741 if (IS_UNDEFINED(n)) {
745 if (len <= 0) return '';
748 // Correct start: If not given (or undefined), set to zero; otherwise
749 // convert to integer and handle negative case.
750 if (IS_UNDEFINED(start)) {
753 start = TO_INTEGER(start);
754 // If positive, and greater than or equal to the string length,
755 // return empty string.
756 if (start >= s.length) return '';
757 // If negative and absolute value is larger than the string length,
761 if (start < 0) start = 0;
765 var end = start + len;
766 if (end > s.length) end = s.length;
768 return %_SubString(s, start, end);
772 // ECMA-262, 15.5.4.16
773 function StringToLowerCaseJS() {
774 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLowerCase");
776 return %StringToLowerCase(TO_STRING_INLINE(this));
780 // ECMA-262, 15.5.4.17
781 function StringToLocaleLowerCase() {
782 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLocaleLowerCase");
784 return %StringToLowerCase(TO_STRING_INLINE(this));
788 // ECMA-262, 15.5.4.18
789 function StringToUpperCaseJS() {
790 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toUpperCase");
792 return %StringToUpperCase(TO_STRING_INLINE(this));
796 // ECMA-262, 15.5.4.19
797 function StringToLocaleUpperCase() {
798 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLocaleUpperCase");
800 return %StringToUpperCase(TO_STRING_INLINE(this));
804 function StringTrimJS() {
805 CHECK_OBJECT_COERCIBLE(this, "String.prototype.trim");
807 return %StringTrim(TO_STRING_INLINE(this), true, true);
810 function StringTrimLeft() {
811 CHECK_OBJECT_COERCIBLE(this, "String.prototype.trimLeft");
813 return %StringTrim(TO_STRING_INLINE(this), true, false);
816 function StringTrimRight() {
817 CHECK_OBJECT_COERCIBLE(this, "String.prototype.trimRight");
819 return %StringTrim(TO_STRING_INLINE(this), false, true);
823 // ECMA-262, section 15.5.3.2
824 function StringFromCharCode(code) {
825 var n = %_ArgumentsLength();
827 if (!%_IsSmi(code)) code = ToNumber(code);
828 return %_StringCharFromCode(code & 0xffff);
831 var one_byte = %NewString(n, NEW_ONE_BYTE_STRING);
833 for (i = 0; i < n; i++) {
834 var code = %_Arguments(i);
835 if (!%_IsSmi(code)) code = ToNumber(code) & 0xffff;
836 if (code < 0) code = code & 0xffff;
837 if (code > 0xff) break;
838 %_OneByteSeqStringSetChar(i, code, one_byte);
840 if (i == n) return one_byte;
841 one_byte = %TruncateString(one_byte, i);
843 var two_byte = %NewString(n - i, NEW_TWO_BYTE_STRING);
844 for (var j = 0; i < n; i++, j++) {
845 var code = %_Arguments(i);
846 if (!%_IsSmi(code)) code = ToNumber(code) & 0xffff;
847 %_TwoByteSeqStringSetChar(j, code, two_byte);
849 return one_byte + two_byte;
853 // ES6 draft, revision 26 (2014-07-18), section B.2.3.2.1
854 function HtmlEscape(str) {
855 return %_CallFunction(TO_STRING_INLINE(str), /"/g, """, StringReplace);
859 // ES6 draft, revision 26 (2014-07-18), section B.2.3.2
860 function StringAnchor(name) {
861 CHECK_OBJECT_COERCIBLE(this, "String.prototype.anchor");
862 return "<a name=\"" + HtmlEscape(name) + "\">" + TO_STRING_INLINE(this) +
867 // ES6 draft, revision 26 (2014-07-18), section B.2.3.3
868 function StringBig() {
869 CHECK_OBJECT_COERCIBLE(this, "String.prototype.big");
870 return "<big>" + TO_STRING_INLINE(this) + "</big>";
874 // ES6 draft, revision 26 (2014-07-18), section B.2.3.4
875 function StringBlink() {
876 CHECK_OBJECT_COERCIBLE(this, "String.prototype.blink");
877 return "<blink>" + TO_STRING_INLINE(this) + "</blink>";
881 // ES6 draft, revision 26 (2014-07-18), section B.2.3.5
882 function StringBold() {
883 CHECK_OBJECT_COERCIBLE(this, "String.prototype.bold");
884 return "<b>" + TO_STRING_INLINE(this) + "</b>";
888 // ES6 draft, revision 26 (2014-07-18), section B.2.3.6
889 function StringFixed() {
890 CHECK_OBJECT_COERCIBLE(this, "String.prototype.fixed");
891 return "<tt>" + TO_STRING_INLINE(this) + "</tt>";
895 // ES6 draft, revision 26 (2014-07-18), section B.2.3.7
896 function StringFontcolor(color) {
897 CHECK_OBJECT_COERCIBLE(this, "String.prototype.fontcolor");
898 return "<font color=\"" + HtmlEscape(color) + "\">" + TO_STRING_INLINE(this) +
903 // ES6 draft, revision 26 (2014-07-18), section B.2.3.8
904 function StringFontsize(size) {
905 CHECK_OBJECT_COERCIBLE(this, "String.prototype.fontsize");
906 return "<font size=\"" + HtmlEscape(size) + "\">" + TO_STRING_INLINE(this) +
911 // ES6 draft, revision 26 (2014-07-18), section B.2.3.9
912 function StringItalics() {
913 CHECK_OBJECT_COERCIBLE(this, "String.prototype.italics");
914 return "<i>" + TO_STRING_INLINE(this) + "</i>";
918 // ES6 draft, revision 26 (2014-07-18), section B.2.3.10
919 function StringLink(s) {
920 CHECK_OBJECT_COERCIBLE(this, "String.prototype.link");
921 return "<a href=\"" + HtmlEscape(s) + "\">" + TO_STRING_INLINE(this) + "</a>";
925 // ES6 draft, revision 26 (2014-07-18), section B.2.3.11
926 function StringSmall() {
927 CHECK_OBJECT_COERCIBLE(this, "String.prototype.small");
928 return "<small>" + TO_STRING_INLINE(this) + "</small>";
932 // ES6 draft, revision 26 (2014-07-18), section B.2.3.12
933 function StringStrike() {
934 CHECK_OBJECT_COERCIBLE(this, "String.prototype.strike");
935 return "<strike>" + TO_STRING_INLINE(this) + "</strike>";
939 // ES6 draft, revision 26 (2014-07-18), section B.2.3.13
940 function StringSub() {
941 CHECK_OBJECT_COERCIBLE(this, "String.prototype.sub");
942 return "<sub>" + TO_STRING_INLINE(this) + "</sub>";
946 // ES6 draft, revision 26 (2014-07-18), section B.2.3.14
947 function StringSup() {
948 CHECK_OBJECT_COERCIBLE(this, "String.prototype.sup");
949 return "<sup>" + TO_STRING_INLINE(this) + "</sup>";
952 // ES6 draft 01-20-14, section 21.1.3.13
953 function StringRepeat(count) {
954 CHECK_OBJECT_COERCIBLE(this, "String.prototype.repeat");
956 var s = TO_STRING_INLINE(this);
957 var n = $toInteger(count);
958 // The maximum string length is stored in a smi, so a longer repeat
959 // must result in a range error.
960 if (n < 0 || n > %_MaxSmi()) throw MakeRangeError(kInvalidCountValue);
966 if (n === 0) return r;
972 // ES6 draft 04-05-14, section 21.1.3.18
973 function StringStartsWith(searchString /* position */) { // length == 1
974 CHECK_OBJECT_COERCIBLE(this, "String.prototype.startsWith");
976 var s = TO_STRING_INLINE(this);
978 if (IS_REGEXP(searchString)) {
979 throw MakeTypeError(kFirstArgumentNotRegExp, "String.prototype.startsWith");
982 var ss = TO_STRING_INLINE(searchString);
984 if (%_ArgumentsLength() > 1) {
985 var arg = %_Arguments(1); // position
986 if (!IS_UNDEFINED(arg)) {
987 pos = $toInteger(arg);
991 var s_len = s.length;
992 if (pos < 0) pos = 0;
993 if (pos > s_len) pos = s_len;
994 var ss_len = ss.length;
996 if (ss_len + pos > s_len) {
1000 for (var i = 0; i < ss_len; i++) {
1001 if (%_StringCharCodeAt(s, pos + i) !== %_StringCharCodeAt(ss, i)) {
1010 // ES6 draft 04-05-14, section 21.1.3.7
1011 function StringEndsWith(searchString /* position */) { // length == 1
1012 CHECK_OBJECT_COERCIBLE(this, "String.prototype.endsWith");
1014 var s = TO_STRING_INLINE(this);
1016 if (IS_REGEXP(searchString)) {
1017 throw MakeTypeError(kFirstArgumentNotRegExp, "String.prototype.endsWith");
1020 var ss = TO_STRING_INLINE(searchString);
1021 var s_len = s.length;
1023 if (%_ArgumentsLength() > 1) {
1024 var arg = %_Arguments(1); // position
1025 if (!IS_UNDEFINED(arg)) {
1026 pos = $toInteger(arg);
1030 if (pos < 0) pos = 0;
1031 if (pos > s_len) pos = s_len;
1032 var ss_len = ss.length;
1039 for (var i = 0; i < ss_len; i++) {
1040 if (%_StringCharCodeAt(s, pos + i) !== %_StringCharCodeAt(ss, i)) {
1049 // ES6 draft 04-05-14, section 21.1.3.6
1050 function StringIncludes(searchString /* position */) { // length == 1
1051 CHECK_OBJECT_COERCIBLE(this, "String.prototype.includes");
1053 var string = TO_STRING_INLINE(this);
1055 if (IS_REGEXP(searchString)) {
1056 throw MakeTypeError(kFirstArgumentNotRegExp, "String.prototype.includes");
1059 searchString = TO_STRING_INLINE(searchString);
1061 if (%_ArgumentsLength() > 1) {
1062 pos = %_Arguments(1); // position
1063 pos = TO_INTEGER(pos);
1066 var stringLength = string.length;
1067 if (pos < 0) pos = 0;
1068 if (pos > stringLength) pos = stringLength;
1069 var searchStringLength = searchString.length;
1071 if (searchStringLength + pos > stringLength) {
1075 return %StringIndexOf(string, searchString, pos) !== -1;
1079 // ES6 Draft 05-22-2014, section 21.1.3.3
1080 function StringCodePointAt(pos) {
1081 CHECK_OBJECT_COERCIBLE(this, "String.prototype.codePointAt");
1083 var string = TO_STRING_INLINE(this);
1084 var size = string.length;
1085 pos = TO_INTEGER(pos);
1086 if (pos < 0 || pos >= size) {
1089 var first = %_StringCharCodeAt(string, pos);
1090 if (first < 0xD800 || first > 0xDBFF || pos + 1 == size) {
1093 var second = %_StringCharCodeAt(string, pos + 1);
1094 if (second < 0xDC00 || second > 0xDFFF) {
1097 return (first - 0xD800) * 0x400 + second + 0x2400;
1101 // ES6 Draft 05-22-2014, section 21.1.2.2
1102 function StringFromCodePoint(_) { // length = 1
1104 var length = %_ArgumentsLength();
1107 for (index = 0; index < length; index++) {
1108 code = %_Arguments(index);
1109 if (!%_IsSmi(code)) {
1110 code = ToNumber(code);
1112 if (code < 0 || code > 0x10FFFF || code !== TO_INTEGER(code)) {
1113 throw MakeRangeError(kInvalidCodePoint, code);
1115 if (code <= 0xFFFF) {
1116 result += %_StringCharFromCode(code);
1119 result += %_StringCharFromCode((code >>> 10) & 0x3FF | 0xD800);
1120 result += %_StringCharFromCode(code & 0x3FF | 0xDC00);
1127 // -------------------------------------------------------------------
1128 // String methods related to templates
1130 // ES6 Draft 03-17-2015, section 21.1.2.4
1131 function StringRaw(callSite) {
1132 // TODO(caitp): Use rest parameters when implemented
1133 var numberOfSubstitutions = %_ArgumentsLength();
1134 var cooked = TO_OBJECT(callSite);
1135 var raw = TO_OBJECT(cooked.raw);
1136 var literalSegments = $toLength(raw.length);
1137 if (literalSegments <= 0) return "";
1139 var result = ToString(raw[0]);
1141 for (var i = 1; i < literalSegments; ++i) {
1142 if (i < numberOfSubstitutions) {
1143 result += ToString(%_Arguments(i));
1145 result += ToString(raw[i]);
1151 // -------------------------------------------------------------------
1153 // Set the String function and constructor.
1154 %SetCode(GlobalString, StringConstructor);
1155 %FunctionSetPrototype(GlobalString, new GlobalString());
1157 // Set up the constructor property on the String prototype object.
1159 GlobalString.prototype, "constructor", GlobalString, DONT_ENUM);
1161 // Set up the non-enumerable functions on the String object.
1162 utils.InstallFunctions(GlobalString, DONT_ENUM, [
1163 "fromCharCode", StringFromCharCode,
1164 "fromCodePoint", StringFromCodePoint,
1168 // Set up the non-enumerable functions on the String prototype object.
1169 utils.InstallFunctions(GlobalString.prototype, DONT_ENUM, [
1170 "valueOf", StringValueOf,
1171 "toString", StringToString,
1172 "charAt", StringCharAtJS,
1173 "charCodeAt", StringCharCodeAtJS,
1174 "codePointAt", StringCodePointAt,
1175 "concat", StringConcat,
1176 "endsWith", StringEndsWith,
1177 "includes", StringIncludes,
1178 "indexOf", StringIndexOfJS,
1179 "lastIndexOf", StringLastIndexOfJS,
1180 "localeCompare", StringLocaleCompareJS,
1181 "match", StringMatchJS,
1182 "normalize", StringNormalizeJS,
1183 "repeat", StringRepeat,
1184 "replace", StringReplace,
1185 "search", StringSearch,
1186 "slice", StringSlice,
1187 "split", StringSplitJS,
1188 "substring", StringSubstring,
1189 "substr", StringSubstr,
1190 "startsWith", StringStartsWith,
1191 "toLowerCase", StringToLowerCaseJS,
1192 "toLocaleLowerCase", StringToLocaleLowerCase,
1193 "toUpperCase", StringToUpperCaseJS,
1194 "toLocaleUpperCase", StringToLocaleUpperCase,
1195 "trim", StringTrimJS,
1196 "trimLeft", StringTrimLeft,
1197 "trimRight", StringTrimRight,
1200 "anchor", StringAnchor,
1201 "fontcolor", StringFontcolor,
1202 "fontsize", StringFontsize,
1204 "blink", StringBlink,
1206 "fixed", StringFixed,
1207 "italics", StringItalics,
1208 "small", StringSmall,
1209 "strike", StringStrike,
1214 // -------------------------------------------------------------------
1217 utils.Export(function(to) {
1218 to.StringCharAt = StringCharAtJS;
1219 to.StringIndexOf = StringIndexOfJS;
1220 to.StringLastIndexOf = StringLastIndexOfJS;
1221 to.StringMatch = StringMatchJS;
1222 to.StringReplace = StringReplace;
1223 to.StringSlice = StringSlice;
1224 to.StringSplit = StringSplitJS;
1225 to.StringSubstr = StringSubstr;
1226 to.StringSubstring = StringSubstring;