RegExp.js (34977B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 // https://github.com/tc39/ecma262/pull/2418 22.2.6.4 get RegExp.prototype.flags 6 // https://arai-a.github.io/ecma262-compare/?pr=2418&id=sec-get-regexp.prototype.flags 7 // Uncloned functions with `$` prefix are allocated as extended function 8 // to store the original name in `SetCanonicalName`. 9 function $RegExpFlagsGetter() { 10 // Steps 1-2. 11 var R = this; 12 if (!IsObject(R)) { 13 ThrowTypeError(JSMSG_OBJECT_REQUIRED, R === null ? "null" : typeof R); 14 } 15 16 // Step 3. 17 var result = ""; 18 19 // Steps 4-5. 20 if (R.hasIndices) { 21 result += "d"; 22 } 23 24 // Steps 6-7. 25 if (R.global) { 26 result += "g"; 27 } 28 29 // Steps 8-9. 30 if (R.ignoreCase) { 31 result += "i"; 32 } 33 34 // Steps 10-11. 35 if (R.multiline) { 36 result += "m"; 37 } 38 39 // Steps 12-13. 40 if (R.dotAll) { 41 result += "s"; 42 } 43 44 // Steps 14-15. 45 if (R.unicode) { 46 result += "u"; 47 } 48 49 // Steps 16-17. 50 if (R.unicodeSets) { 51 result += "v"; 52 } 53 54 // Steps 18-19 55 if (R.sticky) { 56 result += "y"; 57 } 58 59 // Step 20. 60 return result; 61 } 62 SetCanonicalName($RegExpFlagsGetter, "get flags"); 63 64 // ES 2017 draft 40edb3a95a475c1b251141ac681b8793129d9a6d 21.2.5.14. 65 function $RegExpToString() { 66 // Step 1. 67 var R = this; 68 69 // Step 2. 70 if (!IsObject(R)) { 71 ThrowTypeError(JSMSG_OBJECT_REQUIRED, R === null ? "null" : typeof R); 72 } 73 74 // Step 3. 75 var pattern = ToString(R.source); 76 77 // Step 4. 78 var flags = ToString(R.flags); 79 80 // Steps 5-6. 81 return "/" + pattern + "/" + flags; 82 } 83 SetCanonicalName($RegExpToString, "toString"); 84 85 // ES 2016 draft Mar 25, 2016 21.2.5.2.3. 86 function AdvanceStringIndex(S, index) { 87 // Step 1. 88 assert(typeof S === "string", "Expected string as 1st argument"); 89 90 // Step 2. 91 assert( 92 index >= 0 && index <= MAX_NUMERIC_INDEX, 93 "Expected integer as 2nd argument" 94 ); 95 96 // Step 3 (skipped). 97 98 // Step 4 (skipped). 99 100 // Steps 5-11. 101 var supplementary = ( 102 index < S.length && 103 callFunction(std_String_codePointAt, S, index) > 0xffff 104 ); 105 return index + 1 + supplementary; 106 } 107 108 // ES2023 draft rev 2c78e6f6b5bc6bfbf79dd8a12a9593e5b57afcd2 109 // 22.2.5.8 RegExp.prototype [ @@match ] ( string ) 110 function RegExpMatch(string) { 111 // Step 1. 112 var rx = this; 113 114 // Step 2. 115 if (!IsObject(rx)) { 116 ThrowTypeError(JSMSG_OBJECT_REQUIRED, rx === null ? "null" : typeof rx); 117 } 118 119 // Step 3. 120 var S = ToString(string); 121 122 // Optimized paths for simple cases. 123 if (IsOptimizableRegExpObject(rx)) { 124 // Step 4. 125 var flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT); 126 var global = !!(flags & REGEXP_GLOBAL_FLAG); 127 128 if (global) { 129 // Step 6.a. 130 var fullUnicode = !!(flags & REGEXP_UNICODE_FLAG) || !!(flags & REGEXP_UNICODESETS_FLAG); 131 132 // Steps 6.b-e. 133 return RegExpGlobalMatchOpt(rx, S, fullUnicode); 134 } 135 136 // Step 5. 137 return RegExpBuiltinExec(rx, S); 138 } 139 140 // Stes 4-6 141 return RegExpMatchSlowPath(rx, S); 142 } 143 144 // ES2023 draft rev 2c78e6f6b5bc6bfbf79dd8a12a9593e5b57afcd2 145 // 22.2.5.8 RegExp.prototype [ @@match ] ( string ) 146 // Steps 4-6 147 function RegExpMatchSlowPath(rx, S) { 148 // Step 4. 149 var flags = ToString(rx.flags); 150 151 // Step 5. 152 if (!callFunction(std_String_includes, flags, "g")) { 153 return RegExpExec(rx, S); 154 } 155 156 // Step 6.a. 157 var fullUnicode = callFunction(std_String_includes, flags, "u") || callFunction(std_String_includes, flags, "v"); 158 159 // Step 6.b. 160 rx.lastIndex = 0; 161 162 // Step 6.c. 163 var A = []; 164 165 // Step 6.d. 166 var n = 0; 167 168 // Step 6.e. 169 while (true) { 170 // Step 6.e.i. 171 var result = RegExpExec(rx, S); 172 173 // Step 6.e.ii. 174 if (result === null) { 175 return n === 0 ? null : A; 176 } 177 178 // Step 6.e.iii.1. 179 var matchStr = ToString(result[0]); 180 181 // Step 6.e.iii.2. 182 DefineDataProperty(A, n, matchStr); 183 184 // Step 6.e.iii.3. 185 if (matchStr === "") { 186 var lastIndex = ToLength(rx.lastIndex); 187 rx.lastIndex = fullUnicode 188 ? AdvanceStringIndex(S, lastIndex) 189 : lastIndex + 1; 190 } 191 192 // Step 6.e.iii.4. 193 n++; 194 } 195 } 196 197 // ES 2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e 21.2.5.6. 198 // Steps 6.b-e. 199 // Optimized path for @@match with global flag. 200 function RegExpGlobalMatchOpt(rx, S, fullUnicode) { 201 // Step 6.b. 202 var lastIndex = 0; 203 rx.lastIndex = 0; 204 205 // Step 6.c. 206 var A = []; 207 208 // Step 6.d. 209 var n = 0; 210 211 var lengthS = S.length; 212 213 // Step 6.e. 214 while (true) { 215 // Step 6.e.i. 216 var position = RegExpSearcher(rx, S, lastIndex); 217 218 // Step 6.e.ii. 219 if (position === -1) { 220 return n === 0 ? null : A; 221 } 222 223 lastIndex = RegExpSearcherLastLimit(S); 224 225 // Step 6.e.iii.1. 226 var matchStr = Substring(S, position, lastIndex - position); 227 228 // Step 6.e.iii.2. 229 DefineDataProperty(A, n, matchStr); 230 231 // Step 6.e.iii.4. 232 if (matchStr === "") { 233 lastIndex = fullUnicode 234 ? AdvanceStringIndex(S, lastIndex) 235 : lastIndex + 1; 236 if (lastIndex > lengthS) { 237 return A; 238 } 239 } 240 241 // Step 6.e.iii.5. 242 n++; 243 } 244 } 245 246 // ES2023 draft rev 2c78e6f6b5bc6bfbf79dd8a12a9593e5b57afcd2 247 // 22.2.5.11 RegExp.prototype [ @@replace ] ( string, replaceValue ) 248 function RegExpReplace(string, replaceValue) { 249 // Step 1. 250 var rx = this; 251 252 // Step 2. 253 if (!IsObject(rx)) { 254 ThrowTypeError(JSMSG_OBJECT_REQUIRED, rx === null ? "null" : typeof rx); 255 } 256 257 // Step 3. 258 var S = ToString(string); 259 260 // Step 4. 261 var lengthS = S.length; 262 263 // Step 5. 264 var functionalReplace = IsCallable(replaceValue); 265 266 // Step 6. 267 var firstDollarIndex = -1; 268 if (!functionalReplace) { 269 // Step 6.a. 270 replaceValue = ToString(replaceValue); 271 272 // Skip if replaceValue is an empty string or a single character. 273 // A single character string may contain "$", but that cannot be a 274 // substitution. 275 if (replaceValue.length > 1) { 276 firstDollarIndex = GetFirstDollarIndex(replaceValue); 277 } 278 } 279 280 // Optimized paths. 281 if (IsOptimizableRegExpObject(rx)) { 282 // Step 7. 283 var flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT); 284 285 // Step 9. 286 var global = !!(flags & REGEXP_GLOBAL_FLAG); 287 288 // Steps 9-17. 289 if (global) { 290 if (functionalReplace) { 291 // For large strings check if the replacer function is 292 // applicable for the elem-base optimization. 293 if (lengthS > 5000) { 294 var elemBase = GetElemBaseForLambda(replaceValue); 295 if (IsObject(elemBase)) { 296 return RegExpGlobalReplaceOptElemBase( 297 rx, 298 S, 299 lengthS, 300 replaceValue, 301 flags, 302 elemBase 303 ); 304 } 305 } 306 return RegExpGlobalReplaceOptFunc(rx, S, lengthS, replaceValue, flags); 307 } 308 if (firstDollarIndex !== -1) { 309 return RegExpGlobalReplaceOptSubst( 310 rx, 311 S, 312 lengthS, 313 replaceValue, 314 flags, 315 firstDollarIndex 316 ); 317 } 318 return RegExpGlobalReplaceOptSimple(rx, S, lengthS, replaceValue, flags); 319 } 320 321 if (functionalReplace) { 322 return RegExpLocalReplaceOptFunc(rx, S, lengthS, replaceValue); 323 } 324 if (firstDollarIndex !== -1) { 325 return RegExpLocalReplaceOptSubst( 326 rx, 327 S, 328 lengthS, 329 replaceValue, 330 firstDollarIndex 331 ); 332 } 333 return RegExpLocalReplaceOptSimple(rx, S, lengthS, replaceValue); 334 } 335 336 // Steps 7-17. 337 return RegExpReplaceSlowPath( 338 rx, 339 S, 340 lengthS, 341 replaceValue, 342 functionalReplace, 343 firstDollarIndex 344 ); 345 } 346 347 // ES2023 draft rev 2c78e6f6b5bc6bfbf79dd8a12a9593e5b57afcd2 348 // 22.2.5.11 RegExp.prototype [ @@replace ] ( string, replaceValue ) 349 // Steps 7-17. 350 // Slow path for @@replace. 351 function RegExpReplaceSlowPath( 352 rx, 353 S, 354 lengthS, 355 replaceValue, 356 functionalReplace, 357 firstDollarIndex 358 ) { 359 // Step 7. 360 var flags = ToString(rx.flags); 361 362 // Step 8. 363 var global = callFunction(std_String_includes, flags, "g"); 364 365 // Step 9. 366 var fullUnicode = false; 367 if (global) { 368 // Step 9.a. 369 fullUnicode = callFunction(std_String_includes, flags, "u") || callFunction(std_String_includes, flags, "v"); 370 371 // Step 9.b. 372 rx.lastIndex = 0; 373 } 374 375 // Step 10. 376 var results = new_List(); 377 var nResults = 0; 378 379 // Steps 11-12. 380 while (true) { 381 // Step 12.a. 382 var result = RegExpExec(rx, S); 383 384 // Step 12.b. 385 if (result === null) { 386 break; 387 } 388 389 // Step 12.c.i. 390 DefineDataProperty(results, nResults++, result); 391 392 // Step 12.c.ii. 393 if (!global) { 394 break; 395 } 396 397 // Step 12.c.iii.1. 398 var matchStr = ToString(result[0]); 399 400 // Step 12.c.iii.2. 401 if (matchStr === "") { 402 var lastIndex = ToLength(rx.lastIndex); 403 rx.lastIndex = fullUnicode 404 ? AdvanceStringIndex(S, lastIndex) 405 : lastIndex + 1; 406 } 407 } 408 409 // Step 13. 410 var accumulatedResult = ""; 411 412 // Step 14. 413 var nextSourcePosition = 0; 414 415 // Step 15. 416 for (var i = 0; i < nResults; i++) { 417 result = results[i]; 418 419 // Steps 15.a-b. 420 var nCaptures = std_Math_max(ToLength(result.length) - 1, 0); 421 422 // Step 15.c. 423 var matched = ToString(result[0]); 424 425 // Step 15.d. 426 var matchLength = matched.length; 427 428 // Steps 15.e-f. 429 var position = std_Math_max( 430 std_Math_min(ToInteger(result.index), lengthS), 431 0 432 ); 433 434 var replacement; 435 if (functionalReplace || firstDollarIndex !== -1) { 436 // Steps 15.g-l. 437 replacement = RegExpGetComplexReplacement( 438 result, 439 matched, 440 S, 441 position, 442 nCaptures, 443 replaceValue, 444 functionalReplace, 445 firstDollarIndex 446 ); 447 } else { 448 // Steps 15.g, 15.i, 15.i.iv. 449 // We don't need captures array, but ToString is visible to script. 450 for (var n = 1; n <= nCaptures; n++) { 451 // Steps 15.i.i-ii. 452 var capN = result[n]; 453 454 // Step 15.i.ii. 455 if (capN !== undefined) { 456 ToString(capN); 457 } 458 } 459 460 // Steps 15.j, 15.l.i. 461 // We don't need namedCaptures, but ToObject is visible to script. 462 var namedCaptures = result.groups; 463 if (namedCaptures !== undefined) { 464 ToObject(namedCaptures); 465 } 466 467 // Step 15.l.ii. 468 replacement = replaceValue; 469 } 470 471 // Step 15.m. 472 if (position >= nextSourcePosition) { 473 // Step 15.m.ii. 474 accumulatedResult += 475 Substring(S, nextSourcePosition, position - nextSourcePosition) + 476 replacement; 477 478 // Step 15.m.iii. 479 nextSourcePosition = position + matchLength; 480 } 481 } 482 483 // Step 16. 484 if (nextSourcePosition >= lengthS) { 485 return accumulatedResult; 486 } 487 488 // Step 17. 489 return ( 490 accumulatedResult + 491 Substring(S, nextSourcePosition, lengthS - nextSourcePosition) 492 ); 493 } 494 495 // ES2023 draft rev 2c78e6f6b5bc6bfbf79dd8a12a9593e5b57afcd2 496 // 22.2.5.11 RegExp.prototype [ @@replace ] ( string, replaceValue ) 497 // https://tc39.es/ecma262/#sec-regexp.prototype-@@replace 498 // Steps 15.g-l. 499 // Calculates functional/substitution replacement from match result. 500 // Used in the following functions: 501 // * RegExpReplaceSlowPath 502 function RegExpGetComplexReplacement( 503 result, 504 matched, 505 S, 506 position, 507 nCaptures, 508 replaceValue, 509 functionalReplace, 510 firstDollarIndex 511 ) { 512 // Step 15.g. 513 var captures = new_List(); 514 var capturesLength = 0; 515 516 // Step 15.k.i (reordered). 517 DefineDataProperty(captures, capturesLength++, matched); 518 519 // Steps 15.h, 15.i, 15.i.v. 520 for (var n = 1; n <= nCaptures; n++) { 521 // Step 15.i.i. 522 var capN = result[n]; 523 524 // Step 15.i.ii. 525 if (capN !== undefined) { 526 capN = ToString(capN); 527 } 528 529 // Step 15.i.iii. 530 DefineDataProperty(captures, capturesLength++, capN); 531 } 532 533 // Step 15.j. 534 var namedCaptures = result.groups; 535 536 // Step 15.k. 537 if (functionalReplace) { 538 // For `nCaptures` <= 4 case, call `replaceValue` directly, otherwise 539 // use `std_Function_apply` with all arguments stored in `captures`. 540 if (namedCaptures === undefined) { 541 switch (nCaptures) { 542 case 0: 543 return ToString( 544 callContentFunction( 545 replaceValue, 546 undefined, 547 SPREAD(captures, 1), 548 position, 549 S 550 ) 551 ); 552 case 1: 553 return ToString( 554 callContentFunction( 555 replaceValue, 556 undefined, 557 SPREAD(captures, 2), 558 position, 559 S 560 ) 561 ); 562 case 2: 563 return ToString( 564 callContentFunction( 565 replaceValue, 566 undefined, 567 SPREAD(captures, 3), 568 position, 569 S 570 ) 571 ); 572 case 3: 573 return ToString( 574 callContentFunction( 575 replaceValue, 576 undefined, 577 SPREAD(captures, 4), 578 position, 579 S 580 ) 581 ); 582 case 4: 583 return ToString( 584 callContentFunction( 585 replaceValue, 586 undefined, 587 SPREAD(captures, 5), 588 position, 589 S 590 ) 591 ); 592 } 593 } 594 595 // Steps 15.k.ii-vi. 596 DefineDataProperty(captures, capturesLength++, position); 597 DefineDataProperty(captures, capturesLength++, S); 598 if (namedCaptures !== undefined) { 599 DefineDataProperty(captures, capturesLength++, namedCaptures); 600 } 601 return ToString( 602 callFunction(std_Function_apply, replaceValue, undefined, captures) 603 ); 604 } 605 606 // Step 15.l. 607 if (namedCaptures !== undefined) { 608 namedCaptures = ToObject(namedCaptures); 609 } 610 return RegExpGetSubstitution( 611 captures, 612 S, 613 position, 614 replaceValue, 615 firstDollarIndex, 616 namedCaptures 617 ); 618 } 619 620 // ES2023 draft rev 2c78e6f6b5bc6bfbf79dd8a12a9593e5b57afcd2 621 // 22.2.5.11 RegExp.prototype [ @@replace ] ( string, replaceValue ) 622 // https://tc39.es/ecma262/#sec-regexp.prototype-@@replace 623 // Steps 15.g-k. 624 // Calculates functional replacement from match result. 625 // Used in the following functions: 626 // * RegExpGlobalReplaceOptFunc 627 // * RegExpGlobalReplaceOptElemBase 628 // * RegExpLocalReplaceOptFunc 629 function RegExpGetFunctionalReplacement(result, S, position, replaceValue) { 630 // For `nCaptures` <= 4 case, call `replaceValue` directly, otherwise 631 // use `std_Function_apply` with all arguments stored in `captures`. 632 assert(result.length >= 1, "RegExpMatcher doesn't return an empty array"); 633 var nCaptures = result.length - 1; 634 635 // Step 15.j (reordered) 636 var namedCaptures = result.groups; 637 638 if (namedCaptures === undefined) { 639 switch (nCaptures) { 640 case 0: 641 return ToString( 642 callContentFunction( 643 replaceValue, 644 undefined, 645 SPREAD(result, 1), 646 position, 647 S 648 ) 649 ); 650 case 1: 651 return ToString( 652 callContentFunction( 653 replaceValue, 654 undefined, 655 SPREAD(result, 2), 656 position, 657 S 658 ) 659 ); 660 case 2: 661 return ToString( 662 callContentFunction( 663 replaceValue, 664 undefined, 665 SPREAD(result, 3), 666 position, 667 S 668 ) 669 ); 670 case 3: 671 return ToString( 672 callContentFunction( 673 replaceValue, 674 undefined, 675 SPREAD(result, 4), 676 position, 677 S 678 ) 679 ); 680 case 4: 681 return ToString( 682 callContentFunction( 683 replaceValue, 684 undefined, 685 SPREAD(result, 5), 686 position, 687 S 688 ) 689 ); 690 } 691 } 692 693 // Steps 15.g-i, 15.k.i-ii. 694 var captures = new_List(); 695 for (var n = 0; n <= nCaptures; n++) { 696 assert( 697 typeof result[n] === "string" || result[n] === undefined, 698 "RegExpMatcher returns only strings and undefined" 699 ); 700 DefineDataProperty(captures, n, result[n]); 701 } 702 703 // Step 15.k.iii. 704 DefineDataProperty(captures, nCaptures + 1, position); 705 DefineDataProperty(captures, nCaptures + 2, S); 706 707 // Step 15.k.iv. 708 if (namedCaptures !== undefined) { 709 DefineDataProperty(captures, nCaptures + 3, namedCaptures); 710 } 711 712 // Steps 15.k.v-vi. 713 return ToString( 714 callFunction(std_Function_apply, replaceValue, undefined, captures) 715 ); 716 } 717 718 // ES2023 draft rev 2c78e6f6b5bc6bfbf79dd8a12a9593e5b57afcd2 719 // 22.2.5.11 RegExp.prototype [ @@replace ] ( string, replaceValue ) 720 // Steps 9.b-17. 721 // Optimized path for @@replace with the following conditions: 722 // * global flag is true 723 // * replaceValue is a string without "$" 724 function RegExpGlobalReplaceOptSimple(rx, S, lengthS, replaceValue, flags) { 725 // Step 9.a. 726 var fullUnicode = !!(flags & REGEXP_UNICODE_FLAG) || !!(flags & REGEXP_UNICODESETS_FLAG); 727 728 // Step 9.b. 729 var lastIndex = 0; 730 rx.lastIndex = 0; 731 732 // Step 13 (reordered). 733 var accumulatedResult = ""; 734 735 // Step 14 (reordered). 736 var nextSourcePosition = 0; 737 738 // Step 12. 739 while (true) { 740 // Step 12.a. 741 var position = RegExpSearcher(rx, S, lastIndex); 742 743 // Step 12.b. 744 if (position === -1) { 745 break; 746 } 747 748 lastIndex = RegExpSearcherLastLimit(S); 749 750 // Step 15.m.ii. 751 accumulatedResult += 752 Substring(S, nextSourcePosition, position - nextSourcePosition) + 753 replaceValue; 754 755 // Step 15.m.iii. 756 nextSourcePosition = lastIndex; 757 758 // Step 12.c.iii.2. 759 if (lastIndex === position) { 760 lastIndex = fullUnicode 761 ? AdvanceStringIndex(S, lastIndex) 762 : lastIndex + 1; 763 if (lastIndex > lengthS) { 764 break; 765 } 766 } 767 } 768 769 // Step 16. 770 if (nextSourcePosition >= lengthS) { 771 return accumulatedResult; 772 } 773 774 // Step 17. 775 return ( 776 accumulatedResult + 777 Substring(S, nextSourcePosition, lengthS - nextSourcePosition) 778 ); 779 } 780 781 // ES2023 draft rev 2c78e6f6b5bc6bfbf79dd8a12a9593e5b57afcd2 782 // 22.2.5.11 RegExp.prototype [ @@replace ] ( string, replaceValue ) 783 // Steps 7-17. 784 // Optimized path for @@replace. 785 786 // Conditions: 787 // * global flag is true 788 // * replaceValue is a function 789 #define FUNC_NAME RegExpGlobalReplaceOptFunc 790 #define FUNCTIONAL 791 #include "RegExpGlobalReplaceOpt.h.js" 792 #undef FUNCTIONAL 793 #undef FUNC_NAME 794 /* global RegExpGlobalReplaceOptFunc */ 795 796 // Conditions: 797 // * global flag is true 798 // * replaceValue is a function that returns element of an object 799 #define FUNC_NAME RegExpGlobalReplaceOptElemBase 800 #define ELEMBASE 801 #include "RegExpGlobalReplaceOpt.h.js" 802 #undef ELEMBASE 803 #undef FUNC_NAME 804 /* global RegExpGlobalReplaceOptElemBase */ 805 806 // Conditions: 807 // * global flag is true 808 // * replaceValue is a string with "$" 809 #define FUNC_NAME RegExpGlobalReplaceOptSubst 810 #define SUBSTITUTION 811 #include "RegExpGlobalReplaceOpt.h.js" 812 #undef SUBSTITUTION 813 #undef FUNC_NAME 814 /* global RegExpGlobalReplaceOptSubst */ 815 816 // Conditions: 817 // * global flag is false 818 // * replaceValue is a string without "$" 819 #define FUNC_NAME RegExpLocalReplaceOptSimple 820 #define SIMPLE 821 #include "RegExpLocalReplaceOpt.h.js" 822 #undef SIMPLE 823 #undef FUNC_NAME 824 /* global RegExpLocalReplaceOptSimple */ 825 826 // Conditions: 827 // * global flag is false 828 // * replaceValue is a function 829 #define FUNC_NAME RegExpLocalReplaceOptFunc 830 #define FUNCTIONAL 831 #include "RegExpLocalReplaceOpt.h.js" 832 #undef FUNCTIONAL 833 #undef FUNC_NAME 834 /* global RegExpLocalReplaceOptFunc */ 835 836 // Conditions: 837 // * global flag is false 838 // * replaceValue is a string with "$" 839 #define FUNC_NAME RegExpLocalReplaceOptSubst 840 #define SUBSTITUTION 841 #include "RegExpLocalReplaceOpt.h.js" 842 #undef SUBSTITUTION 843 #undef FUNC_NAME 844 /* global RegExpLocalReplaceOptSubst */ 845 846 // ES2017 draft rev 6390c2f1b34b309895d31d8c0512eac8660a0210 847 // 21.2.5.9 RegExp.prototype [ @@search ] ( string ) 848 function RegExpSearch(string) { 849 // Step 1. 850 var rx = this; 851 852 // Step 2. 853 if (!IsObject(rx)) { 854 ThrowTypeError(JSMSG_OBJECT_REQUIRED, rx === null ? "null" : typeof rx); 855 } 856 857 // Step 3. 858 var S = ToString(string); 859 860 // Step 4. 861 var previousLastIndex = rx.lastIndex; 862 863 // Step 5. 864 var lastIndexIsZero = SameValue(previousLastIndex, 0); 865 if (!lastIndexIsZero) { 866 rx.lastIndex = 0; 867 } 868 869 if (IsOptimizableRegExpObject(rx) && S.length < 0x7fff) { 870 // Step 6. 871 var result = RegExpSearcher(rx, S, 0); 872 873 // We need to consider two cases: 874 // 875 // 1. Neither global nor sticky is set: 876 // RegExpBuiltinExec doesn't modify lastIndex for local RegExps, that 877 // means |SameValue(rx.lastIndex, 0)| is true after calling exec. The 878 // comparison in steps 7-8 |SameValue(rx.lastIndex, previousLastIndex)| 879 // is therefore equal to the already computed |lastIndexIsZero| value. 880 // 881 // 2. Global or sticky flag is set. 882 // RegExpBuiltinExec will always update lastIndex and we need to 883 // restore the property to its original value. 884 885 // Steps 7-8. 886 if (!lastIndexIsZero) { 887 rx.lastIndex = previousLastIndex; 888 } else { 889 var flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT); 890 if (flags & (REGEXP_GLOBAL_FLAG | REGEXP_STICKY_FLAG)) { 891 rx.lastIndex = previousLastIndex; 892 } 893 } 894 895 // Steps 9-10. 896 return result; 897 } 898 899 return RegExpSearchSlowPath(rx, S, previousLastIndex); 900 } 901 902 // ES2017 draft rev 6390c2f1b34b309895d31d8c0512eac8660a0210 903 // 21.2.5.9 RegExp.prototype [ @@search ] ( string ) 904 // Steps 6-10. 905 function RegExpSearchSlowPath(rx, S, previousLastIndex) { 906 // Step 6. 907 var result = RegExpExec(rx, S); 908 909 // Step 7. 910 var currentLastIndex = rx.lastIndex; 911 912 // Step 8. 913 if (!SameValue(currentLastIndex, previousLastIndex)) { 914 rx.lastIndex = previousLastIndex; 915 } 916 917 // Step 9. 918 if (result === null) { 919 return -1; 920 } 921 922 // Step 10. 923 return result.index; 924 } 925 926 // ES 2017 draft 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e 21.2.5.11. 927 function RegExpSplit(string, limit) { 928 // Step 1. 929 var rx = this; 930 931 // Step 2. 932 if (!IsObject(rx)) { 933 ThrowTypeError(JSMSG_OBJECT_REQUIRED, rx === null ? "null" : typeof rx); 934 } 935 936 // Step 3. 937 var S = ToString(string); 938 939 // Step 4. 940 var builtinCtor = GetBuiltinConstructor("RegExp"); 941 var C = SpeciesConstructor(rx, builtinCtor); 942 943 var optimizable = 944 IsOptimizableRegExpObject(rx) && 945 C === builtinCtor && 946 (limit === undefined || typeof limit === "number"); 947 948 var flags, unicodeMatching, splitter; 949 if (optimizable) { 950 // Step 5. 951 flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT); 952 #ifdef NIGHTLY_BUILD 953 assert(!!(flags & REGEXP_LEGACY_FEATURES_ENABLED_FLAG), 954 "Legacy features must be enabled in optimized path"); 955 #endif 956 // Steps 6-7. 957 unicodeMatching = !!(flags & REGEXP_UNICODE_FLAG); 958 959 // Steps 8-10. 960 // If split operation is optimizable, perform non-sticky match. 961 if (flags & REGEXP_STICKY_FLAG) { 962 var source = UnsafeGetStringFromReservedSlot(rx, REGEXP_SOURCE_SLOT); 963 var newFlags = flags & ~(REGEXP_STICKY_FLAG | REGEXP_LEGACY_FEATURES_ENABLED_FLAG); 964 splitter = RegExpConstructRaw(source, newFlags, true); 965 } else { 966 splitter = rx; 967 } 968 } else { 969 // Step 5. 970 flags = ToString(rx.flags); 971 972 // Steps 6-7. 973 unicodeMatching = callFunction(std_String_includes, flags, "u"); 974 975 // Steps 8-9. 976 var newFlags; 977 if (callFunction(std_String_includes, flags, "y")) { 978 newFlags = flags; 979 } else { 980 newFlags = flags + "y"; 981 } 982 983 // Step 10. 984 splitter = constructContentFunction(C, C, rx, newFlags); 985 } 986 987 // Step 11. 988 var A = []; 989 990 // Step 12. 991 var lengthA = 0; 992 993 // Step 13. 994 var lim; 995 if (limit === undefined) { 996 lim = MAX_UINT32; 997 } else { 998 lim = limit >>> 0; 999 } 1000 1001 // Step 15. 1002 var p = 0; 1003 1004 // Step 16. 1005 if (lim === 0) { 1006 return A; 1007 } 1008 1009 // Step 14 (reordered). 1010 var size = S.length; 1011 1012 // Step 17. 1013 if (size === 0) { 1014 // Step 17.a-b. 1015 if (optimizable) { 1016 if (RegExpSearcher(splitter, S, 0) !== -1) { 1017 return A; 1018 } 1019 } else { 1020 if (RegExpExec(splitter, S) !== null) { 1021 return A; 1022 } 1023 } 1024 1025 // Step 17.d. 1026 DefineDataProperty(A, 0, S); 1027 1028 // Step 17.e. 1029 return A; 1030 } 1031 1032 // Step 18. 1033 var q = p; 1034 1035 var optimizableNoCaptures = optimizable && !RegExpHasCaptureGroups(splitter, S); 1036 1037 // Step 19. 1038 while (q < size) { 1039 var e, z; 1040 if (optimizableNoCaptures) { 1041 // If there are no capturing groups, avoid allocating the match result 1042 // object |z| (we set it to null). This is the only difference between 1043 // this branch and the |if (optimizable)| case below. 1044 1045 // Step 19.a (skipped). 1046 // splitter.lastIndex is not used. 1047 1048 // Steps 19.b-c. 1049 q = RegExpSearcher(splitter, S, q); 1050 if (q === -1 || q >= size) { 1051 break; 1052 } 1053 1054 // Step 19.d.i. 1055 e = RegExpSearcherLastLimit(S); 1056 z = null; 1057 } else if (optimizable) { 1058 // Step 19.a (skipped). 1059 // splitter.lastIndex is not used. 1060 1061 // Step 19.b. 1062 z = RegExpMatcher(splitter, S, q); 1063 1064 // Step 19.c. 1065 if (z === null) { 1066 break; 1067 } 1068 1069 // splitter.lastIndex is not updated. 1070 q = z.index; 1071 if (q >= size) { 1072 break; 1073 } 1074 1075 // Step 19.d.i. 1076 e = q + z[0].length; 1077 } else { 1078 // Step 19.a. 1079 splitter.lastIndex = q; 1080 1081 // Step 19.b. 1082 z = RegExpExec(splitter, S); 1083 1084 // Step 19.c. 1085 if (z === null) { 1086 q = unicodeMatching ? AdvanceStringIndex(S, q) : q + 1; 1087 continue; 1088 } 1089 1090 // Step 19.d.i. 1091 e = ToLength(splitter.lastIndex); 1092 } 1093 1094 // Step 19.d.iii. 1095 if (e === p) { 1096 q = unicodeMatching ? AdvanceStringIndex(S, q) : q + 1; 1097 continue; 1098 } 1099 1100 // Steps 19.d.iv.1-3. 1101 DefineDataProperty(A, lengthA, Substring(S, p, q - p)); 1102 1103 // Step 19.d.iv.4. 1104 lengthA++; 1105 1106 // Step 19.d.iv.5. 1107 if (lengthA === lim) { 1108 return A; 1109 } 1110 1111 // Step 19.d.iv.6. 1112 p = e; 1113 1114 if (z !== null) { 1115 // Steps 19.d.iv.7-8. 1116 var numberOfCaptures = std_Math_max(ToLength(z.length) - 1, 0); 1117 1118 // Step 19.d.iv.9. 1119 var i = 1; 1120 1121 // Step 19.d.iv.10. 1122 while (i <= numberOfCaptures) { 1123 // Steps 19.d.iv.10.a-b. 1124 DefineDataProperty(A, lengthA, z[i]); 1125 1126 // Step 19.d.iv.10.c. 1127 i++; 1128 1129 // Step 19.d.iv.10.d. 1130 lengthA++; 1131 1132 // Step 19.d.iv.10.e. 1133 if (lengthA === lim) { 1134 return A; 1135 } 1136 } 1137 } 1138 1139 // Step 19.d.iv.11. 1140 q = p; 1141 } 1142 1143 // Steps 20-22. 1144 if (p >= size) { 1145 DefineDataProperty(A, lengthA, ""); 1146 } else { 1147 DefineDataProperty(A, lengthA, Substring(S, p, size - p)); 1148 } 1149 1150 // Step 23. 1151 return A; 1152 } 1153 1154 // ES6 21.2.5.2. 1155 // NOTE: This is not RegExpExec (21.2.5.2.1). 1156 function RegExp_prototype_Exec(string) { 1157 // Steps 1-3. 1158 var R = this; 1159 if (!IsObject(R) || !IsRegExpObject(R)) { 1160 return callFunction( 1161 CallRegExpMethodIfWrapped, 1162 R, 1163 string, 1164 "RegExp_prototype_Exec" 1165 ); 1166 } 1167 1168 // Steps 4-5. 1169 var S = ToString(string); 1170 1171 // Step 6. 1172 return RegExpBuiltinExec(R, S); 1173 } 1174 1175 // ES6 21.2.5.13. 1176 function RegExpTest(string) { 1177 // Steps 1-2. 1178 var R = this; 1179 if (!IsObject(R)) { 1180 ThrowTypeError(JSMSG_OBJECT_REQUIRED, R === null ? "null" : typeof R); 1181 } 1182 1183 // Steps 3-4. 1184 var S = ToString(string); 1185 1186 // Steps 5-6. 1187 return RegExpExecForTest(R, S); 1188 } 1189 1190 // ES 2016 draft Mar 25, 2016 21.2.4.2. 1191 function $RegExpSpecies() { 1192 // Step 1. 1193 return this; 1194 } 1195 SetCanonicalName($RegExpSpecies, "get [Symbol.species]"); 1196 1197 // String.prototype.matchAll proposal. 1198 // 1199 // RegExp.prototype [ @@matchAll ] ( string ) 1200 function RegExpMatchAll(string) { 1201 // Step 1. 1202 var rx = this; 1203 1204 // Step 2. 1205 if (!IsObject(rx)) { 1206 ThrowTypeError(JSMSG_OBJECT_REQUIRED, rx === null ? "null" : typeof rx); 1207 } 1208 1209 // Step 3. 1210 var str = ToString(string); 1211 1212 // Step 4. 1213 var builtinCtor = GetBuiltinConstructor("RegExp"); 1214 var C = SpeciesConstructor(rx, builtinCtor); 1215 1216 var source, flags, matcher, lastIndex; 1217 if (IsOptimizableRegExpObject(rx) && C === builtinCtor) { 1218 // Step 5, 9-12. 1219 source = UnsafeGetStringFromReservedSlot(rx, REGEXP_SOURCE_SLOT); 1220 flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT); 1221 #ifdef NIGHTLY_BUILD 1222 assert(!!(flags & REGEXP_LEGACY_FEATURES_ENABLED_FLAG), 1223 "Legacy features must be enabled in optimized path"); 1224 #endif 1225 1226 // Step 6. 1227 matcher = rx; 1228 1229 // Step 7. 1230 lastIndex = ToLength(rx.lastIndex); 1231 1232 // Step 8 (not applicable for the optimized path). 1233 } else { 1234 // Step 5. 1235 source = ""; 1236 flags = ToString(rx.flags); 1237 1238 // Step 6. 1239 matcher = constructContentFunction(C, C, rx, flags); 1240 1241 // Steps 7-8. 1242 matcher.lastIndex = ToLength(rx.lastIndex); 1243 1244 // Steps 9-12. 1245 flags = 1246 (callFunction(std_String_includes, flags, "g") ? REGEXP_GLOBAL_FLAG : 0) | 1247 (callFunction(std_String_includes, flags, "u") ? REGEXP_UNICODE_FLAG : 0) | 1248 (callFunction(std_String_includes, flags, "v") ? REGEXP_UNICODESETS_FLAG : 0); 1249 1250 // Take the non-optimized path. 1251 lastIndex = REGEXP_STRING_ITERATOR_LASTINDEX_SLOW; 1252 } 1253 1254 // Step 13. 1255 return CreateRegExpStringIterator(matcher, str, source, flags, lastIndex); 1256 } 1257 1258 // String.prototype.matchAll proposal. 1259 // 1260 // CreateRegExpStringIterator ( R, S, global, fullUnicode ) 1261 function CreateRegExpStringIterator(regexp, string, source, flags, lastIndex) { 1262 // Step 1. 1263 assert(typeof string === "string", "|string| is a string value"); 1264 1265 // Steps 2-3. 1266 assert(typeof flags === "number", "|flags| is a number value"); 1267 1268 assert(typeof source === "string", "|source| is a string value"); 1269 assert(typeof lastIndex === "number", "|lastIndex| is a number value"); 1270 1271 // Steps 4-9. 1272 var iterator = NewRegExpStringIterator(); 1273 UnsafeSetReservedSlot(iterator, REGEXP_STRING_ITERATOR_REGEXP_SLOT, regexp); 1274 UnsafeSetReservedSlot(iterator, REGEXP_STRING_ITERATOR_STRING_SLOT, string); 1275 UnsafeSetReservedSlot(iterator, REGEXP_STRING_ITERATOR_SOURCE_SLOT, source); 1276 UnsafeSetReservedSlot(iterator, REGEXP_STRING_ITERATOR_FLAGS_SLOT, flags | 0); 1277 UnsafeSetReservedSlot( 1278 iterator, 1279 REGEXP_STRING_ITERATOR_LASTINDEX_SLOT, 1280 lastIndex 1281 ); 1282 1283 // Step 10. 1284 return iterator; 1285 } 1286 1287 // String.prototype.matchAll proposal. 1288 // 1289 // %RegExpStringIteratorPrototype%.next ( ) 1290 function RegExpStringIteratorNext() { 1291 // Steps 1-3. 1292 var obj = this; 1293 if (!IsObject(obj) || (obj = GuardToRegExpStringIterator(obj)) === null) { 1294 return callFunction( 1295 CallRegExpStringIteratorMethodIfWrapped, 1296 this, 1297 "RegExpStringIteratorNext" 1298 ); 1299 } 1300 1301 var result = { value: undefined, done: false }; 1302 1303 // Step 4. 1304 var lastIndex = UnsafeGetReservedSlot( 1305 obj, 1306 REGEXP_STRING_ITERATOR_LASTINDEX_SLOT 1307 ); 1308 if (lastIndex === REGEXP_STRING_ITERATOR_LASTINDEX_DONE) { 1309 result.done = true; 1310 return result; 1311 } 1312 1313 // Step 5. 1314 var regexp = UnsafeGetObjectFromReservedSlot( 1315 obj, 1316 REGEXP_STRING_ITERATOR_REGEXP_SLOT 1317 ); 1318 1319 // Step 6. 1320 var string = UnsafeGetStringFromReservedSlot( 1321 obj, 1322 REGEXP_STRING_ITERATOR_STRING_SLOT 1323 ); 1324 1325 // Steps 7-8. 1326 var flags = UnsafeGetInt32FromReservedSlot( 1327 obj, 1328 REGEXP_STRING_ITERATOR_FLAGS_SLOT 1329 ); 1330 var global = !!(flags & REGEXP_GLOBAL_FLAG); 1331 var fullUnicode = !!(flags & REGEXP_UNICODE_FLAG) || !!(flags & REGEXP_UNICODESETS_FLAG); 1332 1333 if (lastIndex >= 0) { 1334 assert(IsRegExpObject(regexp), "|regexp| is a RegExp object"); 1335 1336 var source = UnsafeGetStringFromReservedSlot( 1337 obj, 1338 REGEXP_STRING_ITERATOR_SOURCE_SLOT 1339 ); 1340 if ( 1341 IsRegExpPrototypeOptimizable() && 1342 UnsafeGetStringFromReservedSlot(regexp, REGEXP_SOURCE_SLOT) === source && 1343 UnsafeGetInt32FromReservedSlot(regexp, REGEXP_FLAGS_SLOT) === flags 1344 ) { 1345 // Step 9 (Inlined RegExpBuiltinExec). 1346 var globalOrSticky = !!( 1347 flags & 1348 (REGEXP_GLOBAL_FLAG | REGEXP_STICKY_FLAG) 1349 ); 1350 if (!globalOrSticky) { 1351 lastIndex = 0; 1352 } 1353 1354 var match = 1355 lastIndex <= string.length 1356 ? RegExpMatcher(regexp, string, lastIndex) 1357 : null; 1358 1359 // Step 10. 1360 if (match === null) { 1361 // Step 10.a. 1362 UnsafeSetReservedSlot( 1363 obj, 1364 REGEXP_STRING_ITERATOR_LASTINDEX_SLOT, 1365 REGEXP_STRING_ITERATOR_LASTINDEX_DONE 1366 ); 1367 1368 // Step 10.b. 1369 result.done = true; 1370 return result; 1371 } 1372 1373 // Step 11.a. 1374 if (global) { 1375 // Step 11.a.i. 1376 var matchLength = match[0].length; 1377 lastIndex = match.index + matchLength; 1378 1379 // Step 11.a.ii. 1380 if (matchLength === 0) { 1381 // Steps 11.a.ii.1-3. 1382 lastIndex = fullUnicode 1383 ? AdvanceStringIndex(string, lastIndex) 1384 : lastIndex + 1; 1385 } 1386 1387 UnsafeSetReservedSlot( 1388 obj, 1389 REGEXP_STRING_ITERATOR_LASTINDEX_SLOT, 1390 lastIndex 1391 ); 1392 } else { 1393 // Step 11.b.i. 1394 UnsafeSetReservedSlot( 1395 obj, 1396 REGEXP_STRING_ITERATOR_LASTINDEX_SLOT, 1397 REGEXP_STRING_ITERATOR_LASTINDEX_DONE 1398 ); 1399 } 1400 1401 // Steps 11.a.iii and 11.b.ii. 1402 result.value = match; 1403 return result; 1404 } 1405 1406 // Reify the RegExp object. 1407 var newFlags = flags & ~REGEXP_LEGACY_FEATURES_ENABLED_FLAG; 1408 regexp = RegExpConstructRaw(source, newFlags, true); 1409 regexp.lastIndex = lastIndex; 1410 UnsafeSetReservedSlot(obj, REGEXP_STRING_ITERATOR_REGEXP_SLOT, regexp); 1411 1412 // Mark the iterator as no longer optimizable. 1413 UnsafeSetReservedSlot( 1414 obj, 1415 REGEXP_STRING_ITERATOR_LASTINDEX_SLOT, 1416 REGEXP_STRING_ITERATOR_LASTINDEX_SLOW 1417 ); 1418 } 1419 1420 // Step 9. 1421 var match = RegExpExec(regexp, string); 1422 1423 // Step 10. 1424 if (match === null) { 1425 // Step 10.a. 1426 UnsafeSetReservedSlot( 1427 obj, 1428 REGEXP_STRING_ITERATOR_LASTINDEX_SLOT, 1429 REGEXP_STRING_ITERATOR_LASTINDEX_DONE 1430 ); 1431 1432 // Step 10.b. 1433 result.done = true; 1434 return result; 1435 } 1436 1437 // Step 11.a. 1438 if (global) { 1439 // Step 11.a.i. 1440 var matchStr = ToString(match[0]); 1441 1442 // Step 11.a.ii. 1443 if (matchStr.length === 0) { 1444 // Step 11.a.ii.1. 1445 var thisIndex = ToLength(regexp.lastIndex); 1446 1447 // Step 11.a.ii.2. 1448 var nextIndex = fullUnicode 1449 ? AdvanceStringIndex(string, thisIndex) 1450 : thisIndex + 1; 1451 1452 // Step 11.a.ii.3. 1453 regexp.lastIndex = nextIndex; 1454 } 1455 } else { 1456 // Step 11.b.i. 1457 UnsafeSetReservedSlot( 1458 obj, 1459 REGEXP_STRING_ITERATOR_LASTINDEX_SLOT, 1460 REGEXP_STRING_ITERATOR_LASTINDEX_DONE 1461 ); 1462 } 1463 1464 // Steps 11.a.iii and 11.b.ii. 1465 result.value = match; 1466 return result; 1467 } 1468 1469 // ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624 1470 // 7.2.8 IsRegExp ( argument ) 1471 function IsRegExp(argument) { 1472 // Step 1. 1473 if (!IsObject(argument)) { 1474 return false; 1475 } 1476 1477 // Step 2. 1478 var matcher = argument[GetBuiltinSymbol("match")]; 1479 1480 // Step 3. 1481 if (matcher !== undefined) { 1482 return !!matcher; 1483 } 1484 1485 // Steps 4-5. 1486 return IsPossiblyWrappedRegExpObject(argument); 1487 }