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. 4 5 (function(global, utils) { 6 7 %CheckIsBootstrapping(); 8 9 // ------------------------------------------------------------------- 10 // Imports 11 12 var ArrayIndexOf; 13 var ArrayJoin; 14 var GlobalRegExp = global.RegExp; 15 var GlobalString = global.String; 16 var InternalArray = utils.InternalArray; 17 var InternalPackedArray = utils.InternalPackedArray; 18 var MakeRangeError; 19 var MakeTypeError; 20 var MathMax; 21 var MathMin; 22 var matchSymbol = utils.ImportNow("match_symbol"); 23 var RegExpExec; 24 var RegExpExecNoTests; 25 var RegExpLastMatchInfo; 26 var searchSymbol = utils.ImportNow("search_symbol"); 27 var splitSymbol = utils.ImportNow("split_symbol"); 28 29 utils.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 44 function 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 53 function 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 62 function 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 74 function 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 86 function 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 104 function 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 121 function 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. 152 function 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. 160 function 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. 183 function 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. 206 var reusableMatchInfo = [2, "", "", -1, -1]; 207 208 209 // ECMA-262, section 15.5.4.11 210 function 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. 301 function 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. 383 function 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. 398 var reusableReplaceArray = new InternalArray(4); 399 400 // Helper function for replacing regular expressions with the result of a 401 // function application in String.prototype.replace. 402 function 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 469 function 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. 507 function 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 524 function 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. 566 function 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 598 function 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 631 function 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 671 function 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 679 function 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 687 function 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 695 function StringToLocaleUpperCase() { 696 CHECK_OBJECT_COERCIBLE(this, "String.prototype.toLocaleUpperCase"); 697 698 return %StringToUpperCase(TO_STRING(this)); 699 } 700 701 // ES5, 15.5.4.20 702 function StringTrimJS() { 703 CHECK_OBJECT_COERCIBLE(this, "String.prototype.trim"); 704 705 return %StringTrim(TO_STRING(this), true, true); 706 } 707 708 function StringTrimLeft() { 709 CHECK_OBJECT_COERCIBLE(this, "String.prototype.trimLeft"); 710 711 return %StringTrim(TO_STRING(this), true, false); 712 } 713 714 function 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 722 function 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 748 function HtmlEscape(str) { 749 return %_Call(StringReplace, TO_STRING(str), /"/g, """); 750 } 751 752 753 // ES6 draft, revision 26 (2014-07-18), section B.2.3.2 754 function 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 762 function 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 769 function 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 776 function 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 783 function 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 790 function 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 798 function 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 806 function 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 813 function 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 820 function 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 827 function 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 834 function 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 841 function 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 847 function 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 873 function 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 903 function 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 934 function 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 964 function 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 986 function 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 1015 function 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. 1045 utils.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. 1052 utils.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 1100 utils.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 }) 1113