//===-- RegularExpression.cpp -----------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "lldb/Utility/RegularExpression.h"

#include "llvm/ADT/StringRef.h"

#include <string>

// Enable enhanced mode if it is available. This allows for things like \d for
// digit, \s for space, and many more, but it isn't available everywhere.
#if defined(REG_ENHANCED)
#define DEFAULT_COMPILE_FLAGS (REG_ENHANCED | REG_EXTENDED)
#else
#define DEFAULT_COMPILE_FLAGS (REG_EXTENDED)
#endif

using namespace lldb_private;

RegularExpression::RegularExpression() : m_re(), m_comp_err(1), m_preg() {
  memset(&m_preg, 0, sizeof(m_preg));
}

// Constructor that compiles "re" using "flags" and stores the resulting
// compiled regular expression into this object.
RegularExpression::RegularExpression(llvm::StringRef str)
    : RegularExpression() {
  Compile(str);
}

RegularExpression::RegularExpression(const RegularExpression &rhs)
    : RegularExpression() {
  Compile(rhs.GetText());
}

const RegularExpression &RegularExpression::
operator=(const RegularExpression &rhs) {
  if (&rhs != this)
    Compile(rhs.GetText());
  return *this;
}

// Destructor
//
// Any previously compiled regular expression contained in this object will be
// freed.
RegularExpression::~RegularExpression() { Free(); }

// Compile a regular expression using the supplied regular expression text and
// flags. The compiled regular expression lives in this object so that it can
// be readily used for regular expression matches. Execute() can be called
// after the regular expression is compiled. Any previously compiled regular
// expression contained in this object will be freed.
//
// RETURNS
//  True if the regular expression compiles successfully, false
//  otherwise.
bool RegularExpression::Compile(llvm::StringRef str) {
  Free();

  // regcomp() on darwin does not recognize "" as a valid regular expression,
  // so we substitute it with an equivalent non-empty one.
  m_re = str.empty() ? "()" : str;
  m_comp_err = ::regcomp(&m_preg, m_re.c_str(), DEFAULT_COMPILE_FLAGS);
  return m_comp_err == 0;
}

// Execute a regular expression match using the compiled regular expression
// that is already in this object against the match string "s". If any parens
// are used for regular expression matches "match_count" should indicate the
// number of regmatch_t values that are present in "match_ptr". The regular
// expression will be executed using the "execute_flags".
bool RegularExpression::Execute(llvm::StringRef str, Match *match) const {
  int err = 1;
  if (m_comp_err == 0) {
    // Argument to regexec must be null-terminated.
    std::string reg_str = str;
    if (match) {
      err = ::regexec(&m_preg, reg_str.c_str(), match->GetSize(),
                      match->GetData(), 0);
    } else {
      err = ::regexec(&m_preg, reg_str.c_str(), 0, nullptr, 0);
    }
  }

  if (err != 0) {
    // The regular expression didn't compile, so clear the matches
    if (match)
      match->Clear();
    return false;
  }
  return true;
}

bool RegularExpression::Match::GetMatchAtIndex(llvm::StringRef s, uint32_t idx,
                                               std::string &match_str) const {
  llvm::StringRef match_str_ref;
  if (GetMatchAtIndex(s, idx, match_str_ref)) {
    match_str = match_str_ref.str();
    return true;
  }
  return false;
}

bool RegularExpression::Match::GetMatchAtIndex(
    llvm::StringRef s, uint32_t idx, llvm::StringRef &match_str) const {
  if (idx < m_matches.size()) {
    if (m_matches[idx].rm_eo == -1 && m_matches[idx].rm_so == -1)
      return false;

    if (m_matches[idx].rm_eo == m_matches[idx].rm_so) {
      // Matched the empty string...
      match_str = llvm::StringRef();
      return true;
    } else if (m_matches[idx].rm_eo > m_matches[idx].rm_so) {
      match_str = s.substr(m_matches[idx].rm_so,
                           m_matches[idx].rm_eo - m_matches[idx].rm_so);
      return true;
    }
  }
  return false;
}

bool RegularExpression::Match::GetMatchSpanningIndices(
    llvm::StringRef s, uint32_t idx1, uint32_t idx2,
    llvm::StringRef &match_str) const {
  if (idx1 < m_matches.size() && idx2 < m_matches.size()) {
    if (m_matches[idx1].rm_so == m_matches[idx2].rm_eo) {
      // Matched the empty string...
      match_str = llvm::StringRef();
      return true;
    } else if (m_matches[idx1].rm_so < m_matches[idx2].rm_eo) {
      match_str = s.substr(m_matches[idx1].rm_so,
                           m_matches[idx2].rm_eo - m_matches[idx1].rm_so);
      return true;
    }
  }
  return false;
}

// Returns true if the regular expression compiled and is ready for execution.
bool RegularExpression::IsValid() const { return m_comp_err == 0; }

// Returns the text that was used to compile the current regular expression.
llvm::StringRef RegularExpression::GetText() const { return m_re; }

// Free any contained compiled regular expressions.
void RegularExpression::Free() {
  if (m_comp_err == 0) {
    m_re.clear();
    regfree(&m_preg);
    // Set a compile error since we no longer have a valid regex
    m_comp_err = 1;
  }
}

size_t RegularExpression::GetErrorAsCString(char *err_str,
                                            size_t err_str_max_len) const {
  if (m_comp_err == 0) {
    if (err_str && err_str_max_len)
      *err_str = '\0';
    return 0;
  }

  return ::regerror(m_comp_err, &m_preg, err_str, err_str_max_len);
}

bool RegularExpression::operator<(const RegularExpression &rhs) const {
  return (m_re < rhs.m_re);
}
