/* 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 (IsOptimizableRegExpObject(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) || !!(flags & REGEXP_UNICODESETS_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") || callFunction(std_String_includes, flags, "v");

  // 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++;
  }
}

// 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 (IsOptimizableRegExpObject(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") || callFunction(std_String_includes, flags, "v");

    // 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) || !!(flags &  REGEXP_UNICODESETS_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 (IsOptimizableRegExpObject(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;
}

// 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 builtinCtor = GetBuiltinConstructor("RegExp");
  var C = SpeciesConstructor(rx, builtinCtor);

  var optimizable =
    IsOptimizableRegExpObject(rx) &&
    C === builtinCtor &&
    (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]");

// 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 builtinCtor = GetBuiltinConstructor("RegExp");
  var C = SpeciesConstructor(rx, builtinCtor);

  var source, flags, matcher, lastIndex;
  if (IsOptimizableRegExpObject(rx) && C === builtinCtor) {
    // 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;
}

// 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) || !!(flags & REGEXP_UNICODESETS_FLAG);

  if (lastIndex >= 0) {
    assert(IsRegExpObject(regexp), "|regexp| is a RegExp object");

    var source = UnsafeGetStringFromReservedSlot(
      obj,
      REGEXP_STRING_ITERATOR_SOURCE_SLOT
    );
    if (
      IsRegExpPrototypeOptimizable() &&
      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);
}
