// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include 'src/builtins/builtins-regexp-gen.h'

namespace regexp {

extern transitioning macro
RegExpMatchAllAssembler::CreateRegExpStringIterator(
    NativeContext, Object, String, bool, bool): JSAny;

@export
transitioning macro RegExpPrototypeMatchAllImpl(implicit context: Context)(
    nativeContext: NativeContext, receiver: JSAny, string: JSAny): JSAny {
  // 1. Let R be the this value.
  // 2. If Type(R) is not Object, throw a TypeError exception.
  ThrowIfNotJSReceiver(
      receiver, MessageTemplate::kIncompatibleMethodReceiver,
      'RegExp.prototype.@@matchAll');
  const receiver = UnsafeCast<JSReceiver>(receiver);

  // 3. Let S be ? ToString(O).
  const string: String = ToString_Inline(string);

  let matcher: Object;
  let global: bool;
  let unicode: bool;

  // 'FastJSRegExp' uses the strict fast path check because following code
  // uses the flags property.
  // TODO(jgruber): Handle slow flag accesses on the fast path and make this
  // permissive.
  typeswitch (receiver) {
    case (fastRegExp: FastJSRegExp): {
      const source = fastRegExp.source;

      // 4. Let C be ? SpeciesConstructor(R, %RegExp%).
      // 5. Let flags be ? ToString(? Get(R, "flags")).
      // 6. Let matcher be ? Construct(C, « R, flags »).
      const flags: String = FastFlagsGetter(fastRegExp);
      matcher = RegExpCreate(nativeContext, source, flags);
      const matcherRegExp = UnsafeCast<JSRegExp>(matcher);
      assert(IsFastRegExpPermissive(matcherRegExp));

      // 7. Let lastIndex be ? ToLength(? Get(R, "lastIndex")).
      // 8. Perform ? Set(matcher, "lastIndex", lastIndex, true).
      const fastRegExp = UnsafeCast<FastJSRegExp>(receiver);
      FastStoreLastIndex(matcherRegExp, fastRegExp.lastIndex);

      // 9. If flags contains "g", let global be true.
      // 10. Else, let global be false.
      global = FastFlagGetter(matcherRegExp, Flag::kGlobal);

      // 11. If flags contains "u", let fullUnicode be true.
      // 12. Else, let fullUnicode be false.
      unicode = FastFlagGetter(matcherRegExp, Flag::kUnicode);
    }
    case (Object): {
      // 4. Let C be ? SpeciesConstructor(R, %RegExp%).
      const regexpFun = LoadRegExpFunction(nativeContext);
      const speciesConstructor =
          UnsafeCast<Constructor>(SpeciesConstructor(receiver, regexpFun));

      // 5. Let flags be ? ToString(? Get(R, "flags")).
      const flags = GetProperty(receiver, 'flags');
      const flagsString = ToString_Inline(flags);

      // 6. Let matcher be ? Construct(C, « R, flags »).
      matcher = Construct(speciesConstructor, receiver, flagsString);

      // 7. Let lastIndex be ? ToLength(? Get(R, "lastIndex")).
      const lastIndex: Number = ToLength_Inline(SlowLoadLastIndex(receiver));

      // 8. Perform ? Set(matcher, "lastIndex", lastIndex, true).
      SlowStoreLastIndex(UnsafeCast<JSReceiver>(matcher), lastIndex);

      // 9. If flags contains "g", let global be true.
      // 10. Else, let global be false.
      const globalCharString: String = StringConstant('g');
      const globalIndex: Smi = StringIndexOf(flagsString, globalCharString, 0);
      global = globalIndex != -1;

      // 11. If flags contains "u", let fullUnicode be true.
      // 12. Else, let fullUnicode be false.
      const unicodeCharString = StringConstant('u');
      const unicodeIndex: Smi =
          StringIndexOf(flagsString, unicodeCharString, 0);
      unicode = unicodeIndex != -1;
    }
  }

  // 13. Return ! CreateRegExpStringIterator(matcher, S, global, fullUnicode).
  return CreateRegExpStringIterator(
      nativeContext, matcher, string, global, unicode);
}

// https://tc39.github.io/proposal-string-matchall/
// RegExp.prototype [ @@matchAll ] ( string )
transitioning javascript builtin RegExpPrototypeMatchAll(
    js-implicit context: NativeContext, receiver: JSAny)(string: JSAny): JSAny {
  return RegExpPrototypeMatchAllImpl(context, receiver, string);
}

// https://tc39.github.io/proposal-string-matchall/
// %RegExpStringIteratorPrototype%.next ( )
transitioning javascript builtin RegExpStringIteratorPrototypeNext(
    js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
  // 1. Let O be the this value.
  // 2. If Type(O) is not Object, throw a TypeError exception.
  // 3. If O does not have all of the internal slots of a RegExp String
  // Iterator Object Instance (see 5.3), throw a TypeError exception.
  const methodName: constexpr string = '%RegExpStringIterator%.prototype.next';
  const receiver = Cast<JSRegExpStringIterator>(receiver) otherwise
  ThrowTypeError(
      MessageTemplate::kIncompatibleMethodReceiver, methodName, receiver);

  try {
    // 4. If O.[[Done]] is true, then
    //   a. Return ! CreateIterResultObject(undefined, true).
    const flags: SmiTagged<JSRegExpStringIteratorFlags> = receiver.flags;
    if (flags.done) goto ReturnEmptyDoneResult;

    // 5. Let R be O.[[iteratingRegExp]].
    const iteratingRegExp: JSReceiver = receiver.iterating_reg_exp;

    // 6. Let S be O.[[IteratedString]].
    const iteratingString: String = receiver.iterated_string;

    // 7. Let global be O.[[Global]].
    // 8. Let fullUnicode be O.[[Unicode]].
    // 9. Let match be ? RegExpExec(R, S).
    let match: Object;
    let isFastRegExp: bool = false;
    try {
      if (IsFastRegExpPermissive(iteratingRegExp)) {
        const regexp = UnsafeCast<JSRegExp>(iteratingRegExp);
        const lastIndex = LoadLastIndexAsLength(regexp, true);
        const matchIndices: RegExpMatchInfo =
            RegExpPrototypeExecBodyWithoutResultFast(
                regexp, iteratingString, lastIndex)
            otherwise IfNoMatch;
        match = ConstructNewResultFromMatchInfo(
            regexp, matchIndices, iteratingString, lastIndex);
        isFastRegExp = true;
      } else {
        match = RegExpExec(iteratingRegExp, iteratingString);
        if (match == Null) {
          goto IfNoMatch;
        }
      }
      // 11. Else,
      // b. Else, handle non-global case first.
      if (!flags.global) {
        // i. Set O.[[Done]] to true.
        receiver.flags.done = true;

        // ii. Return ! CreateIterResultObject(match, false).
        return AllocateJSIteratorResult(UnsafeCast<JSAny>(match), False);
      }
      // a. If global is true,
      assert(flags.global);
      if (isFastRegExp) {
        // i. Let matchStr be ? ToString(? Get(match, "0")).
        const match = UnsafeCast<JSRegExpResult>(match);
        const resultFixedArray = UnsafeCast<FixedArray>(match.elements);
        const matchStr = UnsafeCast<String>(resultFixedArray.objects[0]);

        // When iterating_regexp is fast, we assume it stays fast even after
        // accessing the first match from the RegExp result.
        assert(IsFastRegExpPermissive(iteratingRegExp));
        const iteratingRegExp = UnsafeCast<JSRegExp>(iteratingRegExp);
        if (matchStr == kEmptyString) {
          // 1. Let thisIndex be ? ToLength(? Get(R, "lastIndex")).
          const thisIndex: Smi = FastLoadLastIndex(iteratingRegExp);

          // 2. Let nextIndex be ! AdvanceStringIndex(S, thisIndex,
          // fullUnicode).
          const nextIndex: Smi =
              AdvanceStringIndexFast(iteratingString, thisIndex, flags.unicode);

          // 3. Perform ? Set(R, "lastIndex", nextIndex, true).
          FastStoreLastIndex(iteratingRegExp, nextIndex);
        }

        // iii. Return ! CreateIterResultObject(match, false).
        return AllocateJSIteratorResult(match, False);
      }
      assert(!isFastRegExp);
      // i. Let matchStr be ? ToString(? Get(match, "0")).
      const match = UnsafeCast<JSAny>(match);
      const matchStr = ToString_Inline(GetProperty(match, SmiConstant(0)));

      if (matchStr == kEmptyString) {
        // 1. Let thisIndex be ? ToLength(? Get(R, "lastIndex")).
        const lastIndex: JSAny = SlowLoadLastIndex(iteratingRegExp);
        const thisIndex: Number = ToLength_Inline(lastIndex);

        // 2. Let nextIndex be ! AdvanceStringIndex(S, thisIndex,
        // fullUnicode).
        const nextIndex: Number =
            AdvanceStringIndexSlow(iteratingString, thisIndex, flags.unicode);

        // 3. Perform ? Set(R, "lastIndex", nextIndex, true).
        SlowStoreLastIndex(iteratingRegExp, nextIndex);
      }
      // iii. Return ! CreateIterResultObject(match, false).
      return AllocateJSIteratorResult(match, False);
    }
    // 10. If match is null, then
    label IfNoMatch {
      // a. Set O.[[Done]] to true.
      receiver.flags.done = true;

      // b. Return ! CreateIterResultObject(undefined, true).
      goto ReturnEmptyDoneResult;
    }
  } label ReturnEmptyDoneResult {
    return AllocateJSIteratorResult(Undefined, True);
  }
}
}