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