// 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 {

const kATOM: constexpr int31
    generates 'JSRegExp::ATOM';
const kTagIndex: constexpr int31
    generates 'JSRegExp::kTagIndex';
const kAtomPatternIndex: constexpr int31
    generates 'JSRegExp::kAtomPatternIndex';

extern transitioning macro RegExpBuiltinsAssembler::FlagGetter(
    implicit context: Context)(Object, constexpr Flag, constexpr bool): bool;

extern macro UnsafeLoadFixedArrayElement(
    RegExpMatchInfo, constexpr int31): Object;

transitioning macro RegExpPrototypeMatchBody(implicit context: Context)(
    regexp: JSReceiver, string: String, isFastPath: constexpr bool): JSAny {
  if constexpr (isFastPath) {
    assert(Is<FastJSRegExp>(regexp));
  }

  const isGlobal: bool = FlagGetter(regexp, Flag::kGlobal, isFastPath);

  if (!isGlobal) {
    return isFastPath ? RegExpPrototypeExecBodyFast(regexp, string) :
                        RegExpExec(regexp, string);
  }

  assert(isGlobal);
  const isUnicode: bool = FlagGetter(regexp, Flag::kUnicode, isFastPath);

  StoreLastIndex(regexp, 0, isFastPath);

  // Allocate an array to store the resulting match strings.

  let array = growable_fixed_array::NewGrowableFixedArray();

  // Check if the regexp is an ATOM type. If so, then keep the literal string
  // to search for so that we can avoid calling substring in the loop below.
  let atom: bool = false;
  let searchString: String = EmptyStringConstant();
  if constexpr (isFastPath) {
    const maybeAtomRegexp = UnsafeCast<JSRegExp>(regexp);
    const data = UnsafeCast<FixedArray>(maybeAtomRegexp.data);
    if (UnsafeCast<Smi>(data.objects[kTagIndex]) == kATOM) {
      searchString = UnsafeCast<String>(data.objects[kAtomPatternIndex]);
      atom = true;
    }
  }

  while (true) {
    let match: String = EmptyStringConstant();
    try {
      if constexpr (isFastPath) {
        // On the fast path, grab the matching string from the raw match index
        // array.
        const matchIndices: RegExpMatchInfo =
            RegExpPrototypeExecBodyWithoutResultFast(
                UnsafeCast<JSRegExp>(regexp), string) otherwise IfDidNotMatch;
        if (atom) {
          match = searchString;
        } else {
          const matchFrom = UnsafeLoadFixedArrayElement(
              matchIndices, kRegExpMatchInfoFirstCaptureIndex);
          const matchTo = UnsafeLoadFixedArrayElement(
              matchIndices, kRegExpMatchInfoFirstCaptureIndex + 1);
          match = SubString(
              string, UnsafeCast<Smi>(matchFrom), UnsafeCast<Smi>(matchTo));
        }
      } else {
        assert(!isFastPath);
        const resultTemp = RegExpExec(regexp, string);
        if (resultTemp == Null) {
          goto IfDidNotMatch;
        }
        match = ToString_Inline(GetProperty(resultTemp, SmiConstant(0)));
      }
      goto IfDidMatch;
    } label IfDidNotMatch {
      return array.length == 0 ? Null : array.ToJSArray();
    } label IfDidMatch {
      // Store the match, growing the fixed array if needed.

      array.Push(match);

      // Advance last index if the match is the empty string.
      const matchLength: Smi = match.length_smi;
      if (matchLength != 0) {
        continue;
      }
      let lastIndex = LoadLastIndex(regexp, isFastPath);
      if constexpr (isFastPath) {
        assert(TaggedIsPositiveSmi(lastIndex));
      } else {
        lastIndex = ToLength_Inline(lastIndex);
      }

      const newLastIndex: Number = AdvanceStringIndex(
          string, UnsafeCast<Number>(lastIndex), isUnicode, isFastPath);

      if constexpr (isFastPath) {
        // On the fast path, we can be certain that lastIndex can never be
        // incremented to overflow the Smi range since the maximal string
        // length is less than the maximal Smi value.
        const kMaxStringLengthFitsSmi: constexpr bool =
            kStringMaxLengthUintptr < kSmiMaxValue;
        static_assert(kMaxStringLengthFitsSmi);
        assert(TaggedIsPositiveSmi(newLastIndex));
      }

      StoreLastIndex(regexp, newLastIndex, isFastPath);
    }
  }

  VerifiedUnreachable();
}

transitioning macro FastRegExpPrototypeMatchBody(implicit context: Context)(
    receiver: FastJSRegExp, string: String): JSAny {
  return RegExpPrototypeMatchBody(receiver, string, true);
}

transitioning macro SlowRegExpPrototypeMatchBody(implicit context: Context)(
    receiver: JSReceiver, string: String): JSAny {
  return RegExpPrototypeMatchBody(receiver, string, false);
}

// Helper that skips a few initial checks. and assumes...
// 1) receiver is a "fast" RegExp
// 2) pattern is a string
transitioning builtin RegExpMatchFast(implicit context: Context)(
    receiver: FastJSRegExp, string: String): JSAny {
  return FastRegExpPrototypeMatchBody(receiver, string);
}

// ES#sec-regexp.prototype-@@match
// RegExp.prototype [ @@match ] ( string )
transitioning javascript builtin RegExpPrototypeMatch(
    js-implicit context: NativeContext, receiver: JSAny)(string: JSAny): JSAny {
  ThrowIfNotJSReceiver(
      receiver, MessageTemplate::kIncompatibleMethodReceiver,
      'RegExp.prototype.@@match');
  const receiver = UnsafeCast<JSReceiver>(receiver);
  const string: String = ToString_Inline(string);

  // Strict: Reads global and unicode properties.
  // TODO(jgruber): Handle slow flag accesses on the fast path and make this
  // permissive.
  const fastRegExp = Cast<FastJSRegExp>(receiver)
      otherwise return SlowRegExpPrototypeMatchBody(receiver, string);

  // TODO(pwong): Could be optimized to remove the overhead of calling the
  //              builtin (at the cost of a larger builtin).
  return RegExpMatchFast(fastRegExp, string);
}
}