// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#include "vm/regexp/regexp.h"
#include "platform/assert.h"
#include "vm/bootstrap_natives.h"
#include "vm/canonical_tables.h"
#include "vm/exceptions.h"
#include "vm/native_entry.h"
#include "vm/object.h"
#include "vm/object_store.h"
#include "vm/regexp/regexp-parser.h"
#include "vm/reusable_handles.h"
#include "vm/symbols.h"
#include "vm/thread.h"

namespace dart {

DEFINE_NATIVE_ENTRY(RegExp_factory, 0, 5) {
  GET_NON_NULL_NATIVE_ARGUMENT(String, pattern, arguments->NativeArgAt(0));

  bool multi_line = arguments->NativeArgAt(1) == Bool::True().ptr();
  bool ignore_case = arguments->NativeArgAt(2) != Bool::True().ptr();
  bool unicode = arguments->NativeArgAt(3) == Bool::True().ptr();
  bool dot_all = arguments->NativeArgAt(4) == Bool::True().ptr();

  RegExpFlags flags;
  flags |= RegExpFlag::kGlobal;  // All dart regexps are global.
  if (ignore_case) flags |= RegExpFlag::kIgnoreCase;
  if (multi_line) flags |= RegExpFlag::kMultiline;
  if (unicode) flags |= RegExpFlag::kUnicode;
  if (dot_all) flags |= RegExpFlag::kDotAll;

  RegExpKey lookup_key(pattern, flags);
  RegExp& regexp = RegExp::Handle(thread->zone());
  {
    REUSABLE_OBJECT_HANDLESCOPE(thread);
    REUSABLE_SMI_HANDLESCOPE(thread);
    REUSABLE_WEAK_ARRAY_HANDLESCOPE(thread);
    Object& key = thread->ObjectHandle();
    Smi& value = thread->SmiHandle();
    WeakArray& data = thread->WeakArrayHandle();
    data = thread->isolate_group()->object_store()->regexp_table();
    CanonicalRegExpSet table(&key, &value, &data);
    regexp ^= table.GetOrNull(lookup_key);
    table.Release();
    if (!regexp.IsNull()) {
      return regexp.ptr();
    }
  }

  // Parse the pattern once in order to throw any format exceptions within
  // the factory constructor. It is parsed again upon compilation.
  RegExpCompileData compileData;
  // Throws an exception on parsing failure.
  if (!RegExpParser::ParseRegExpFromHeapString(isolate, zone, pattern, flags,
                                               &compileData)) {
    USE(RegExpStatics::ThrowRegExpException(isolate, flags, pattern,
                                            compileData.error));
    UNREACHABLE();
  }

  {
    RegExpKey lookup_symbol_key(String::Handle(Symbols::New(thread, pattern)),
                                flags);
    SafepointMutexLocker ml(thread->isolate_group()->symbols_mutex());
    CanonicalRegExpSet table(
        thread->zone(),
        thread->isolate_group()->object_store()->regexp_table());
    regexp ^= table.InsertNewOrGet(lookup_symbol_key);
    thread->isolate_group()->object_store()->set_regexp_table(table.Release());
  }

  ASSERT(regexp.flags() == flags);
  return regexp.ptr();
}

DEFINE_NATIVE_ENTRY(RegExp_getPattern, 0, 1) {
  const RegExp& regexp = RegExp::CheckedHandle(zone, arguments->NativeArgAt(0));
  ASSERT(!regexp.IsNull());
  return regexp.pattern();
}

DEFINE_NATIVE_ENTRY(RegExp_getIsMultiLine, 0, 1) {
  const RegExp& regexp = RegExp::CheckedHandle(zone, arguments->NativeArgAt(0));
  ASSERT(!regexp.IsNull());
  return Bool::Get(IsMultiline(regexp.flags())).ptr();
}

DEFINE_NATIVE_ENTRY(RegExp_getIsUnicode, 0, 1) {
  const RegExp& regexp = RegExp::CheckedHandle(zone, arguments->NativeArgAt(0));
  ASSERT(!regexp.IsNull());
  return Bool::Get(IsUnicode(regexp.flags())).ptr();
}

DEFINE_NATIVE_ENTRY(RegExp_getIsDotAll, 0, 1) {
  const RegExp& regexp = RegExp::CheckedHandle(zone, arguments->NativeArgAt(0));
  ASSERT(!regexp.IsNull());
  return Bool::Get(IsDotAll(regexp.flags())).ptr();
}

DEFINE_NATIVE_ENTRY(RegExp_getIsCaseSensitive, 0, 1) {
  const RegExp& regexp = RegExp::CheckedHandle(zone, arguments->NativeArgAt(0));
  ASSERT(!regexp.IsNull());
  return Bool::Get(!IsIgnoreCase(regexp.flags())).ptr();
}

static ObjectPtr ThrowUninitialized(const RegExp& regexp) {
  const String& pattern = String::Handle(regexp.pattern());
  const String& errmsg =
      String::Handle(String::New("Regular expression is not initialized yet."));
  const String& message = String::Handle(String::Concat(errmsg, pattern));
  const Array& args = Array::Handle(Array::New(1));
  args.SetAt(0, message);
  Exceptions::ThrowByType(Exceptions::kState, args);
  return Object::null();
}

DEFINE_NATIVE_ENTRY(RegExp_getGroupCount, 0, 1) {
  const RegExp& regexp = RegExp::CheckedHandle(zone, arguments->NativeArgAt(0));
  ASSERT(!regexp.IsNull());
  if (regexp.num_bracket_expressions() != -1) {
    return Smi::New(regexp.num_bracket_expressions());
  }
  return ThrowUninitialized(regexp);
}

DEFINE_NATIVE_ENTRY(RegExp_getGroupNameMap, 0, 1) {
  const RegExp& regexp = RegExp::CheckedHandle(zone, arguments->NativeArgAt(0));
  ASSERT(!regexp.IsNull());
  if (regexp.num_bracket_expressions<std::memory_order_acquire>() != -1) {
    return regexp.capture_name_map();
  }
  return ThrowUninitialized(regexp);
}

static ObjectPtr ExecuteMatch(Thread* thread,
                              Zone* zone,
                              NativeArguments* arguments,
                              bool sticky) {
  const RegExp& regexp = RegExp::CheckedHandle(zone, arguments->NativeArgAt(0));
  ASSERT(!regexp.IsNull());
  GET_NON_NULL_NATIVE_ARGUMENT(String, subject, arguments->NativeArgAt(1));
  GET_NON_NULL_NATIVE_ARGUMENT(Smi, start_index, arguments->NativeArgAt(2));

  // Both generated code and the interpreter are using 32-bit registers and
  // 32-bit backtracking stack so they can't work with strings which are
  // larger than that. Validate these assumptions before running the regexp.
  if (!Utils::IsInt(32, subject.Length())) {
    Exceptions::ThrowRangeError("length",
                                Integer::Handle(Integer::New(subject.Length())),
                                0, kMaxInt32);
  }
  if (!Utils::IsInt(32, start_index.Value())) {
    Exceptions::ThrowRangeError("start_index", Integer::Cast(start_index),
                                kMinInt32, kMaxInt32);
  }

  return RegExpStatics::Interpret(thread, regexp, subject, start_index.Value(),
                                  sticky);
}

DEFINE_NATIVE_ENTRY(RegExp_ExecuteMatch, 0, 3) {
  // This function is intrinsified. See Intrinsifier::RegExp_ExecuteMatch.
  return ExecuteMatch(thread, zone, arguments, /*sticky=*/false);
}

DEFINE_NATIVE_ENTRY(RegExp_ExecuteMatchSticky, 0, 3) {
  // This function is intrinsified. See Intrinsifier::RegExp_ExecuteMatchSticky.
  return ExecuteMatch(thread, zone, arguments, /*sticky=*/true);
}

}  // namespace dart
