@ 261106.2
current
#include "glob/glob_unicode.h"

#include <algorithm>
#include <tuple>

#ifdef NODE_HAVE_I18N_SUPPORT
#include <unicode/uchar.h>
#include <unicode/uniset.h>
#include <unicode/ustring.h>
#endif  // NODE_HAVE_I18N_SUPPORT

namespace node::glob {

namespace {

constexpr uint32_t kMaxCodePoint = 0x10FFFF;
constexpr uint32_t kMaxCodeUnit = 0xFFFF;

#ifdef NODE_HAVE_I18N_SUPPORT
uint32_t PropertyMask(UnicodeProperty property) {
  switch (property) {
    case UnicodeProperty::kLetter:
      return U_GC_L_MASK;
    case UnicodeProperty::kLetterNumber:
      return U_GC_NL_MASK;
    case UnicodeProperty::kDecimalNumber:
      return U_GC_ND_MASK;
    case UnicodeProperty::kSpaceSeparator:
      return U_GC_ZS_MASK;
    case UnicodeProperty::kControl:
      return U_GC_CC_MASK;
    case UnicodeProperty::kSeparator:
      return U_GC_Z_MASK;
    case UnicodeProperty::kOther:
      return U_GC_C_MASK;
    case UnicodeProperty::kLowercase:
      return U_GC_LL_MASK;
    case UnicodeProperty::kUppercase:
      return U_GC_LU_MASK;
    case UnicodeProperty::kPunctuation:
      return U_GC_P_MASK;
    case UnicodeProperty::kConnector:
      return U_GC_PC_MASK;
  }
  return 0;
}
#endif  // NODE_HAVE_I18N_SUPPORT

}  // namespace

void CharSet::AddRange(uint32_t lo, uint32_t hi) {
  if (lo > hi) return;
  ranges_.push_back({lo, std::min(hi, kMaxCodePoint)});
}

bool CharSet::AddProperty(UnicodeProperty property) {
#ifdef NODE_HAVE_I18N_SUPPORT
  const uint32_t mask = PropertyMask(property);
  UErrorCode status = U_ZERO_ERROR;
  icu::UnicodeSet set;
  set.applyIntPropertyValue(
      UCHAR_GENERAL_CATEGORY_MASK, static_cast<int32_t>(mask), status);
  if (U_FAILURE(status)) return false;
  const int32_t count = set.getRangeCount();
  for (int32_t i = 0; i < count; i++) {
    AddRange(static_cast<uint32_t>(set.getRangeStart(i)),
             static_cast<uint32_t>(set.getRangeEnd(i)));
  }
  return true;
#else
  return false;
#endif  // NODE_HAVE_I18N_SUPPORT
}

void CharSet::Seal() {
  if (ranges_.empty()) return;
  std::ranges::sort(
      ranges_, {}, [](const Range& r) { return std::tie(r.lo, r.hi); });
  std::vector<Range> merged;
  merged.push_back(ranges_.front());
  for (size_t i = 1; i < ranges_.size(); i++) {
    Range& last = merged.back();
    const Range& next = ranges_[i];
    // Adjacent ranges merge too, so the result is canonical.
    if (next.lo <= last.hi || next.lo == last.hi + 1) {
      last.hi = std::max(last.hi, next.hi);
    } else {
      merged.push_back(next);
    }
  }
  ranges_ = std::move(merged);
}

bool CharSet::CloseOverCaseFold() {
  Seal();
#ifdef NODE_HAVE_I18N_SUPPORT
  icu::UnicodeSet set;
  for (const Range& range : ranges_) {
    set.add(static_cast<UChar32>(range.lo), static_cast<UChar32>(range.hi));
  }
  set.closeOver(USET_CASE_INSENSITIVE);
  ranges_.clear();
  const int32_t count = set.getRangeCount();
  for (int32_t i = 0; i < count; i++) {
    AddRange(static_cast<uint32_t>(set.getRangeStart(i)),
             static_cast<uint32_t>(set.getRangeEnd(i)));
  }
  Seal();
  return true;
#else
  const std::vector<Range> original = ranges_;
  for (const Range& range : original) {
    for (uint32_t cp = range.lo; cp <= std::min(range.hi, uint32_t{'z'});
         cp++) {
      AddChar(AsciiLower(cp));
      AddChar(AsciiUpper(cp));
    }
  }
  Seal();
  return false;
#endif  // NODE_HAVE_I18N_SUPPORT
}

void CharSet::CanonicalizeMembers() {
  Seal();
  const std::vector<Range> original = ranges_;
  ranges_.clear();
  for (const Range& range : original) {
    if (range.lo > kMaxCodeUnit) continue;
    const uint32_t hi = std::min(range.hi, kMaxCodeUnit);
    for (uint32_t cp = range.lo; cp <= hi; cp++) {
      AddChar(Canonicalize(cp, /*fold=*/false));
      if (cp == kMaxCodeUnit) break;
    }
  }
  Seal();
}

bool CharSet::Contains(uint32_t cp) const {
  // Seal() left the ranges sorted, so this is fairly simple.
  const auto after = std::ranges::upper_bound(
      ranges_, cp, {}, [](const Range& r) { return r.lo; });
  return after != ranges_.begin() && cp <= std::prev(after)->hi;
}

uint32_t Canonicalize(uint32_t cp, bool fold) {
  if (cp <= kMaxAsciiCodePoint) return AsciiUpper(cp);
#ifdef NODE_HAVE_I18N_SUPPORT
  if (fold) {
    return static_cast<uint32_t>(
        u_foldCase(static_cast<UChar32>(cp), U_FOLD_CASE_DEFAULT));
  }
  const uint32_t upper =
      static_cast<uint32_t>(u_toupper(static_cast<UChar32>(cp)));

  // A non-ASCII character whose uppercase mapping is ASCII must
  // not fold into it
  return upper <= kMaxAsciiCodePoint ? cp : upper;
#else
  return cp;
#endif  // NODE_HAVE_I18N_SUPPORT
}

void AppendLowered(const std::u16string& text, std::u16string* out) {
#ifdef NODE_HAVE_I18N_SUPPORT
  const int32_t length = static_cast<int32_t>(text.size());
  const size_t offset = out->size();
  // Lowercasing can lengthen a strings (oddly, see U+0130) so
  // reserve a little slack.
  //
  // Should a pattern for whatever reason contain a number of these
  // odd expansions, the U_BUFFER_OVERFLOW_ERROR case below will handle
  // them using a slower path.
  out->resize(offset + text.size() + 8);
  UErrorCode status = U_ZERO_ERROR;
  int32_t written = u_strToLower(reinterpret_cast<UChar*>(out->data() + offset),
                                 static_cast<int32_t>(out->size() - offset),
                                 reinterpret_cast<const UChar*>(text.data()),
                                 length,
                                 "",
                                 &status);
  if (status == U_BUFFER_OVERFLOW_ERROR) {
    out->resize(offset + written);
    status = U_ZERO_ERROR;
    written = u_strToLower(reinterpret_cast<UChar*>(out->data() + offset),
                           static_cast<int32_t>(out->size() - offset),
                           reinterpret_cast<const UChar*>(text.data()),
                           length,
                           "",
                           &status);
  }
  if (U_FAILURE(status)) {
    out->resize(offset);
    out->append(text);
    return;
  }
  out->resize(offset + written);
#else
  for (char16_t c : text) {
    out->push_back(static_cast<char16_t>(AsciiLower(c)));
  }
#endif  // NODE_HAVE_I18N_SUPPORT
}

}  // namespace node::glob