/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // https://github.com/tc39/ecma262/pull/2418 22.2.6.4 get RegExp.prototype.flags // https://arai-a.github.io/ecma262-compare/?pr=2418&id=sec-get-regexp.prototype.flags // Uncloned functions with `$` prefix are allocated as extended function // to store the original name in `SetCanonicalName`. function $RegExpFlagsGetter() { // Steps 1-2. var R = this; if (!IsObject(R)) { ThrowTypeError(JSMSG_OBJECT_REQUIRED, R === null ? "null" : typeof R); } // Step 3. var result = ""; // Steps 4-5. if (R.hasIndices) { result += "d"; } // Steps 6-7. if (R.global) { result += "g"; } // Steps 8-9. if (R.ignoreCase) { result += "i"; } // Steps 10-11. if (R.multiline) { result += "m"; } // Steps 12-13. if (R.dotAll) { result += "s"; } // Steps 14-15. if (R.unicode) { result += "u"; } // Steps 16-17. if (R.unicodeSets) { result += "v"; } // Steps 18-19 if (R.sticky) { result += "y"; } // Step 20. return result; } SetCanonicalName($RegExpFlagsGetter, "get flags"); // ES 2017 draft 40edb3a95a475c1b251141ac681b8793129d9a6d 21.2.5.14. function $RegExpToString() { // Step 1. var R = this; // Step 2. if (!IsObject(R)) { ThrowTypeError(JSMSG_OBJECT_REQUIRED, R === null ? "null" : typeof R); } // Step 3. var pattern = ToString(R.source); // Step 4. var flags = ToString(R.flags); // Steps 5-6. return "/" + pattern + "/" + flags; } SetCanonicalName($RegExpToString, "toString"); // ES 2016 draft Mar 25, 2016 21.2.5.2.3. function AdvanceStringIndex(S, index) { // Step 1. assert(typeof S === "string", "Expected string as 1st argument"); // Step 2. assert( index >= 0 && index <= MAX_NUMERIC_INDEX, "Expected integer as 2nd argument" ); // Step 3 (skipped). // Step 4 (skipped). // Steps 5-11. var supplementary = ( index < S.length && callFunction(std_String_codePointAt, S, index) > 0xffff ); return index + 1 + supplementary; } // ES2023 draft rev 2c78e6f6b5bc6bfbf79dd8a12a9593e5b57afcd2 // 22.2.5.8 RegExp.prototype [ @@match ] ( string ) function RegExpMatch(string) { // Step 1. var rx = this; // Step 2. if (!IsObject(rx)) { ThrowTypeError(JSMSG_OBJECT_REQUIRED, rx === null ? "null" : typeof rx); } // Step 3. var S = ToString(string); // Optimized paths for simple cases. if (IsRegExpMethodOptimizable(rx)) { // Step 4. var flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT); var global = !!(flags & REGEXP_GLOBAL_FLAG); if (global) { // Step 6.a. var fullUnicode = !!(flags & REGEXP_UNICODE_FLAG); // Steps 6.b-e. return RegExpGlobalMatchOpt(rx, S, fullUnicode); } // Step 5. return RegExpBuiltinExec(rx, S); } // Stes 4-6 return RegExpMatchSlowPath(rx, S); } // ES2023 draft rev 2c78e6f6b5bc6bfbf79dd8a12a9593e5b57afcd2 // 22.2.5.8 RegExp.prototype [ @@match ] ( string ) // Steps 4-6 function RegExpMatchSlowPath(rx, S) { // Step 4. var flags = ToString(rx.flags); // Step 5. if (!callFunction(std_String_includes, flags, "g")) { return RegExpExec(rx, S); } // Step 6.a. var fullUnicode = callFunction(std_String_includes, flags, "u"); // Step 6.b. rx.lastIndex = 0; // Step 6.c. var A = []; // Step 6.d. var n = 0; // Step 6.e. while (true) { // Step 6.e.i. var result = RegExpExec(rx, S); // Step 6.e.ii. if (result === null) { return n === 0 ? null : A; } // Step 6.e.iii.1. var matchStr = ToString(result[0]); // Step 6.e.iii.2. DefineDataProperty(A, n, matchStr); // Step 6.e.iii.3. if (matchStr === "") { var lastIndex = ToLength(rx.lastIndex); rx.lastIndex = fullUnicode ? AdvanceStringIndex(S, lastIndex) : lastIndex + 1; } // Step 6.e.iii.4. n++; } } // ES 2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e 21.2.5.6. // Steps 6.b-e. // Optimized path for @@match with global flag. function RegExpGlobalMatchOpt(rx, S, fullUnicode) { // Step 6.b. var lastIndex = 0; rx.lastIndex = 0; // Step 6.c. var A = []; // Step 6.d. var n = 0; var lengthS = S.length; // Step 6.e. while (true) { // Step 6.e.i. var position = RegExpSearcher(rx, S, lastIndex); // Step 6.e.ii. if (position === -1) { return n === 0 ? null : A; } lastIndex = RegExpSearcherLastLimit(S); // Step 6.e.iii.1. var matchStr = Substring(S, position, lastIndex - position); // Step 6.e.iii.2. DefineDataProperty(A, n, matchStr); // Step 6.e.iii.4. if (matchStr === "") { lastIndex = fullUnicode ? AdvanceStringIndex(S, lastIndex) : lastIndex + 1; if (lastIndex > lengthS) { return A; } } // Step 6.e.iii.5. n++; } } // Checks if following properties and getters are not modified, and accessing // them not observed by content script: // * flags // * hasIndices // * global // * ignoreCase // * multiline // * dotAll // * sticky // * unicode // * unicodeSets // * exec // * lastIndex function IsRegExpMethodOptimizable(rx) { if (!IsRegExpObject(rx)) { return false; } var RegExpProto = GetBuiltinPrototype("RegExp"); // If RegExpPrototypeOptimizable and RegExpInstanceOptimizable succeed, // `RegExpProto.exec` is guaranteed to be data properties. return ( RegExpPrototypeOptimizable(RegExpProto) && RegExpInstanceOptimizable(rx, RegExpProto) && RegExpProto.exec === RegExp_prototype_Exec ); } // ES2023 draft rev 2c78e6f6b5bc6bfbf79dd8a12a9593e5b57afcd2 // 22.2.5.11 RegExp.prototype [ @@replace ] ( string, replaceValue ) function RegExpReplace(string, replaceValue) { // Step 1. var rx = this; // Step 2. if (!IsObject(rx)) { ThrowTypeError(JSMSG_OBJECT_REQUIRED, rx === null ? "null" : typeof rx); } // Step 3. var S = ToString(string); // Step 4. var lengthS = S.length; // Step 5. var functionalReplace = IsCallable(replaceValue); // Step 6. var firstDollarIndex = -1; if (!functionalReplace) { // Step 6.a. replaceValue = ToString(replaceValue); // Skip if replaceValue is an empty string or a single character. // A single character string may contain "$", but that cannot be a // substitution. if (replaceValue.length > 1) { firstDollarIndex = GetFirstDollarIndex(replaceValue); } } // Optimized paths. if (IsRegExpMethodOptimizable(rx)) { // Step 7. var flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT); // Step 9. var global = !!(flags & REGEXP_GLOBAL_FLAG); // Steps 9-17. if (global) { if (functionalReplace) { // For large strings check if the replacer function is // applicable for the elem-base optimization. if (lengthS > 5000) { var elemBase = GetElemBaseForLambda(replaceValue); if (IsObject(elemBase)) { return RegExpGlobalReplaceOptElemBase( rx, S, lengthS, replaceValue, flags, elemBase ); } } return RegExpGlobalReplaceOptFunc(rx, S, lengthS, replaceValue, flags); } if (firstDollarIndex !== -1) { return RegExpGlobalReplaceOptSubst( rx, S, lengthS, replaceValue, flags, firstDollarIndex ); } return RegExpGlobalReplaceOptSimple(rx, S, lengthS, replaceValue, flags); } if (functionalReplace) { return RegExpLocalReplaceOptFunc(rx, S, lengthS, replaceValue); } if (firstDollarIndex !== -1) { return RegExpLocalReplaceOptSubst( rx, S, lengthS, replaceValue, firstDollarIndex ); } return RegExpLocalReplaceOptSimple(rx, S, lengthS, replaceValue); } // Steps 7-17. return RegExpReplaceSlowPath( rx, S, lengthS, replaceValue, functionalReplace, firstDollarIndex ); } // ES2023 draft rev 2c78e6f6b5bc6bfbf79dd8a12a9593e5b57afcd2 // 22.2.5.11 RegExp.prototype [ @@replace ] ( string, replaceValue ) // Steps 7-17. // Slow path for @@replace. function RegExpReplaceSlowPath( rx, S, lengthS, replaceValue, functionalReplace, firstDollarIndex ) { // Step 7. var flags = ToString(rx.flags); // Step 8. var global = callFunction(std_String_includes, flags, "g"); // Step 9. var fullUnicode = false; if (global) { // Step 9.a. fullUnicode = callFunction(std_String_includes, flags, "u"); // Step 9.b. rx.lastIndex = 0; } // Step 10. var results = new_List(); var nResults = 0; // Steps 11-12. while (true) { // Step 12.a. var result = RegExpExec(rx, S); // Step 12.b. if (result === null) { break; } // Step 12.c.i. DefineDataProperty(results, nResults++, result); // Step 12.c.ii. if (!global) { break; } // Step 12.c.iii.1. var matchStr = ToString(result[0]); // Step 12.c.iii.2. if (matchStr === "") { var lastIndex = ToLength(rx.lastIndex); rx.lastIndex = fullUnicode ? AdvanceStringIndex(S, lastIndex) : lastIndex + 1; } } // Step 13. var accumulatedResult = ""; // Step 14. var nextSourcePosition = 0; // Step 15. for (var i = 0; i < nResults; i++) { result = results[i]; // Steps 15.a-b. var nCaptures = std_Math_max(ToLength(result.length) - 1, 0); // Step 15.c. var matched = ToString(result[0]); // Step 15.d. var matchLength = matched.length; // Steps 15.e-f. var position = std_Math_max( std_Math_min(ToInteger(result.index), lengthS), 0 ); var replacement; if (functionalReplace || firstDollarIndex !== -1) { // Steps 15.g-l. replacement = RegExpGetComplexReplacement( result, matched, S, position, nCaptures, replaceValue, functionalReplace, firstDollarIndex ); } else { // Steps 15.g, 15.i, 15.i.iv. // We don't need captures array, but ToString is visible to script. for (var n = 1; n <= nCaptures; n++) { // Steps 15.i.i-ii. var capN = result[n]; // Step 15.i.ii. if (capN !== undefined) { ToString(capN); } } // Steps 15.j, 15.l.i. // We don't need namedCaptures, but ToObject is visible to script. var namedCaptures = result.groups; if (namedCaptures !== undefined) { ToObject(namedCaptures); } // Step 15.l.ii. replacement = replaceValue; } // Step 15.m. if (position >= nextSourcePosition) { // Step 15.m.ii. accumulatedResult += Substring(S, nextSourcePosition, position - nextSourcePosition) + replacement; // Step 15.m.iii. nextSourcePosition = position + matchLength; } } // Step 16. if (nextSourcePosition >= lengthS) { return accumulatedResult; } // Step 17. return ( accumulatedResult + Substring(S, nextSourcePosition, lengthS - nextSourcePosition) ); } // ES2023 draft rev 2c78e6f6b5bc6bfbf79dd8a12a9593e5b57afcd2 // 22.2.5.11 RegExp.prototype [ @@replace ] ( string, replaceValue ) // https://tc39.es/ecma262/#sec-regexp.prototype-@@replace // Steps 15.g-l. // Calculates functional/substitution replacement from match result. // Used in the following functions: // * RegExpReplaceSlowPath function RegExpGetComplexReplacement( result, matched, S, position, nCaptures, replaceValue, functionalReplace, firstDollarIndex ) { // Step 15.g. var captures = new_List(); var capturesLength = 0; // Step 15.k.i (reordered). DefineDataProperty(captures, capturesLength++, matched); // Steps 15.h, 15.i, 15.i.v. for (var n = 1; n <= nCaptures; n++) { // Step 15.i.i. var capN = result[n]; // Step 15.i.ii. if (capN !== undefined) { capN = ToString(capN); } // Step 15.i.iii. DefineDataProperty(captures, capturesLength++, capN); } // Step 15.j. var namedCaptures = result.groups; // Step 15.k. if (functionalReplace) { // For `nCaptures` <= 4 case, call `replaceValue` directly, otherwise // use `std_Function_apply` with all arguments stored in `captures`. if (namedCaptures === undefined) { switch (nCaptures) { case 0: return ToString( callContentFunction( replaceValue, undefined, SPREAD(captures, 1), position, S ) ); case 1: return ToString( callContentFunction( replaceValue, undefined, SPREAD(captures, 2), position, S ) ); case 2: return ToString( callContentFunction( replaceValue, undefined, SPREAD(captures, 3), position, S ) ); case 3: return ToString( callContentFunction( replaceValue, undefined, SPREAD(captures, 4), position, S ) ); case 4: return ToString( callContentFunction( replaceValue, undefined, SPREAD(captures, 5), position, S ) ); } } // Steps 15.k.ii-vi. DefineDataProperty(captures, capturesLength++, position); DefineDataProperty(captures, capturesLength++, S); if (namedCaptures !== undefined) { DefineDataProperty(captures, capturesLength++, namedCaptures); } return ToString( callFunction(std_Function_apply, replaceValue, undefined, captures) ); } // Step 15.l. if (namedCaptures !== undefined) { namedCaptures = ToObject(namedCaptures); } return RegExpGetSubstitution( captures, S, position, replaceValue, firstDollarIndex, namedCaptures ); } // ES2023 draft rev 2c78e6f6b5bc6bfbf79dd8a12a9593e5b57afcd2 // 22.2.5.11 RegExp.prototype [ @@replace ] ( string, replaceValue ) // https://tc39.es/ecma262/#sec-regexp.prototype-@@replace // Steps 15.g-k. // Calculates functional replacement from match result. // Used in the following functions: // * RegExpGlobalReplaceOptFunc // * RegExpGlobalReplaceOptElemBase // * RegExpLocalReplaceOptFunc function RegExpGetFunctionalReplacement(result, S, position, replaceValue) { // For `nCaptures` <= 4 case, call `replaceValue` directly, otherwise // use `std_Function_apply` with all arguments stored in `captures`. assert(result.length >= 1, "RegExpMatcher doesn't return an empty array"); var nCaptures = result.length - 1; // Step 15.j (reordered) var namedCaptures = result.groups; if (namedCaptures === undefined) { switch (nCaptures) { case 0: return ToString( callContentFunction( replaceValue, undefined, SPREAD(result, 1), position, S ) ); case 1: return ToString( callContentFunction( replaceValue, undefined, SPREAD(result, 2), position, S ) ); case 2: return ToString( callContentFunction( replaceValue, undefined, SPREAD(result, 3), position, S ) ); case 3: return ToString( callContentFunction( replaceValue, undefined, SPREAD(result, 4), position, S ) ); case 4: return ToString( callContentFunction( replaceValue, undefined, SPREAD(result, 5), position, S ) ); } } // Steps 15.g-i, 15.k.i-ii. var captures = new_List(); for (var n = 0; n <= nCaptures; n++) { assert( typeof result[n] === "string" || result[n] === undefined, "RegExpMatcher returns only strings and undefined" ); DefineDataProperty(captures, n, result[n]); } // Step 15.k.iii. DefineDataProperty(captures, nCaptures + 1, position); DefineDataProperty(captures, nCaptures + 2, S); // Step 15.k.iv. if (namedCaptures !== undefined) { DefineDataProperty(captures, nCaptures + 3, namedCaptures); } // Steps 15.k.v-vi. return ToString( callFunction(std_Function_apply, replaceValue, undefined, captures) ); } // ES2023 draft rev 2c78e6f6b5bc6bfbf79dd8a12a9593e5b57afcd2 // 22.2.5.11 RegExp.prototype [ @@replace ] ( string, replaceValue ) // Steps 9.b-17. // Optimized path for @@replace with the following conditions: // * global flag is true // * replaceValue is a string without "$" function RegExpGlobalReplaceOptSimple(rx, S, lengthS, replaceValue, flags) { // Step 9.a. var fullUnicode = !!(flags & REGEXP_UNICODE_FLAG); // Step 9.b. var lastIndex = 0; rx.lastIndex = 0; // Step 13 (reordered). var accumulatedResult = ""; // Step 14 (reordered). var nextSourcePosition = 0; // Step 12. while (true) { // Step 12.a. var position = RegExpSearcher(rx, S, lastIndex); // Step 12.b. if (position === -1) { break; } lastIndex = RegExpSearcherLastLimit(S); // Step 15.m.ii. accumulatedResult += Substring(S, nextSourcePosition, position - nextSourcePosition) + replaceValue; // Step 15.m.iii. nextSourcePosition = lastIndex; // Step 12.c.iii.2. if (lastIndex === position) { lastIndex = fullUnicode ? AdvanceStringIndex(S, lastIndex) : lastIndex + 1; if (lastIndex > lengthS) { break; } } } // Step 16. if (nextSourcePosition >= lengthS) { return accumulatedResult; } // Step 17. return ( accumulatedResult + Substring(S, nextSourcePosition, lengthS - nextSourcePosition) ); } // ES2023 draft rev 2c78e6f6b5bc6bfbf79dd8a12a9593e5b57afcd2 // 22.2.5.11 RegExp.prototype [ @@replace ] ( string, replaceValue ) // Steps 7-17. // Optimized path for @@replace. // Conditions: // * global flag is true // * replaceValue is a function #define FUNC_NAME RegExpGlobalReplaceOptFunc #define FUNCTIONAL #include "RegExpGlobalReplaceOpt.h.js" #undef FUNCTIONAL #undef FUNC_NAME /* global RegExpGlobalReplaceOptFunc */ // Conditions: // * global flag is true // * replaceValue is a function that returns element of an object #define FUNC_NAME RegExpGlobalReplaceOptElemBase #define ELEMBASE #include "RegExpGlobalReplaceOpt.h.js" #undef ELEMBASE #undef FUNC_NAME /* global RegExpGlobalReplaceOptElemBase */ // Conditions: // * global flag is true // * replaceValue is a string with "$" #define FUNC_NAME RegExpGlobalReplaceOptSubst #define SUBSTITUTION #include "RegExpGlobalReplaceOpt.h.js" #undef SUBSTITUTION #undef FUNC_NAME /* global RegExpGlobalReplaceOptSubst */ // Conditions: // * global flag is false // * replaceValue is a string without "$" #define FUNC_NAME RegExpLocalReplaceOptSimple #define SIMPLE #include "RegExpLocalReplaceOpt.h.js" #undef SIMPLE #undef FUNC_NAME /* global RegExpLocalReplaceOptSimple */ // Conditions: // * global flag is false // * replaceValue is a function #define FUNC_NAME RegExpLocalReplaceOptFunc #define FUNCTIONAL #include "RegExpLocalReplaceOpt.h.js" #undef FUNCTIONAL #undef FUNC_NAME /* global RegExpLocalReplaceOptFunc */ // Conditions: // * global flag is false // * replaceValue is a string with "$" #define FUNC_NAME RegExpLocalReplaceOptSubst #define SUBSTITUTION #include "RegExpLocalReplaceOpt.h.js" #undef SUBSTITUTION #undef FUNC_NAME /* global RegExpLocalReplaceOptSubst */ // ES2017 draft rev 6390c2f1b34b309895d31d8c0512eac8660a0210 // 21.2.5.9 RegExp.prototype [ @@search ] ( string ) function RegExpSearch(string) { // Step 1. var rx = this; // Step 2. if (!IsObject(rx)) { ThrowTypeError(JSMSG_OBJECT_REQUIRED, rx === null ? "null" : typeof rx); } // Step 3. var S = ToString(string); // Step 4. var previousLastIndex = rx.lastIndex; // Step 5. var lastIndexIsZero = SameValue(previousLastIndex, 0); if (!lastIndexIsZero) { rx.lastIndex = 0; } if (IsRegExpMethodOptimizable(rx) && S.length < 0x7fff) { // Step 6. var result = RegExpSearcher(rx, S, 0); // We need to consider two cases: // // 1. Neither global nor sticky is set: // RegExpBuiltinExec doesn't modify lastIndex for local RegExps, that // means |SameValue(rx.lastIndex, 0)| is true after calling exec. The // comparison in steps 7-8 |SameValue(rx.lastIndex, previousLastIndex)| // is therefore equal to the already computed |lastIndexIsZero| value. // // 2. Global or sticky flag is set. // RegExpBuiltinExec will always update lastIndex and we need to // restore the property to its original value. // Steps 7-8. if (!lastIndexIsZero) { rx.lastIndex = previousLastIndex; } else { var flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT); if (flags & (REGEXP_GLOBAL_FLAG | REGEXP_STICKY_FLAG)) { rx.lastIndex = previousLastIndex; } } // Steps 9-10. return result; } return RegExpSearchSlowPath(rx, S, previousLastIndex); } // ES2017 draft rev 6390c2f1b34b309895d31d8c0512eac8660a0210 // 21.2.5.9 RegExp.prototype [ @@search ] ( string ) // Steps 6-10. function RegExpSearchSlowPath(rx, S, previousLastIndex) { // Step 6. var result = RegExpExec(rx, S); // Step 7. var currentLastIndex = rx.lastIndex; // Step 8. if (!SameValue(currentLastIndex, previousLastIndex)) { rx.lastIndex = previousLastIndex; } // Step 9. if (result === null) { return -1; } // Step 10. return result.index; } function IsRegExpSplitOptimizable(rx, C) { if (!IsRegExpObject(rx)) { return false; } var RegExpCtor = GetBuiltinConstructor("RegExp"); if (C !== RegExpCtor) { return false; } var RegExpProto = RegExpCtor.prototype; // If RegExpPrototypeOptimizable succeeds, `RegExpProto.exec` is guaranteed // to be a data property. return ( RegExpPrototypeOptimizable(RegExpProto) && RegExpInstanceOptimizable(rx, RegExpProto) && RegExpProto.exec === RegExp_prototype_Exec ); } // ES 2017 draft 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e 21.2.5.11. function RegExpSplit(string, limit) { // Step 1. var rx = this; // Step 2. if (!IsObject(rx)) { ThrowTypeError(JSMSG_OBJECT_REQUIRED, rx === null ? "null" : typeof rx); } // Step 3. var S = ToString(string); // Step 4. var C = SpeciesConstructor(rx, GetBuiltinConstructor("RegExp")); var optimizable = IsRegExpSplitOptimizable(rx, C) && (limit === undefined || typeof limit === "number"); var flags, unicodeMatching, splitter; if (optimizable) { // Step 5. flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT); // Steps 6-7. unicodeMatching = !!(flags & REGEXP_UNICODE_FLAG); // Steps 8-10. // If split operation is optimizable, perform non-sticky match. if (flags & REGEXP_STICKY_FLAG) { var source = UnsafeGetStringFromReservedSlot(rx, REGEXP_SOURCE_SLOT); splitter = RegExpConstructRaw(source, flags & ~REGEXP_STICKY_FLAG); } else { splitter = rx; } } else { // Step 5. flags = ToString(rx.flags); // Steps 6-7. unicodeMatching = callFunction(std_String_includes, flags, "u"); // Steps 8-9. var newFlags; if (callFunction(std_String_includes, flags, "y")) { newFlags = flags; } else { newFlags = flags + "y"; } // Step 10. splitter = constructContentFunction(C, C, rx, newFlags); } // Step 11. var A = []; // Step 12. var lengthA = 0; // Step 13. var lim; if (limit === undefined) { lim = MAX_UINT32; } else { lim = limit >>> 0; } // Step 15. var p = 0; // Step 16. if (lim === 0) { return A; } // Step 14 (reordered). var size = S.length; // Step 17. if (size === 0) { // Step 17.a-b. if (optimizable) { if (RegExpSearcher(splitter, S, 0) !== -1) { return A; } } else { if (RegExpExec(splitter, S) !== null) { return A; } } // Step 17.d. DefineDataProperty(A, 0, S); // Step 17.e. return A; } // Step 18. var q = p; var optimizableNoCaptures = optimizable && !RegExpHasCaptureGroups(splitter, S); // Step 19. while (q < size) { var e, z; if (optimizableNoCaptures) { // If there are no capturing groups, avoid allocating the match result // object |z| (we set it to null). This is the only difference between // this branch and the |if (optimizable)| case below. // Step 19.a (skipped). // splitter.lastIndex is not used. // Steps 19.b-c. q = RegExpSearcher(splitter, S, q); if (q === -1 || q >= size) { break; } // Step 19.d.i. e = RegExpSearcherLastLimit(S); z = null; } else if (optimizable) { // Step 19.a (skipped). // splitter.lastIndex is not used. // Step 19.b. z = RegExpMatcher(splitter, S, q); // Step 19.c. if (z === null) { break; } // splitter.lastIndex is not updated. q = z.index; if (q >= size) { break; } // Step 19.d.i. e = q + z[0].length; } else { // Step 19.a. splitter.lastIndex = q; // Step 19.b. z = RegExpExec(splitter, S); // Step 19.c. if (z === null) { q = unicodeMatching ? AdvanceStringIndex(S, q) : q + 1; continue; } // Step 19.d.i. e = ToLength(splitter.lastIndex); } // Step 19.d.iii. if (e === p) { q = unicodeMatching ? AdvanceStringIndex(S, q) : q + 1; continue; } // Steps 19.d.iv.1-3. DefineDataProperty(A, lengthA, Substring(S, p, q - p)); // Step 19.d.iv.4. lengthA++; // Step 19.d.iv.5. if (lengthA === lim) { return A; } // Step 19.d.iv.6. p = e; if (z !== null) { // Steps 19.d.iv.7-8. var numberOfCaptures = std_Math_max(ToLength(z.length) - 1, 0); // Step 19.d.iv.9. var i = 1; // Step 19.d.iv.10. while (i <= numberOfCaptures) { // Steps 19.d.iv.10.a-b. DefineDataProperty(A, lengthA, z[i]); // Step 19.d.iv.10.c. i++; // Step 19.d.iv.10.d. lengthA++; // Step 19.d.iv.10.e. if (lengthA === lim) { return A; } } } // Step 19.d.iv.11. q = p; } // Steps 20-22. if (p >= size) { DefineDataProperty(A, lengthA, ""); } else { DefineDataProperty(A, lengthA, Substring(S, p, size - p)); } // Step 23. return A; } // ES6 21.2.5.2. // NOTE: This is not RegExpExec (21.2.5.2.1). function RegExp_prototype_Exec(string) { // Steps 1-3. var R = this; if (!IsObject(R) || !IsRegExpObject(R)) { return callFunction( CallRegExpMethodIfWrapped, R, string, "RegExp_prototype_Exec" ); } // Steps 4-5. var S = ToString(string); // Step 6. return RegExpBuiltinExec(R, S); } // ES6 21.2.5.13. function RegExpTest(string) { // Steps 1-2. var R = this; if (!IsObject(R)) { ThrowTypeError(JSMSG_OBJECT_REQUIRED, R === null ? "null" : typeof R); } // Steps 3-4. var S = ToString(string); // Steps 5-6. return RegExpExecForTest(R, S); } // ES 2016 draft Mar 25, 2016 21.2.4.2. function $RegExpSpecies() { // Step 1. return this; } SetCanonicalName($RegExpSpecies, "get [Symbol.species]"); function IsRegExpMatchAllOptimizable(rx, C) { if (!IsRegExpObject(rx)) { return false; } var RegExpCtor = GetBuiltinConstructor("RegExp"); if (C !== RegExpCtor) { return false; } var RegExpProto = RegExpCtor.prototype; return ( RegExpPrototypeOptimizable(RegExpProto) && RegExpInstanceOptimizable(rx, RegExpProto) ); } // String.prototype.matchAll proposal. // // RegExp.prototype [ @@matchAll ] ( string ) function RegExpMatchAll(string) { // Step 1. var rx = this; // Step 2. if (!IsObject(rx)) { ThrowTypeError(JSMSG_OBJECT_REQUIRED, rx === null ? "null" : typeof rx); } // Step 3. var str = ToString(string); // Step 4. var C = SpeciesConstructor(rx, GetBuiltinConstructor("RegExp")); var source, flags, matcher, lastIndex; if (IsRegExpMatchAllOptimizable(rx, C)) { // Step 5, 9-12. source = UnsafeGetStringFromReservedSlot(rx, REGEXP_SOURCE_SLOT); flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT); // Step 6. matcher = rx; // Step 7. lastIndex = ToLength(rx.lastIndex); // Step 8 (not applicable for the optimized path). } else { // Step 5. source = ""; flags = ToString(rx.flags); // Step 6. matcher = constructContentFunction(C, C, rx, flags); // Steps 7-8. matcher.lastIndex = ToLength(rx.lastIndex); // Steps 9-12. flags = (callFunction(std_String_includes, flags, "g") ? REGEXP_GLOBAL_FLAG : 0) | (callFunction(std_String_includes, flags, "u") ? REGEXP_UNICODE_FLAG : 0); // Take the non-optimized path. lastIndex = REGEXP_STRING_ITERATOR_LASTINDEX_SLOW; } // Step 13. return CreateRegExpStringIterator(matcher, str, source, flags, lastIndex); } // String.prototype.matchAll proposal. // // CreateRegExpStringIterator ( R, S, global, fullUnicode ) function CreateRegExpStringIterator(regexp, string, source, flags, lastIndex) { // Step 1. assert(typeof string === "string", "|string| is a string value"); // Steps 2-3. assert(typeof flags === "number", "|flags| is a number value"); assert(typeof source === "string", "|source| is a string value"); assert(typeof lastIndex === "number", "|lastIndex| is a number value"); // Steps 4-9. var iterator = NewRegExpStringIterator(); UnsafeSetReservedSlot(iterator, REGEXP_STRING_ITERATOR_REGEXP_SLOT, regexp); UnsafeSetReservedSlot(iterator, REGEXP_STRING_ITERATOR_STRING_SLOT, string); UnsafeSetReservedSlot(iterator, REGEXP_STRING_ITERATOR_SOURCE_SLOT, source); UnsafeSetReservedSlot(iterator, REGEXP_STRING_ITERATOR_FLAGS_SLOT, flags | 0); UnsafeSetReservedSlot( iterator, REGEXP_STRING_ITERATOR_LASTINDEX_SLOT, lastIndex ); // Step 10. return iterator; } function IsRegExpStringIteratorNextOptimizable() { var RegExpProto = GetBuiltinPrototype("RegExp"); // If RegExpPrototypeOptimizable succeeds, `RegExpProto.exec` is // guaranteed to be a data property. return ( RegExpPrototypeOptimizable(RegExpProto) && RegExpProto.exec === RegExp_prototype_Exec ); } // String.prototype.matchAll proposal. // // %RegExpStringIteratorPrototype%.next ( ) function RegExpStringIteratorNext() { // Steps 1-3. var obj = this; if (!IsObject(obj) || (obj = GuardToRegExpStringIterator(obj)) === null) { return callFunction( CallRegExpStringIteratorMethodIfWrapped, this, "RegExpStringIteratorNext" ); } var result = { value: undefined, done: false }; // Step 4. var lastIndex = UnsafeGetReservedSlot( obj, REGEXP_STRING_ITERATOR_LASTINDEX_SLOT ); if (lastIndex === REGEXP_STRING_ITERATOR_LASTINDEX_DONE) { result.done = true; return result; } // Step 5. var regexp = UnsafeGetObjectFromReservedSlot( obj, REGEXP_STRING_ITERATOR_REGEXP_SLOT ); // Step 6. var string = UnsafeGetStringFromReservedSlot( obj, REGEXP_STRING_ITERATOR_STRING_SLOT ); // Steps 7-8. var flags = UnsafeGetInt32FromReservedSlot( obj, REGEXP_STRING_ITERATOR_FLAGS_SLOT ); var global = !!(flags & REGEXP_GLOBAL_FLAG); var fullUnicode = !!(flags & REGEXP_UNICODE_FLAG); if (lastIndex >= 0) { assert(IsRegExpObject(regexp), "|regexp| is a RegExp object"); var source = UnsafeGetStringFromReservedSlot( obj, REGEXP_STRING_ITERATOR_SOURCE_SLOT ); if ( IsRegExpStringIteratorNextOptimizable() && UnsafeGetStringFromReservedSlot(regexp, REGEXP_SOURCE_SLOT) === source && UnsafeGetInt32FromReservedSlot(regexp, REGEXP_FLAGS_SLOT) === flags ) { // Step 9 (Inlined RegExpBuiltinExec). var globalOrSticky = !!( flags & (REGEXP_GLOBAL_FLAG | REGEXP_STICKY_FLAG) ); if (!globalOrSticky) { lastIndex = 0; } var match = lastIndex <= string.length ? RegExpMatcher(regexp, string, lastIndex) : null; // Step 10. if (match === null) { // Step 10.a. UnsafeSetReservedSlot( obj, REGEXP_STRING_ITERATOR_LASTINDEX_SLOT, REGEXP_STRING_ITERATOR_LASTINDEX_DONE ); // Step 10.b. result.done = true; return result; } // Step 11.a. if (global) { // Step 11.a.i. var matchLength = match[0].length; lastIndex = match.index + matchLength; // Step 11.a.ii. if (matchLength === 0) { // Steps 11.a.ii.1-3. lastIndex = fullUnicode ? AdvanceStringIndex(string, lastIndex) : lastIndex + 1; } UnsafeSetReservedSlot( obj, REGEXP_STRING_ITERATOR_LASTINDEX_SLOT, lastIndex ); } else { // Step 11.b.i. UnsafeSetReservedSlot( obj, REGEXP_STRING_ITERATOR_LASTINDEX_SLOT, REGEXP_STRING_ITERATOR_LASTINDEX_DONE ); } // Steps 11.a.iii and 11.b.ii. result.value = match; return result; } // Reify the RegExp object. regexp = RegExpConstructRaw(source, flags); regexp.lastIndex = lastIndex; UnsafeSetReservedSlot(obj, REGEXP_STRING_ITERATOR_REGEXP_SLOT, regexp); // Mark the iterator as no longer optimizable. UnsafeSetReservedSlot( obj, REGEXP_STRING_ITERATOR_LASTINDEX_SLOT, REGEXP_STRING_ITERATOR_LASTINDEX_SLOW ); } // Step 9. var match = RegExpExec(regexp, string); // Step 10. if (match === null) { // Step 10.a. UnsafeSetReservedSlot( obj, REGEXP_STRING_ITERATOR_LASTINDEX_SLOT, REGEXP_STRING_ITERATOR_LASTINDEX_DONE ); // Step 10.b. result.done = true; return result; } // Step 11.a. if (global) { // Step 11.a.i. var matchStr = ToString(match[0]); // Step 11.a.ii. if (matchStr.length === 0) { // Step 11.a.ii.1. var thisIndex = ToLength(regexp.lastIndex); // Step 11.a.ii.2. var nextIndex = fullUnicode ? AdvanceStringIndex(string, thisIndex) : thisIndex + 1; // Step 11.a.ii.3. regexp.lastIndex = nextIndex; } } else { // Step 11.b.i. UnsafeSetReservedSlot( obj, REGEXP_STRING_ITERATOR_LASTINDEX_SLOT, REGEXP_STRING_ITERATOR_LASTINDEX_DONE ); } // Steps 11.a.iii and 11.b.ii. result.value = match; return result; } // ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624 // 7.2.8 IsRegExp ( argument ) function IsRegExp(argument) { // Step 1. if (!IsObject(argument)) { return false; } // Step 2. var matcher = argument[GetBuiltinSymbol("match")]; // Step 3. if (matcher !== undefined) { return !!matcher; } // Steps 4-5. return IsPossiblyWrappedRegExpObject(argument); }