Bug 1867190 - Add prefs for PHC probablities r=glandium
[gecko.git] / js / src / builtin / RegExp.js
blob8ed73ad0ec454e5bb162fe139f14e2d86636132a
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/. */
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   }
16   // Step 3.
17   var result = "";
19   // Steps 4-5.
20   if (R.hasIndices) {
21     result += "d";
22   }
24   // Steps 6-7.
25   if (R.global) {
26     result += "g";
27   }
29   // Steps 8-9.
30   if (R.ignoreCase) {
31     result += "i";
32   }
34   // Steps 10-11.
35   if (R.multiline) {
36     result += "m";
37   }
39   // Steps 12-13.
40   if (R.dotAll) {
41     result += "s";
42   }
44   // Steps 14-15.
45   if (R.unicode) {
46     result += "u";
47   }
49   // Steps 16-17.
50   if (R.unicodeSets) {
51     result += "v";
52   }
54   // Steps 18-19
55   if (R.sticky) {
56     result += "y";
57   }
59   // Step 20.
60   return result;
62 SetCanonicalName($RegExpFlagsGetter, "get flags");
64 // ES 2017 draft 40edb3a95a475c1b251141ac681b8793129d9a6d 21.2.5.14.
65 function $RegExpToString() {
66   // Step 1.
67   var R = this;
69   // Step 2.
70   if (!IsObject(R)) {
71     ThrowTypeError(JSMSG_OBJECT_REQUIRED, R === null ? "null" : typeof R);
72   }
74   // Step 3.
75   var pattern = ToString(R.source);
77   // Step 4.
78   var flags = ToString(R.flags);
80   // Steps 5-6.
81   return "/" + pattern + "/" + flags;
83 SetCanonicalName($RegExpToString, "toString");
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");
90   // Step 2.
91   assert(
92     index >= 0 && index <= MAX_NUMERIC_INDEX,
93     "Expected integer as 2nd argument"
94   );
96   // Step 3 (skipped).
98   // Step 4 (skipped).
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;
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;
114   // Step 2.
115   if (!IsObject(rx)) {
116     ThrowTypeError(JSMSG_OBJECT_REQUIRED, rx === null ? "null" : typeof rx);
117   }
119   // Step 3.
120   var S = ToString(string);
122   // Optimized paths for simple cases.
123   if (IsRegExpMethodOptimizable(rx)) {
124     // Step 4.
125     var flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT);
126     var global = !!(flags & REGEXP_GLOBAL_FLAG);
128     if (global) {
129       // Step 6.a.
130       var fullUnicode = !!(flags & REGEXP_UNICODE_FLAG);
132       // Steps 6.b-e.
133       return RegExpGlobalMatchOpt(rx, S, fullUnicode);
134     }
136     // Step 5.
137     return RegExpBuiltinExec(rx, S);
138   }
140   // Stes 4-6
141   return RegExpMatchSlowPath(rx, S);
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);
151   // Step 5.
152   if (!callFunction(std_String_includes, flags, "g")) {
153     return RegExpExec(rx, S);
154   }
156   // Step 6.a.
157   var fullUnicode = callFunction(std_String_includes, flags, "u");
159   // Step 6.b.
160   rx.lastIndex = 0;
162   // Step 6.c.
163   var A = [];
165   // Step 6.d.
166   var n = 0;
168   // Step 6.e.
169   while (true) {
170     // Step 6.e.i.
171     var result = RegExpExec(rx, S);
173     // Step 6.e.ii.
174     if (result === null) {
175       return n === 0 ? null : A;
176     }
178     // Step 6.e.iii.1.
179     var matchStr = ToString(result[0]);
181     // Step 6.e.iii.2.
182     DefineDataProperty(A, n, matchStr);
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     }
192     // Step 6.e.iii.4.
193     n++;
194   }
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;
205   // Step 6.c.
206   var A = [];
208   // Step 6.d.
209   var n = 0;
211   var lengthS = S.length;
213   // Step 6.e.
214   while (true) {
215     // Step 6.e.i.
216     var position = RegExpSearcher(rx, S, lastIndex);
218     // Step 6.e.ii.
219     if (position === -1) {
220       return n === 0 ? null : A;
221     }
223     lastIndex = RegExpSearcherLastLimit(S);
225     // Step 6.e.iii.1.
226     var matchStr = Substring(S, position, lastIndex - position);
228     // Step 6.e.iii.2.
229     DefineDataProperty(A, n, matchStr);
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     }
241     // Step 6.e.iii.5.
242     n++;
243   }
246 // Checks if following properties and getters are not modified, and accessing
247 // them not observed by content script:
248 //   * flags
249 //   * hasIndices
250 //   * global
251 //   * ignoreCase
252 //   * multiline
253 //   * dotAll
254 //   * sticky
255 //   * unicode
256 //   * unicodeSets
257 //   * exec
258 //   * lastIndex
259 function IsRegExpMethodOptimizable(rx) {
260   if (!IsRegExpObject(rx)) {
261     return false;
262   }
264   var RegExpProto = GetBuiltinPrototype("RegExp");
265   // If RegExpPrototypeOptimizable and RegExpInstanceOptimizable succeed,
266   // `RegExpProto.exec` is guaranteed to be data properties.
267   return (
268     RegExpPrototypeOptimizable(RegExpProto) &&
269     RegExpInstanceOptimizable(rx, RegExpProto) &&
270     RegExpProto.exec === RegExp_prototype_Exec
271   );
274 // ES2023 draft rev 2c78e6f6b5bc6bfbf79dd8a12a9593e5b57afcd2
275 // 22.2.5.11 RegExp.prototype [ @@replace ] ( string, replaceValue )
276 function RegExpReplace(string, replaceValue) {
277   // Step 1.
278   var rx = this;
280   // Step 2.
281   if (!IsObject(rx)) {
282     ThrowTypeError(JSMSG_OBJECT_REQUIRED, rx === null ? "null" : typeof rx);
283   }
285   // Step 3.
286   var S = ToString(string);
288   // Step 4.
289   var lengthS = S.length;
291   // Step 5.
292   var functionalReplace = IsCallable(replaceValue);
294   // Step 6.
295   var firstDollarIndex = -1;
296   if (!functionalReplace) {
297     // Step 6.a.
298     replaceValue = ToString(replaceValue);
300     // Skip if replaceValue is an empty string or a single character.
301     // A single character string may contain "$", but that cannot be a
302     // substitution.
303     if (replaceValue.length > 1) {
304       firstDollarIndex = GetFirstDollarIndex(replaceValue);
305     }
306   }
308   // Optimized paths.
309   if (IsRegExpMethodOptimizable(rx)) {
310     // Step 7.
311     var flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT);
313     // Step 9.
314     var global = !!(flags & REGEXP_GLOBAL_FLAG);
316     // Steps 9-17.
317     if (global) {
318       if (functionalReplace) {
319         // For large strings check if the replacer function is
320         // applicable for the elem-base optimization.
321         if (lengthS > 5000) {
322           var elemBase = GetElemBaseForLambda(replaceValue);
323           if (IsObject(elemBase)) {
324             return RegExpGlobalReplaceOptElemBase(
325               rx,
326               S,
327               lengthS,
328               replaceValue,
329               flags,
330               elemBase
331             );
332           }
333         }
334         return RegExpGlobalReplaceOptFunc(rx, S, lengthS, replaceValue, flags);
335       }
336       if (firstDollarIndex !== -1) {
337         return RegExpGlobalReplaceOptSubst(
338           rx,
339           S,
340           lengthS,
341           replaceValue,
342           flags,
343           firstDollarIndex
344         );
345       }
346       return RegExpGlobalReplaceOptSimple(rx, S, lengthS, replaceValue, flags);
347     }
349     if (functionalReplace) {
350       return RegExpLocalReplaceOptFunc(rx, S, lengthS, replaceValue);
351     }
352     if (firstDollarIndex !== -1) {
353       return RegExpLocalReplaceOptSubst(
354         rx,
355         S,
356         lengthS,
357         replaceValue,
358         firstDollarIndex
359       );
360     }
361     return RegExpLocalReplaceOptSimple(rx, S, lengthS, replaceValue);
362   }
364   // Steps 7-17.
365   return RegExpReplaceSlowPath(
366     rx,
367     S,
368     lengthS,
369     replaceValue,
370     functionalReplace,
371     firstDollarIndex
372   );
375 // ES2023 draft rev 2c78e6f6b5bc6bfbf79dd8a12a9593e5b57afcd2
376 // 22.2.5.11 RegExp.prototype [ @@replace ] ( string, replaceValue )
377 // Steps 7-17.
378 // Slow path for @@replace.
379 function RegExpReplaceSlowPath(
380   rx,
381   S,
382   lengthS,
383   replaceValue,
384   functionalReplace,
385   firstDollarIndex
386 ) {
387   // Step 7.
388   var flags = ToString(rx.flags);
390   // Step 8.
391   var global = callFunction(std_String_includes, flags, "g");
393   // Step 9.
394   var fullUnicode = false;
395   if (global) {
396     // Step 9.a.
397     fullUnicode = callFunction(std_String_includes, flags, "u");
399     // Step 9.b.
400     rx.lastIndex = 0;
401   }
403   // Step 10.
404   var results = new_List();
405   var nResults = 0;
407   // Steps 11-12.
408   while (true) {
409     // Step 12.a.
410     var result = RegExpExec(rx, S);
412     // Step 12.b.
413     if (result === null) {
414       break;
415     }
417     // Step 12.c.i.
418     DefineDataProperty(results, nResults++, result);
420     // Step 12.c.ii.
421     if (!global) {
422       break;
423     }
425     // Step 12.c.iii.1.
426     var matchStr = ToString(result[0]);
428     // Step 12.c.iii.2.
429     if (matchStr === "") {
430       var lastIndex = ToLength(rx.lastIndex);
431       rx.lastIndex = fullUnicode
432         ? AdvanceStringIndex(S, lastIndex)
433         : lastIndex + 1;
434     }
435   }
437   // Step 13.
438   var accumulatedResult = "";
440   // Step 14.
441   var nextSourcePosition = 0;
443   // Step 15.
444   for (var i = 0; i < nResults; i++) {
445     result = results[i];
447     // Steps 15.a-b.
448     var nCaptures = std_Math_max(ToLength(result.length) - 1, 0);
450     // Step 15.c.
451     var matched = ToString(result[0]);
453     // Step 15.d.
454     var matchLength = matched.length;
456     // Steps 15.e-f.
457     var position = std_Math_max(
458       std_Math_min(ToInteger(result.index), lengthS),
459       0
460     );
462     var replacement;
463     if (functionalReplace || firstDollarIndex !== -1) {
464       // Steps 15.g-l.
465       replacement = RegExpGetComplexReplacement(
466         result,
467         matched,
468         S,
469         position,
470         nCaptures,
471         replaceValue,
472         functionalReplace,
473         firstDollarIndex
474       );
475     } else {
476       // Steps 15.g, 15.i, 15.i.iv.
477       // We don't need captures array, but ToString is visible to script.
478       for (var n = 1; n <= nCaptures; n++) {
479         // Steps 15.i.i-ii.
480         var capN = result[n];
482         // Step 15.i.ii.
483         if (capN !== undefined) {
484           ToString(capN);
485         }
486       }
488       // Steps 15.j, 15.l.i.
489       // We don't need namedCaptures, but ToObject is visible to script.
490       var namedCaptures = result.groups;
491       if (namedCaptures !== undefined) {
492         ToObject(namedCaptures);
493       }
495       // Step 15.l.ii.
496       replacement = replaceValue;
497     }
499     // Step 15.m.
500     if (position >= nextSourcePosition) {
501       // Step 15.m.ii.
502       accumulatedResult +=
503         Substring(S, nextSourcePosition, position - nextSourcePosition) +
504         replacement;
506       // Step 15.m.iii.
507       nextSourcePosition = position + matchLength;
508     }
509   }
511   // Step 16.
512   if (nextSourcePosition >= lengthS) {
513     return accumulatedResult;
514   }
516   // Step 17.
517   return (
518     accumulatedResult +
519     Substring(S, nextSourcePosition, lengthS - nextSourcePosition)
520   );
523 // ES2023 draft rev 2c78e6f6b5bc6bfbf79dd8a12a9593e5b57afcd2
524 // 22.2.5.11 RegExp.prototype [ @@replace ] ( string, replaceValue )
525 // https://tc39.es/ecma262/#sec-regexp.prototype-@@replace
526 // Steps 15.g-l.
527 // Calculates functional/substitution replacement from match result.
528 // Used in the following functions:
529 //   * RegExpReplaceSlowPath
530 function RegExpGetComplexReplacement(
531   result,
532   matched,
533   S,
534   position,
535   nCaptures,
536   replaceValue,
537   functionalReplace,
538   firstDollarIndex
539 ) {
540   // Step 15.g.
541   var captures = new_List();
542   var capturesLength = 0;
544   // Step 15.k.i (reordered).
545   DefineDataProperty(captures, capturesLength++, matched);
547   // Steps 15.h, 15.i, 15.i.v.
548   for (var n = 1; n <= nCaptures; n++) {
549     // Step 15.i.i.
550     var capN = result[n];
552     // Step 15.i.ii.
553     if (capN !== undefined) {
554       capN = ToString(capN);
555     }
557     // Step 15.i.iii.
558     DefineDataProperty(captures, capturesLength++, capN);
559   }
561   // Step 15.j.
562   var namedCaptures = result.groups;
564   // Step 15.k.
565   if (functionalReplace) {
566     // For `nCaptures` <= 4 case, call `replaceValue` directly, otherwise
567     // use `std_Function_apply` with all arguments stored in `captures`.
568     if (namedCaptures === undefined) {
569       switch (nCaptures) {
570         case 0:
571           return ToString(
572             callContentFunction(
573               replaceValue,
574               undefined,
575               SPREAD(captures, 1),
576               position,
577               S
578             )
579           );
580         case 1:
581           return ToString(
582             callContentFunction(
583               replaceValue,
584               undefined,
585               SPREAD(captures, 2),
586               position,
587               S
588             )
589           );
590         case 2:
591           return ToString(
592             callContentFunction(
593               replaceValue,
594               undefined,
595               SPREAD(captures, 3),
596               position,
597               S
598             )
599           );
600         case 3:
601           return ToString(
602             callContentFunction(
603               replaceValue,
604               undefined,
605               SPREAD(captures, 4),
606               position,
607               S
608             )
609           );
610         case 4:
611           return ToString(
612             callContentFunction(
613               replaceValue,
614               undefined,
615               SPREAD(captures, 5),
616               position,
617               S
618             )
619           );
620       }
621     }
623     // Steps 15.k.ii-vi.
624     DefineDataProperty(captures, capturesLength++, position);
625     DefineDataProperty(captures, capturesLength++, S);
626     if (namedCaptures !== undefined) {
627       DefineDataProperty(captures, capturesLength++, namedCaptures);
628     }
629     return ToString(
630       callFunction(std_Function_apply, replaceValue, undefined, captures)
631     );
632   }
634   // Step 15.l.
635   if (namedCaptures !== undefined) {
636     namedCaptures = ToObject(namedCaptures);
637   }
638   return RegExpGetSubstitution(
639     captures,
640     S,
641     position,
642     replaceValue,
643     firstDollarIndex,
644     namedCaptures
645   );
648 // ES2023 draft rev 2c78e6f6b5bc6bfbf79dd8a12a9593e5b57afcd2
649 // 22.2.5.11 RegExp.prototype [ @@replace ] ( string, replaceValue )
650 // https://tc39.es/ecma262/#sec-regexp.prototype-@@replace
651 // Steps 15.g-k.
652 // Calculates functional replacement from match result.
653 // Used in the following functions:
654 //   * RegExpGlobalReplaceOptFunc
655 //   * RegExpGlobalReplaceOptElemBase
656 //   * RegExpLocalReplaceOptFunc
657 function RegExpGetFunctionalReplacement(result, S, position, replaceValue) {
658   // For `nCaptures` <= 4 case, call `replaceValue` directly, otherwise
659   // use `std_Function_apply` with all arguments stored in `captures`.
660   assert(result.length >= 1, "RegExpMatcher doesn't return an empty array");
661   var nCaptures = result.length - 1;
663   // Step 15.j (reordered)
664   var namedCaptures = result.groups;
666   if (namedCaptures === undefined) {
667     switch (nCaptures) {
668       case 0:
669         return ToString(
670           callContentFunction(
671             replaceValue,
672             undefined,
673             SPREAD(result, 1),
674             position,
675             S
676           )
677         );
678       case 1:
679         return ToString(
680           callContentFunction(
681             replaceValue,
682             undefined,
683             SPREAD(result, 2),
684             position,
685             S
686           )
687         );
688       case 2:
689         return ToString(
690           callContentFunction(
691             replaceValue,
692             undefined,
693             SPREAD(result, 3),
694             position,
695             S
696           )
697         );
698       case 3:
699         return ToString(
700           callContentFunction(
701             replaceValue,
702             undefined,
703             SPREAD(result, 4),
704             position,
705             S
706           )
707         );
708       case 4:
709         return ToString(
710           callContentFunction(
711             replaceValue,
712             undefined,
713             SPREAD(result, 5),
714             position,
715             S
716           )
717         );
718     }
719   }
721   // Steps 15.g-i, 15.k.i-ii.
722   var captures = new_List();
723   for (var n = 0; n <= nCaptures; n++) {
724     assert(
725       typeof result[n] === "string" || result[n] === undefined,
726       "RegExpMatcher returns only strings and undefined"
727     );
728     DefineDataProperty(captures, n, result[n]);
729   }
731   // Step 15.k.iii.
732   DefineDataProperty(captures, nCaptures + 1, position);
733   DefineDataProperty(captures, nCaptures + 2, S);
735   // Step 15.k.iv.
736   if (namedCaptures !== undefined) {
737     DefineDataProperty(captures, nCaptures + 3, namedCaptures);
738   }
740   // Steps 15.k.v-vi.
741   return ToString(
742     callFunction(std_Function_apply, replaceValue, undefined, captures)
743   );
746 // ES2023 draft rev 2c78e6f6b5bc6bfbf79dd8a12a9593e5b57afcd2
747 // 22.2.5.11 RegExp.prototype [ @@replace ] ( string, replaceValue )
748 // Steps 9.b-17.
749 // Optimized path for @@replace with the following conditions:
750 //   * global flag is true
751 //   * replaceValue is a string without "$"
752 function RegExpGlobalReplaceOptSimple(rx, S, lengthS, replaceValue, flags) {
753   // Step 9.a.
754   var fullUnicode = !!(flags & REGEXP_UNICODE_FLAG);
756   // Step 9.b.
757   var lastIndex = 0;
758   rx.lastIndex = 0;
760   // Step 13 (reordered).
761   var accumulatedResult = "";
763   // Step 14 (reordered).
764   var nextSourcePosition = 0;
766   // Step 12.
767   while (true) {
768     // Step 12.a.
769     var position = RegExpSearcher(rx, S, lastIndex);
771     // Step 12.b.
772     if (position === -1) {
773       break;
774     }
776     lastIndex = RegExpSearcherLastLimit(S);
778     // Step 15.m.ii.
779     accumulatedResult +=
780       Substring(S, nextSourcePosition, position - nextSourcePosition) +
781       replaceValue;
783     // Step 15.m.iii.
784     nextSourcePosition = lastIndex;
786     // Step 12.c.iii.2.
787     if (lastIndex === position) {
788       lastIndex = fullUnicode
789         ? AdvanceStringIndex(S, lastIndex)
790         : lastIndex + 1;
791       if (lastIndex > lengthS) {
792         break;
793       }
794     }
795   }
797   // Step 16.
798   if (nextSourcePosition >= lengthS) {
799     return accumulatedResult;
800   }
802   // Step 17.
803   return (
804     accumulatedResult +
805     Substring(S, nextSourcePosition, lengthS - nextSourcePosition)
806   );
809 // ES2023 draft rev 2c78e6f6b5bc6bfbf79dd8a12a9593e5b57afcd2
810 // 22.2.5.11 RegExp.prototype [ @@replace ] ( string, replaceValue )
811 // Steps 7-17.
812 // Optimized path for @@replace.
814 // Conditions:
815 //   * global flag is true
816 //   * replaceValue is a function
817 #define FUNC_NAME RegExpGlobalReplaceOptFunc
818 #define FUNCTIONAL
819 #include "RegExpGlobalReplaceOpt.h.js"
820 #undef FUNCTIONAL
821 #undef FUNC_NAME
822 /* global RegExpGlobalReplaceOptFunc */
824 // Conditions:
825 //   * global flag is true
826 //   * replaceValue is a function that returns element of an object
827 #define FUNC_NAME RegExpGlobalReplaceOptElemBase
828 #define ELEMBASE
829 #include "RegExpGlobalReplaceOpt.h.js"
830 #undef ELEMBASE
831 #undef FUNC_NAME
832 /* global RegExpGlobalReplaceOptElemBase */
834 // Conditions:
835 //   * global flag is true
836 //   * replaceValue is a string with "$"
837 #define FUNC_NAME RegExpGlobalReplaceOptSubst
838 #define SUBSTITUTION
839 #include "RegExpGlobalReplaceOpt.h.js"
840 #undef SUBSTITUTION
841 #undef FUNC_NAME
842 /* global RegExpGlobalReplaceOptSubst */
844 // Conditions:
845 //   * global flag is false
846 //   * replaceValue is a string without "$"
847 #define FUNC_NAME RegExpLocalReplaceOptSimple
848 #define SIMPLE
849 #include "RegExpLocalReplaceOpt.h.js"
850 #undef SIMPLE
851 #undef FUNC_NAME
852 /* global RegExpLocalReplaceOptSimple */
854 // Conditions:
855 //   * global flag is false
856 //   * replaceValue is a function
857 #define FUNC_NAME RegExpLocalReplaceOptFunc
858 #define FUNCTIONAL
859 #include "RegExpLocalReplaceOpt.h.js"
860 #undef FUNCTIONAL
861 #undef FUNC_NAME
862 /* global RegExpLocalReplaceOptFunc */
864 // Conditions:
865 //   * global flag is false
866 //   * replaceValue is a string with "$"
867 #define FUNC_NAME RegExpLocalReplaceOptSubst
868 #define SUBSTITUTION
869 #include "RegExpLocalReplaceOpt.h.js"
870 #undef SUBSTITUTION
871 #undef FUNC_NAME
872 /* global RegExpLocalReplaceOptSubst */
874 // ES2017 draft rev 6390c2f1b34b309895d31d8c0512eac8660a0210
875 // 21.2.5.9 RegExp.prototype [ @@search ] ( string )
876 function RegExpSearch(string) {
877   // Step 1.
878   var rx = this;
880   // Step 2.
881   if (!IsObject(rx)) {
882     ThrowTypeError(JSMSG_OBJECT_REQUIRED, rx === null ? "null" : typeof rx);
883   }
885   // Step 3.
886   var S = ToString(string);
888   // Step 4.
889   var previousLastIndex = rx.lastIndex;
891   // Step 5.
892   var lastIndexIsZero = SameValue(previousLastIndex, 0);
893   if (!lastIndexIsZero) {
894     rx.lastIndex = 0;
895   }
897   if (IsRegExpMethodOptimizable(rx) && S.length < 0x7fff) {
898     // Step 6.
899     var result = RegExpSearcher(rx, S, 0);
901     // We need to consider two cases:
902     //
903     // 1. Neither global nor sticky is set:
904     // RegExpBuiltinExec doesn't modify lastIndex for local RegExps, that
905     // means |SameValue(rx.lastIndex, 0)| is true after calling exec. The
906     // comparison in steps 7-8 |SameValue(rx.lastIndex, previousLastIndex)|
907     // is therefore equal to the already computed |lastIndexIsZero| value.
908     //
909     // 2. Global or sticky flag is set.
910     // RegExpBuiltinExec will always update lastIndex and we need to
911     // restore the property to its original value.
913     // Steps 7-8.
914     if (!lastIndexIsZero) {
915       rx.lastIndex = previousLastIndex;
916     } else {
917       var flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT);
918       if (flags & (REGEXP_GLOBAL_FLAG | REGEXP_STICKY_FLAG)) {
919         rx.lastIndex = previousLastIndex;
920       }
921     }
923     // Steps 9-10.
924     return result;
925   }
927   return RegExpSearchSlowPath(rx, S, previousLastIndex);
930 // ES2017 draft rev 6390c2f1b34b309895d31d8c0512eac8660a0210
931 // 21.2.5.9 RegExp.prototype [ @@search ] ( string )
932 // Steps 6-10.
933 function RegExpSearchSlowPath(rx, S, previousLastIndex) {
934   // Step 6.
935   var result = RegExpExec(rx, S);
937   // Step 7.
938   var currentLastIndex = rx.lastIndex;
940   // Step 8.
941   if (!SameValue(currentLastIndex, previousLastIndex)) {
942     rx.lastIndex = previousLastIndex;
943   }
945   // Step 9.
946   if (result === null) {
947     return -1;
948   }
950   // Step 10.
951   return result.index;
954 function IsRegExpSplitOptimizable(rx, C) {
955   if (!IsRegExpObject(rx)) {
956     return false;
957   }
959   var RegExpCtor = GetBuiltinConstructor("RegExp");
960   if (C !== RegExpCtor) {
961     return false;
962   }
964   var RegExpProto = RegExpCtor.prototype;
965   // If RegExpPrototypeOptimizable succeeds, `RegExpProto.exec` is guaranteed
966   // to be a data property.
967   return (
968     RegExpPrototypeOptimizable(RegExpProto) &&
969     RegExpInstanceOptimizable(rx, RegExpProto) &&
970     RegExpProto.exec === RegExp_prototype_Exec
971   );
974 // ES 2017 draft 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e 21.2.5.11.
975 function RegExpSplit(string, limit) {
976   // Step 1.
977   var rx = this;
979   // Step 2.
980   if (!IsObject(rx)) {
981     ThrowTypeError(JSMSG_OBJECT_REQUIRED, rx === null ? "null" : typeof rx);
982   }
984   // Step 3.
985   var S = ToString(string);
987   // Step 4.
988   var C = SpeciesConstructor(rx, GetBuiltinConstructor("RegExp"));
990   var optimizable =
991     IsRegExpSplitOptimizable(rx, C) &&
992     (limit === undefined || typeof limit === "number");
994   var flags, unicodeMatching, splitter;
995   if (optimizable) {
996     // Step 5.
997     flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT);
999     // Steps 6-7.
1000     unicodeMatching = !!(flags & REGEXP_UNICODE_FLAG);
1002     // Steps 8-10.
1003     // If split operation is optimizable, perform non-sticky match.
1004     if (flags & REGEXP_STICKY_FLAG) {
1005       var source = UnsafeGetStringFromReservedSlot(rx, REGEXP_SOURCE_SLOT);
1006       splitter = RegExpConstructRaw(source, flags & ~REGEXP_STICKY_FLAG);
1007     } else {
1008       splitter = rx;
1009     }
1010   } else {
1011     // Step 5.
1012     flags = ToString(rx.flags);
1014     // Steps 6-7.
1015     unicodeMatching = callFunction(std_String_includes, flags, "u");
1017     // Steps 8-9.
1018     var newFlags;
1019     if (callFunction(std_String_includes, flags, "y")) {
1020       newFlags = flags;
1021     } else {
1022       newFlags = flags + "y";
1023     }
1025     // Step 10.
1026     splitter = constructContentFunction(C, C, rx, newFlags);
1027   }
1029   // Step 11.
1030   var A = [];
1032   // Step 12.
1033   var lengthA = 0;
1035   // Step 13.
1036   var lim;
1037   if (limit === undefined) {
1038     lim = MAX_UINT32;
1039   } else {
1040     lim = limit >>> 0;
1041   }
1043   // Step 15.
1044   var p = 0;
1046   // Step 16.
1047   if (lim === 0) {
1048     return A;
1049   }
1051   // Step 14 (reordered).
1052   var size = S.length;
1054   // Step 17.
1055   if (size === 0) {
1056     // Step 17.a-b.
1057     if (optimizable) {
1058       if (RegExpSearcher(splitter, S, 0) !== -1) {
1059         return A;
1060       }
1061     } else {
1062       if (RegExpExec(splitter, S) !== null) {
1063         return A;
1064       }
1065     }
1067     // Step 17.d.
1068     DefineDataProperty(A, 0, S);
1070     // Step 17.e.
1071     return A;
1072   }
1074   // Step 18.
1075   var q = p;
1077   var optimizableNoCaptures = optimizable && !RegExpHasCaptureGroups(splitter, S);
1079   // Step 19.
1080   while (q < size) {
1081     var e, z;
1082     if (optimizableNoCaptures) {
1083       // If there are no capturing groups, avoid allocating the match result
1084       // object |z| (we set it to null). This is the only difference between
1085       // this branch and the |if (optimizable)| case below.
1087       // Step 19.a (skipped).
1088       // splitter.lastIndex is not used.
1090       // Steps 19.b-c.
1091       q = RegExpSearcher(splitter, S, q);
1092       if (q === -1 || q >= size) {
1093         break;
1094       }
1096       // Step 19.d.i.
1097       e = RegExpSearcherLastLimit(S);
1098       z = null;
1099     } else if (optimizable) {
1100       // Step 19.a (skipped).
1101       // splitter.lastIndex is not used.
1103       // Step 19.b.
1104       z = RegExpMatcher(splitter, S, q);
1106       // Step 19.c.
1107       if (z === null) {
1108         break;
1109       }
1111       // splitter.lastIndex is not updated.
1112       q = z.index;
1113       if (q >= size) {
1114         break;
1115       }
1117       // Step 19.d.i.
1118       e = q + z[0].length;
1119     } else {
1120       // Step 19.a.
1121       splitter.lastIndex = q;
1123       // Step 19.b.
1124       z = RegExpExec(splitter, S);
1126       // Step 19.c.
1127       if (z === null) {
1128         q = unicodeMatching ? AdvanceStringIndex(S, q) : q + 1;
1129         continue;
1130       }
1132       // Step 19.d.i.
1133       e = ToLength(splitter.lastIndex);
1134     }
1136     // Step 19.d.iii.
1137     if (e === p) {
1138       q = unicodeMatching ? AdvanceStringIndex(S, q) : q + 1;
1139       continue;
1140     }
1142     // Steps 19.d.iv.1-3.
1143     DefineDataProperty(A, lengthA, Substring(S, p, q - p));
1145     // Step 19.d.iv.4.
1146     lengthA++;
1148     // Step 19.d.iv.5.
1149     if (lengthA === lim) {
1150       return A;
1151     }
1153     // Step 19.d.iv.6.
1154     p = e;
1156     if (z !== null) {
1157       // Steps 19.d.iv.7-8.
1158       var numberOfCaptures = std_Math_max(ToLength(z.length) - 1, 0);
1160       // Step 19.d.iv.9.
1161       var i = 1;
1163       // Step 19.d.iv.10.
1164       while (i <= numberOfCaptures) {
1165         // Steps 19.d.iv.10.a-b.
1166         DefineDataProperty(A, lengthA, z[i]);
1168         // Step 19.d.iv.10.c.
1169         i++;
1171         // Step 19.d.iv.10.d.
1172         lengthA++;
1174         // Step 19.d.iv.10.e.
1175         if (lengthA === lim) {
1176           return A;
1177         }
1178       }
1179     }
1181     // Step 19.d.iv.11.
1182     q = p;
1183   }
1185   // Steps 20-22.
1186   if (p >= size) {
1187     DefineDataProperty(A, lengthA, "");
1188   } else {
1189     DefineDataProperty(A, lengthA, Substring(S, p, size - p));
1190   }
1192   // Step 23.
1193   return A;
1196 // ES6 21.2.5.2.
1197 // NOTE: This is not RegExpExec (21.2.5.2.1).
1198 function RegExp_prototype_Exec(string) {
1199   // Steps 1-3.
1200   var R = this;
1201   if (!IsObject(R) || !IsRegExpObject(R)) {
1202     return callFunction(
1203       CallRegExpMethodIfWrapped,
1204       R,
1205       string,
1206       "RegExp_prototype_Exec"
1207     );
1208   }
1210   // Steps 4-5.
1211   var S = ToString(string);
1213   // Step 6.
1214   return RegExpBuiltinExec(R, S);
1217 // ES6 21.2.5.13.
1218 function RegExpTest(string) {
1219   // Steps 1-2.
1220   var R = this;
1221   if (!IsObject(R)) {
1222     ThrowTypeError(JSMSG_OBJECT_REQUIRED, R === null ? "null" : typeof R);
1223   }
1225   // Steps 3-4.
1226   var S = ToString(string);
1228   // Steps 5-6.
1229   return RegExpExecForTest(R, S);
1232 // ES 2016 draft Mar 25, 2016 21.2.4.2.
1233 function $RegExpSpecies() {
1234   // Step 1.
1235   return this;
1237 SetCanonicalName($RegExpSpecies, "get [Symbol.species]");
1239 function IsRegExpMatchAllOptimizable(rx, C) {
1240   if (!IsRegExpObject(rx)) {
1241     return false;
1242   }
1244   var RegExpCtor = GetBuiltinConstructor("RegExp");
1245   if (C !== RegExpCtor) {
1246     return false;
1247   }
1249   var RegExpProto = RegExpCtor.prototype;
1250   return (
1251     RegExpPrototypeOptimizable(RegExpProto) &&
1252     RegExpInstanceOptimizable(rx, RegExpProto)
1253   );
1256 // String.prototype.matchAll proposal.
1258 // RegExp.prototype [ @@matchAll ] ( string )
1259 function RegExpMatchAll(string) {
1260   // Step 1.
1261   var rx = this;
1263   // Step 2.
1264   if (!IsObject(rx)) {
1265     ThrowTypeError(JSMSG_OBJECT_REQUIRED, rx === null ? "null" : typeof rx);
1266   }
1268   // Step 3.
1269   var str = ToString(string);
1271   // Step 4.
1272   var C = SpeciesConstructor(rx, GetBuiltinConstructor("RegExp"));
1274   var source, flags, matcher, lastIndex;
1275   if (IsRegExpMatchAllOptimizable(rx, C)) {
1276     // Step 5, 9-12.
1277     source = UnsafeGetStringFromReservedSlot(rx, REGEXP_SOURCE_SLOT);
1278     flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT);
1280     // Step 6.
1281     matcher = rx;
1283     // Step 7.
1284     lastIndex = ToLength(rx.lastIndex);
1286     // Step 8 (not applicable for the optimized path).
1287   } else {
1288     // Step 5.
1289     source = "";
1290     flags = ToString(rx.flags);
1292     // Step 6.
1293     matcher = constructContentFunction(C, C, rx, flags);
1295     // Steps 7-8.
1296     matcher.lastIndex = ToLength(rx.lastIndex);
1298     // Steps 9-12.
1299     flags =
1300       (callFunction(std_String_includes, flags, "g") ? REGEXP_GLOBAL_FLAG : 0) |
1301       (callFunction(std_String_includes, flags, "u") ? REGEXP_UNICODE_FLAG : 0);
1303     // Take the non-optimized path.
1304     lastIndex = REGEXP_STRING_ITERATOR_LASTINDEX_SLOW;
1305   }
1307   // Step 13.
1308   return CreateRegExpStringIterator(matcher, str, source, flags, lastIndex);
1311 // String.prototype.matchAll proposal.
1313 // CreateRegExpStringIterator ( R, S, global, fullUnicode )
1314 function CreateRegExpStringIterator(regexp, string, source, flags, lastIndex) {
1315   // Step 1.
1316   assert(typeof string === "string", "|string| is a string value");
1318   // Steps 2-3.
1319   assert(typeof flags === "number", "|flags| is a number value");
1321   assert(typeof source === "string", "|source| is a string value");
1322   assert(typeof lastIndex === "number", "|lastIndex| is a number value");
1324   // Steps 4-9.
1325   var iterator = NewRegExpStringIterator();
1326   UnsafeSetReservedSlot(iterator, REGEXP_STRING_ITERATOR_REGEXP_SLOT, regexp);
1327   UnsafeSetReservedSlot(iterator, REGEXP_STRING_ITERATOR_STRING_SLOT, string);
1328   UnsafeSetReservedSlot(iterator, REGEXP_STRING_ITERATOR_SOURCE_SLOT, source);
1329   UnsafeSetReservedSlot(iterator, REGEXP_STRING_ITERATOR_FLAGS_SLOT, flags | 0);
1330   UnsafeSetReservedSlot(
1331     iterator,
1332     REGEXP_STRING_ITERATOR_LASTINDEX_SLOT,
1333     lastIndex
1334   );
1336   // Step 10.
1337   return iterator;
1340 function IsRegExpStringIteratorNextOptimizable() {
1341   var RegExpProto = GetBuiltinPrototype("RegExp");
1342   // If RegExpPrototypeOptimizable succeeds, `RegExpProto.exec` is
1343   // guaranteed to be a data property.
1344   return (
1345     RegExpPrototypeOptimizable(RegExpProto) &&
1346     RegExpProto.exec === RegExp_prototype_Exec
1347   );
1350 // String.prototype.matchAll proposal.
1352 // %RegExpStringIteratorPrototype%.next ( )
1353 function RegExpStringIteratorNext() {
1354   // Steps 1-3.
1355   var obj = this;
1356   if (!IsObject(obj) || (obj = GuardToRegExpStringIterator(obj)) === null) {
1357     return callFunction(
1358       CallRegExpStringIteratorMethodIfWrapped,
1359       this,
1360       "RegExpStringIteratorNext"
1361     );
1362   }
1364   var result = { value: undefined, done: false };
1366   // Step 4.
1367   var lastIndex = UnsafeGetReservedSlot(
1368     obj,
1369     REGEXP_STRING_ITERATOR_LASTINDEX_SLOT
1370   );
1371   if (lastIndex === REGEXP_STRING_ITERATOR_LASTINDEX_DONE) {
1372     result.done = true;
1373     return result;
1374   }
1376   // Step 5.
1377   var regexp = UnsafeGetObjectFromReservedSlot(
1378     obj,
1379     REGEXP_STRING_ITERATOR_REGEXP_SLOT
1380   );
1382   // Step 6.
1383   var string = UnsafeGetStringFromReservedSlot(
1384     obj,
1385     REGEXP_STRING_ITERATOR_STRING_SLOT
1386   );
1388   // Steps 7-8.
1389   var flags = UnsafeGetInt32FromReservedSlot(
1390     obj,
1391     REGEXP_STRING_ITERATOR_FLAGS_SLOT
1392   );
1393   var global = !!(flags & REGEXP_GLOBAL_FLAG);
1394   var fullUnicode = !!(flags & REGEXP_UNICODE_FLAG);
1396   if (lastIndex >= 0) {
1397     assert(IsRegExpObject(regexp), "|regexp| is a RegExp object");
1399     var source = UnsafeGetStringFromReservedSlot(
1400       obj,
1401       REGEXP_STRING_ITERATOR_SOURCE_SLOT
1402     );
1403     if (
1404       IsRegExpStringIteratorNextOptimizable() &&
1405       UnsafeGetStringFromReservedSlot(regexp, REGEXP_SOURCE_SLOT) === source &&
1406       UnsafeGetInt32FromReservedSlot(regexp, REGEXP_FLAGS_SLOT) === flags
1407     ) {
1408       // Step 9 (Inlined RegExpBuiltinExec).
1409       var globalOrSticky = !!(
1410         flags &
1411         (REGEXP_GLOBAL_FLAG | REGEXP_STICKY_FLAG)
1412       );
1413       if (!globalOrSticky) {
1414         lastIndex = 0;
1415       }
1417       var match =
1418         lastIndex <= string.length
1419           ? RegExpMatcher(regexp, string, lastIndex)
1420           : null;
1422       // Step 10.
1423       if (match === null) {
1424         // Step 10.a.
1425         UnsafeSetReservedSlot(
1426           obj,
1427           REGEXP_STRING_ITERATOR_LASTINDEX_SLOT,
1428           REGEXP_STRING_ITERATOR_LASTINDEX_DONE
1429         );
1431         // Step 10.b.
1432         result.done = true;
1433         return result;
1434       }
1436       // Step 11.a.
1437       if (global) {
1438         // Step 11.a.i.
1439         var matchLength = match[0].length;
1440         lastIndex = match.index + matchLength;
1442         // Step 11.a.ii.
1443         if (matchLength === 0) {
1444           // Steps 11.a.ii.1-3.
1445           lastIndex = fullUnicode
1446             ? AdvanceStringIndex(string, lastIndex)
1447             : lastIndex + 1;
1448         }
1450         UnsafeSetReservedSlot(
1451           obj,
1452           REGEXP_STRING_ITERATOR_LASTINDEX_SLOT,
1453           lastIndex
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       }
1464       // Steps 11.a.iii and 11.b.ii.
1465       result.value = match;
1466       return result;
1467     }
1469     // Reify the RegExp object.
1470     regexp = RegExpConstructRaw(source, flags);
1471     regexp.lastIndex = lastIndex;
1472     UnsafeSetReservedSlot(obj, REGEXP_STRING_ITERATOR_REGEXP_SLOT, regexp);
1474     // Mark the iterator as no longer optimizable.
1475     UnsafeSetReservedSlot(
1476       obj,
1477       REGEXP_STRING_ITERATOR_LASTINDEX_SLOT,
1478       REGEXP_STRING_ITERATOR_LASTINDEX_SLOW
1479     );
1480   }
1482   // Step 9.
1483   var match = RegExpExec(regexp, string);
1485   // Step 10.
1486   if (match === null) {
1487     // Step 10.a.
1488     UnsafeSetReservedSlot(
1489       obj,
1490       REGEXP_STRING_ITERATOR_LASTINDEX_SLOT,
1491       REGEXP_STRING_ITERATOR_LASTINDEX_DONE
1492     );
1494     // Step 10.b.
1495     result.done = true;
1496     return result;
1497   }
1499   // Step 11.a.
1500   if (global) {
1501     // Step 11.a.i.
1502     var matchStr = ToString(match[0]);
1504     // Step 11.a.ii.
1505     if (matchStr.length === 0) {
1506       // Step 11.a.ii.1.
1507       var thisIndex = ToLength(regexp.lastIndex);
1509       // Step 11.a.ii.2.
1510       var nextIndex = fullUnicode
1511         ? AdvanceStringIndex(string, thisIndex)
1512         : thisIndex + 1;
1514       // Step 11.a.ii.3.
1515       regexp.lastIndex = nextIndex;
1516     }
1517   } else {
1518     // Step 11.b.i.
1519     UnsafeSetReservedSlot(
1520       obj,
1521       REGEXP_STRING_ITERATOR_LASTINDEX_SLOT,
1522       REGEXP_STRING_ITERATOR_LASTINDEX_DONE
1523     );
1524   }
1526   // Steps 11.a.iii and 11.b.ii.
1527   result.value = match;
1528   return result;
1531 // ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624
1532 // 7.2.8 IsRegExp ( argument )
1533 function IsRegExp(argument) {
1534   // Step 1.
1535   if (!IsObject(argument)) {
1536     return false;
1537   }
1539   // Step 2.
1540   var matcher = argument[GetBuiltinSymbol("match")];
1542   // Step 3.
1543   if (matcher !== undefined) {
1544     return !!matcher;
1545   }
1547   // Steps 4-5.
1548   return IsPossiblyWrappedRegExpObject(argument);