// Generated by make_intl_data.py. DO NOT EDIT.
// Version: CLDR-37
// URL: https://unicode.org/Public/cldr/37/core.zip

#include "mozilla/Assertions.h"
#include "mozilla/Span.h"
#include "mozilla/TextUtils.h"

#include <algorithm>
#include <cstdint>
#include <cstring>
#include <iterator>
#include <string>
#include <type_traits>

#include "builtin/intl/LanguageTag.h"
#include "util/Text.h"
#include "vm/JSContext.h"

using namespace js::intl::LanguageTagLimits;

template <size_t Length, size_t TagLength, size_t SubtagLength>
static inline bool HasReplacement(
    const char (&subtags)[Length][TagLength],
    const js::intl::LanguageTagSubtag<SubtagLength>& subtag) {
  MOZ_ASSERT(subtag.length() == TagLength - 1,
             "subtag must have the same length as the list of subtags");

  const char* ptr = subtag.span().data();
  return std::binary_search(std::begin(subtags), std::end(subtags), ptr,
                            [](const char* a, const char* b) {
    return memcmp(a, b, TagLength - 1) < 0;
  });
}

template <size_t Length, size_t TagLength, size_t SubtagLength>
static inline const char* SearchReplacement(
    const char (&subtags)[Length][TagLength],
    const char* (&aliases)[Length],
    const js::intl::LanguageTagSubtag<SubtagLength>& subtag) {
  MOZ_ASSERT(subtag.length() == TagLength - 1,
             "subtag must have the same length as the list of subtags");

  const char* ptr = subtag.span().data();
  auto p = std::lower_bound(std::begin(subtags), std::end(subtags), ptr,
                            [](const char* a, const char* b) {
    return memcmp(a, b, TagLength - 1) < 0;
  });
  if (p != std::end(subtags) && memcmp(*p, ptr, TagLength - 1) == 0) {
    return aliases[std::distance(std::begin(subtags), p)];
  }
  return nullptr;
}

#ifdef DEBUG
static bool IsAsciiLowercaseAlphanumeric(char c) {
  return mozilla::IsAsciiLowercaseAlpha(c) || mozilla::IsAsciiDigit(c);
}

static bool IsAsciiLowercaseAlphanumericOrDash(char c) {
  return IsAsciiLowercaseAlphanumeric(c) || c == '-';
}

static bool IsCanonicallyCasedLanguageTag(mozilla::Span<const char> span) {
  // Tell the analysis the |std::all_of| function can't GC.
  JS::AutoSuppressGCAnalysis nogc;

  return std::all_of(span.begin(), span.end(), mozilla::IsAsciiLowercaseAlpha<char>);
}

static bool IsCanonicallyCasedRegionTag(mozilla::Span<const char> span) {
  // Tell the analysis the |std::all_of| function can't GC.
  JS::AutoSuppressGCAnalysis nogc;

  return std::all_of(span.begin(), span.end(), mozilla::IsAsciiUppercaseAlpha<char>) ||
         std::all_of(span.begin(), span.end(), mozilla::IsAsciiDigit<char>);
}

static bool IsCanonicallyCasedVariantTag(mozilla::Span<const char> span) {
  // Tell the analysis the |std::all_of| function can't GC.
  JS::AutoSuppressGCAnalysis nogc;

  return std::all_of(span.begin(), span.end(), IsAsciiLowercaseAlphanumeric);
}

static bool IsCanonicallyCasedUnicodeKey(mozilla::Span<const char> key) {
  return std::all_of(key.begin(), key.end(), IsAsciiLowercaseAlphanumeric);
}

static bool IsCanonicallyCasedUnicodeType(mozilla::Span<const char> type) {
  return std::all_of(type.begin(), type.end(), IsAsciiLowercaseAlphanumericOrDash);
}

static bool IsCanonicallyCasedTransformKey(mozilla::Span<const char> key) {
  return std::all_of(key.begin(), key.end(), IsAsciiLowercaseAlphanumeric);
}

static bool IsCanonicallyCasedTransformType(mozilla::Span<const char> type) {
  return std::all_of(type.begin(), type.end(), IsAsciiLowercaseAlphanumericOrDash);
}
#endif

// Mappings from language subtags to preferred values.
// Derived from CLDR Supplemental Data, version 37.
// https://unicode.org/Public/cldr/37/core.zip
bool js::intl::LanguageTag::languageMapping(LanguageSubtag& language) {
  MOZ_ASSERT(IsStructurallyValidLanguageTag(language.span()));
  MOZ_ASSERT(IsCanonicallyCasedLanguageTag(language.span()));

  if (language.length() == 2) {
    static const char languages[9][3] = {
      "bh", "in", "iw", "ji", "jw", "mo", "no", "tl", "tw",
    };
    static const char* aliases[9] = {
      "bho", "id", "he", "yi", "jv", "ro", "nb", "fil", "ak",
    };

    if (const char* replacement = SearchReplacement(languages, aliases, language)) {
      language.set(mozilla::MakeStringSpan(replacement));
      return true;
    }
    return false;
  }

  if (language.length() == 3) {
    static const char languages[345][4] = {
      "aam", "aar", "abk", "adp", "afr", "aju", "aka", "alb", "als", "amh",
      "ara", "arb", "arg", "arm", "asd", "asm", "aue", "ava", "ave", "aym",
      "ayr", "ayx", "aze", "azj", "bak", "bam", "baq", "bcc", "bcl", "bel",
      "ben", "bgm", "bih", "bis", "bjd", "bod", "bos", "bre", "bul", "bur",
      "bxk", "bxr", "cat", "ccq", "ces", "cha", "che", "chi", "chu", "chv",
      "cjr", "cka", "cld", "cmk", "cmn", "cor", "cos", "coy", "cqu", "cre",
      "cwd", "cym", "cze", "dan", "deu", "dgo", "dhd", "dik", "diq", "dit",
      "div", "drh", "dut", "dzo", "ekk", "ell", "emk", "eng", "epo", "esk",
      "est", "eus", "ewe", "fao", "fas", "fat", "fij", "fin", "fra", "fre",
      "fry", "fuc", "ful", "gav", "gaz", "gbo", "geo", "ger", "gfx", "ggn",
      "gla", "gle", "glg", "glv", "gno", "gre", "grn", "gti", "gug", "guj",
      "guv", "gya", "hat", "hau", "hdn", "hea", "heb", "her", "him", "hin",
      "hmo", "hrr", "hrv", "hun", "hye", "ibi", "ibo", "ice", "ido", "iii",
      "ike", "iku", "ile", "ilw", "ina", "ind", "ipk", "isl", "ita", "jav",
      "jeg", "jpn", "kal", "kan", "kas", "kat", "kau", "kaz", "kgc", "kgh",
      "khk", "khm", "kik", "kin", "kir", "kmr", "knc", "kng", "knn", "koj",
      "kom", "kon", "kor", "kpv", "krm", "ktr", "kua", "kur", "kvs", "kwq",
      "kxe", "kzj", "kzt", "lao", "lat", "lav", "lbk", "lii", "lim", "lin",
      "lit", "llo", "lmm", "ltz", "lub", "lug", "lvs", "mac", "mah", "mal",
      "mao", "mar", "may", "meg", "mhr", "mkd", "mlg", "mlt", "mnk", "mol",
      "mon", "mri", "msa", "mst", "mup", "mwj", "mya", "myd", "myt", "nad",
      "nau", "nav", "nbl", "ncp", "nde", "ndo", "nep", "nld", "nno", "nns",
      "nnx", "nob", "nor", "npi", "nts", "nya", "oci", "ojg", "oji", "ori",
      "orm", "ory", "oss", "oun", "pan", "pbu", "pcr", "per", "pes", "pli",
      "plt", "pmc", "pmu", "pnb", "pol", "por", "ppa", "ppr", "pry", "pus",
      "puz", "que", "quz", "rmy", "roh", "ron", "rum", "run", "rus", "sag",
      "san", "sca", "scc", "scr", "sin", "skk", "slk", "slo", "slv", "sme",
      "smo", "sna", "snd", "som", "sot", "spa", "spy", "sqi", "src", "srd",
      "srp", "ssw", "sun", "swa", "swe", "swh", "tah", "tam", "tat", "tdu",
      "tel", "tgk", "tgl", "tha", "thc", "thx", "tib", "tie", "tir", "tkk",
      "tlw", "tmp", "tne", "ton", "tsf", "tsn", "tso", "ttq", "tuk", "tur",
      "twi", "uig", "ukr", "umu", "uok", "urd", "uzb", "uzn", "ven", "vie",
      "vol", "wel", "wln", "wol", "xba", "xho", "xia", "xkh", "xpe", "xsj",
      "xsl", "ybd", "ydd", "yid", "yma", "ymt", "yor", "yos", "yuu", "zai",
      "zha", "zho", "zsm", "zul", "zyb",
    };
    static const char* aliases[345] = {
      "aas",  "aa",  "ab",  "dz",  "af", "jrb",  "ak",  "sq",  "sq",  "am",
       "ar",  "ar",  "an",  "hy", "snz",  "as", "ktz",  "av",  "ae",  "ay",
       "ay", "nun",  "az",  "az",  "ba",  "bm",  "eu", "bal", "bik",  "be",
       "bn", "bcg", "bho",  "bi", "drl",  "bo",  "bs",  "br",  "bg",  "my",
      "luy", "bua",  "ca", "rki",  "cs",  "ch",  "ce",  "zh",  "cu",  "cv",
      "mom", "cmr", "syr", "xch",  "zh",  "kw",  "co", "pij", "quh",  "cr",
       "cr",  "cy",  "cs",  "da",  "de", "doi", "mwr", "din", "zza", "dif",
       "dv",  "mn",  "nl",  "dz",  "et",  "el", "man",  "en",  "eo",  "ik",
       "et",  "eu",  "ee",  "fo",  "fa",  "ak",  "fj",  "fi",  "fr",  "fr",
       "fy",  "ff",  "ff", "dev",  "om", "grb",  "ka",  "de", "vaj", "gvr",
       "gd",  "ga",  "gl",  "gv", "gon",  "el",  "gn", "nyc",  "gn",  "gu",
      "duz", "gba",  "ht",  "ha", "hai", "hmn",  "he",  "hz", "srx",  "hi",
       "ho", "jal",  "hr",  "hu",  "hy", "opa",  "ig",  "is",  "io",  "ii",
       "iu",  "iu",  "ie", "gal",  "ia",  "id",  "ik",  "is",  "it",  "jv",
      "oyb",  "ja",  "kl",  "kn",  "ks",  "ka",  "kr",  "kk", "tdf", "kml",
       "mn",  "km",  "ki",  "rw",  "ky",  "ku",  "kr",  "kg", "kok", "kwv",
       "kv",  "kg",  "ko",  "kv", "bmf", "dtp",  "kj",  "ku", "gdj", "yam",
      "tvd", "dtp", "dtp",  "lo",  "la",  "lv", "bnc", "raq",  "li",  "ln",
       "lt", "ngt", "rmx",  "lb",  "lu",  "lg",  "lv",  "mk",  "mh",  "ml",
       "mi",  "mr",  "ms", "cir", "chm",  "mk",  "mg",  "mt", "man",  "ro",
       "mn",  "mi",  "ms", "mry", "raj", "vaj",  "my", "aog", "mry", "xny",
       "na",  "nv",  "nr", "kdz",  "nd",  "ng",  "ne",  "nl",  "nn", "nbr",
      "ngv",  "nb",  "nb",  "ne", "pij",  "ny",  "oc",  "oj",  "oj",  "or",
       "om",  "or",  "os", "vaj",  "pa",  "ps", "adx",  "fa",  "fa",  "pi",
       "mg", "huw", "phr", "lah",  "pl",  "pt", "bfy", "lcq", "prt",  "ps",
      "pub",  "qu",  "qu", "rom",  "rm",  "ro",  "ro",  "rn",  "ru",  "sg",
       "sa", "hle",  "sr",  "hr",  "si", "oyb",  "sk",  "sk",  "sl",  "se",
       "sm",  "sn",  "sd",  "so",  "st",  "es", "kln",  "sq",  "sc",  "sc",
       "sr",  "ss",  "su",  "sw",  "sv",  "sw",  "ty",  "ta",  "tt", "dtp",
       "te",  "tg", "fil",  "th", "tpo", "oyb",  "bo", "ras",  "ti", "twm",
      "weo", "tyj", "kak",  "to", "taj",  "tn",  "ts", "tmh",  "tk",  "tr",
       "ak",  "ug",  "uk", "del", "ema",  "ur",  "uz",  "uz",  "ve",  "vi",
       "vo",  "cy",  "wa",  "wo", "cax",  "xh", "acn", "waw", "kpe", "suj",
      "den", "rki",  "yi",  "yi", "lrr", "mtm",  "yo", "zom", "yug", "zap",
       "za",  "zh",  "ms",  "zu",  "za",
    };

    if (const char* replacement = SearchReplacement(languages, aliases, language)) {
      language.set(mozilla::MakeStringSpan(replacement));
      return true;
    }
    return false;
  }

  return false;
}

// Language subtags with complex mappings.
// Derived from CLDR Supplemental Data, version 37.
// https://unicode.org/Public/cldr/37/core.zip
bool js::intl::LanguageTag::complexLanguageMapping(const LanguageSubtag& language) {
  MOZ_ASSERT(IsStructurallyValidLanguageTag(language.span()));
  MOZ_ASSERT(IsCanonicallyCasedLanguageTag(language.span()));

  if (language.length() == 2) {
    return language.equalTo("sh");
  }

  if (language.length() == 3) {
    static const char languages[6][4] = {
      "cnr", "drw", "hbs", "prs", "swc", "tnf",
    };

    return HasReplacement(languages, language);
  }

  return false;
}

// Mappings from region subtags to preferred values.
// Derived from CLDR Supplemental Data, version 37.
// https://unicode.org/Public/cldr/37/core.zip
bool js::intl::LanguageTag::regionMapping(RegionSubtag& region) {
  MOZ_ASSERT(IsStructurallyValidRegionTag(region.span()));
  MOZ_ASSERT(IsCanonicallyCasedRegionTag(region.span()));

  if (region.length() == 2) {
    static const char regions[23][3] = {
      "BU", "CS", "CT", "DD", "DY", "FQ", "FX", "HV", "JT", "MI",
      "NH", "NQ", "PU", "PZ", "QU", "RH", "TP", "UK", "VD", "WK",
      "YD", "YU", "ZR",
    };
    static const char* aliases[23] = {
      "MM", "RS", "KI", "DE", "BJ", "AQ", "FR", "BF", "UM", "UM",
      "VU", "AQ", "UM", "PA", "EU", "ZW", "TL", "GB", "VN", "UM",
      "YE", "RS", "CD",
    };

    if (const char* replacement = SearchReplacement(regions, aliases, region)) {
      region.set(mozilla::MakeStringSpan(replacement));
      return true;
    }
    return false;
  }

  {
    static const char regions[300][4] = {
      "004", "008", "010", "012", "016", "020", "024", "028", "031", "032",
      "036", "040", "044", "048", "050", "051", "052", "056", "060", "062",
      "064", "068", "070", "072", "074", "076", "084", "086", "090", "092",
      "096", "100", "104", "108", "112", "116", "120", "124", "132", "136",
      "140", "144", "148", "152", "156", "158", "162", "166", "170", "174",
      "175", "178", "180", "184", "188", "191", "192", "196", "203", "204",
      "208", "212", "214", "218", "222", "226", "230", "231", "232", "233",
      "234", "238", "239", "242", "246", "248", "249", "250", "254", "258",
      "260", "262", "266", "268", "270", "275", "276", "278", "280", "288",
      "292", "296", "300", "304", "308", "312", "316", "320", "324", "328",
      "332", "334", "336", "340", "344", "348", "352", "356", "360", "364",
      "368", "372", "376", "380", "384", "388", "392", "398", "400", "404",
      "408", "410", "414", "417", "418", "422", "426", "428", "430", "434",
      "438", "440", "442", "446", "450", "454", "458", "462", "466", "470",
      "474", "478", "480", "484", "492", "496", "498", "499", "500", "504",
      "508", "512", "516", "520", "524", "528", "531", "533", "534", "535",
      "540", "548", "554", "558", "562", "566", "570", "574", "578", "580",
      "581", "583", "584", "585", "586", "591", "598", "600", "604", "608",
      "612", "616", "620", "624", "626", "630", "634", "638", "642", "643",
      "646", "652", "654", "659", "660", "662", "663", "666", "670", "674",
      "678", "682", "686", "688", "690", "694", "702", "703", "704", "705",
      "706", "710", "716", "720", "724", "728", "729", "732", "736", "740",
      "744", "748", "752", "756", "760", "762", "764", "768", "772", "776",
      "780", "784", "788", "792", "795", "796", "798", "800", "804", "807",
      "818", "826", "830", "831", "832", "833", "834", "840", "850", "854",
      "858", "860", "862", "876", "882", "886", "887", "891", "894", "958",
      "959", "960", "962", "963", "964", "965", "966", "967", "968", "969",
      "970", "971", "972", "973", "974", "975", "976", "977", "978", "979",
      "980", "981", "982", "983", "984", "985", "986", "987", "988", "989",
      "990", "991", "992", "993", "994", "995", "996", "997", "998", "999",
    };
    static const char* aliases[300] = {
       "AF",  "AL",  "AQ",  "DZ",  "AS",  "AD",  "AO",  "AG",  "AZ",  "AR",
       "AU",  "AT",  "BS",  "BH",  "BD",  "AM",  "BB",  "BE",  "BM", "034",
       "BT",  "BO",  "BA",  "BW",  "BV",  "BR",  "BZ",  "IO",  "SB",  "VG",
       "BN",  "BG",  "MM",  "BI",  "BY",  "KH",  "CM",  "CA",  "CV",  "KY",
       "CF",  "LK",  "TD",  "CL",  "CN",  "TW",  "CX",  "CC",  "CO",  "KM",
       "YT",  "CG",  "CD",  "CK",  "CR",  "HR",  "CU",  "CY",  "CZ",  "BJ",
       "DK",  "DM",  "DO",  "EC",  "SV",  "GQ",  "ET",  "ET",  "ER",  "EE",
       "FO",  "FK",  "GS",  "FJ",  "FI",  "AX",  "FR",  "FR",  "GF",  "PF",
       "TF",  "DJ",  "GA",  "GE",  "GM",  "PS",  "DE",  "DE",  "DE",  "GH",
       "GI",  "KI",  "GR",  "GL",  "GD",  "GP",  "GU",  "GT",  "GN",  "GY",
       "HT",  "HM",  "VA",  "HN",  "HK",  "HU",  "IS",  "IN",  "ID",  "IR",
       "IQ",  "IE",  "IL",  "IT",  "CI",  "JM",  "JP",  "KZ",  "JO",  "KE",
       "KP",  "KR",  "KW",  "KG",  "LA",  "LB",  "LS",  "LV",  "LR",  "LY",
       "LI",  "LT",  "LU",  "MO",  "MG",  "MW",  "MY",  "MV",  "ML",  "MT",
       "MQ",  "MR",  "MU",  "MX",  "MC",  "MN",  "MD",  "ME",  "MS",  "MA",
       "MZ",  "OM",  "NA",  "NR",  "NP",  "NL",  "CW",  "AW",  "SX",  "BQ",
       "NC",  "VU",  "NZ",  "NI",  "NE",  "NG",  "NU",  "NF",  "NO",  "MP",
       "UM",  "FM",  "MH",  "PW",  "PK",  "PA",  "PG",  "PY",  "PE",  "PH",
       "PN",  "PL",  "PT",  "GW",  "TL",  "PR",  "QA",  "RE",  "RO",  "RU",
       "RW",  "BL",  "SH",  "KN",  "AI",  "LC",  "MF",  "PM",  "VC",  "SM",
       "ST",  "SA",  "SN",  "RS",  "SC",  "SL",  "SG",  "SK",  "VN",  "SI",
       "SO",  "ZA",  "ZW",  "YE",  "ES",  "SS",  "SD",  "EH",  "SD",  "SR",
       "SJ",  "SZ",  "SE",  "CH",  "SY",  "TJ",  "TH",  "TG",  "TK",  "TO",
       "TT",  "AE",  "TN",  "TR",  "TM",  "TC",  "TV",  "UG",  "UA",  "MK",
       "EG",  "GB",  "JE",  "GG",  "JE",  "IM",  "TZ",  "US",  "VI",  "BF",
       "UY",  "UZ",  "VE",  "WF",  "WS",  "YE",  "YE",  "RS",  "ZM",  "AA",
       "QM",  "QN",  "QP",  "QQ",  "QR",  "QS",  "QT",  "EU",  "QV",  "QW",
       "QX",  "QY",  "QZ",  "XA",  "XB",  "XC",  "XD",  "XE",  "XF",  "XG",
       "XH",  "XI",  "XJ",  "XK",  "XL",  "XM",  "XN",  "XO",  "XP",  "XQ",
       "XR",  "XS",  "XT",  "XU",  "XV",  "XW",  "XX",  "XY",  "XZ",  "ZZ",
    };

    if (const char* replacement = SearchReplacement(regions, aliases, region)) {
      region.set(mozilla::MakeStringSpan(replacement));
      return true;
    }
    return false;
  }
}

// Region subtags with complex mappings.
// Derived from CLDR Supplemental Data, version 37.
// https://unicode.org/Public/cldr/37/core.zip
bool js::intl::LanguageTag::complexRegionMapping(const RegionSubtag& region) {
  MOZ_ASSERT(IsStructurallyValidRegionTag(region.span()));
  MOZ_ASSERT(IsCanonicallyCasedRegionTag(region.span()));

  if (region.length() == 2) {
    return region.equalTo("AN") ||
           region.equalTo("NT") ||
           region.equalTo("PC") ||
           region.equalTo("SU");
  }

  {
    static const char regions[8][4] = {
      "172", "200", "530", "532", "536", "582", "810", "890",
    };

    return HasReplacement(regions, region);
  }
}

// Language subtags with complex mappings.
// Derived from CLDR Supplemental Data, version 37.
// https://unicode.org/Public/cldr/37/core.zip
void js::intl::LanguageTag::performComplexLanguageMappings() {
  MOZ_ASSERT(IsStructurallyValidLanguageTag(language().span()));
  MOZ_ASSERT(IsCanonicallyCasedLanguageTag(language().span()));

  if (language().equalTo("cnr")) {
    setLanguage("sr");
    if (region().missing()) {
      setRegion("ME");
    }
  }
  else if (language().equalTo("drw") ||
           language().equalTo("prs") ||
           language().equalTo("tnf")) {
    setLanguage("fa");
    if (region().missing()) {
      setRegion("AF");
    }
  }
  else if (language().equalTo("hbs") ||
           language().equalTo("sh")) {
    setLanguage("sr");
    if (script().missing()) {
      setScript("Latn");
    }
  }
  else if (language().equalTo("swc")) {
    setLanguage("sw");
    if (region().missing()) {
      setRegion("CD");
    }
  }
}

// Region subtags with complex mappings.
// Derived from CLDR Supplemental Data, version 37.
// https://unicode.org/Public/cldr/37/core.zip
void js::intl::LanguageTag::performComplexRegionMappings() {
  MOZ_ASSERT(IsStructurallyValidLanguageTag(language().span()));
  MOZ_ASSERT(IsCanonicallyCasedLanguageTag(language().span()));
  MOZ_ASSERT(IsStructurallyValidRegionTag(region().span()));
  MOZ_ASSERT(IsCanonicallyCasedRegionTag(region().span()));

  if (region().equalTo("172")) {
    if (language().equalTo("hy") ||
        (language().equalTo("und") && script().equalTo("Armn"))) {
      setRegion("AM");
    }
    else if (language().equalTo("az") ||
             language().equalTo("tkr") ||
             language().equalTo("tly") ||
             language().equalTo("ttt")) {
      setRegion("AZ");
    }
    else if (language().equalTo("be")) {
      setRegion("BY");
    }
    else if (language().equalTo("ab") ||
             language().equalTo("ka") ||
             (language().equalTo("ku") && script().equalTo("Yezi")) ||
             language().equalTo("os") ||
             (language().equalTo("und") && script().equalTo("Geor")) ||
             (language().equalTo("und") && script().equalTo("Yezi")) ||
             language().equalTo("xmf")) {
      setRegion("GE");
    }
    else if (language().equalTo("ky")) {
      setRegion("KG");
    }
    else if (language().equalTo("kk") ||
             (language().equalTo("ug") && script().equalTo("Cyrl"))) {
      setRegion("KZ");
    }
    else if (language().equalTo("gag")) {
      setRegion("MD");
    }
    else if (language().equalTo("tg")) {
      setRegion("TJ");
    }
    else if (language().equalTo("tk")) {
      setRegion("TM");
    }
    else if (language().equalTo("crh") ||
             language().equalTo("got") ||
             language().equalTo("ji") ||
             language().equalTo("rue") ||
             language().equalTo("uk") ||
             (language().equalTo("und") && script().equalTo("Goth"))) {
      setRegion("UA");
    }
    else if (language().equalTo("kaa") ||
             language().equalTo("sog") ||
             (language().equalTo("und") && script().equalTo("Sogd")) ||
             (language().equalTo("und") && script().equalTo("Chrs")) ||
             (language().equalTo("und") && script().equalTo("Sogo")) ||
             language().equalTo("uz") ||
             language().equalTo("xco")) {
      setRegion("UZ");
    }
    else {
      setRegion("RU");
    }
  }
  else if (region().equalTo("200")) {
    if (language().equalTo("sk")) {
      setRegion("SK");
    }
    else {
      setRegion("CZ");
    }
  }
  else if (region().equalTo("530") ||
           region().equalTo("532") ||
           region().equalTo("AN")) {
    if (language().equalTo("vic")) {
      setRegion("SX");
    }
    else {
      setRegion("CW");
    }
  }
  else if (region().equalTo("536") ||
           region().equalTo("NT")) {
    if (language().equalTo("akk") ||
        language().equalTo("ckb") ||
        (language().equalTo("ku") && script().equalTo("Arab")) ||
        language().equalTo("mis") ||
        language().equalTo("syr") ||
        (language().equalTo("und") && script().equalTo("Xsux")) ||
        (language().equalTo("und") && script().equalTo("Hatr")) ||
        (language().equalTo("und") && script().equalTo("Syrc"))) {
      setRegion("IQ");
    }
    else {
      setRegion("SA");
    }
  }
  else if (region().equalTo("582") ||
           region().equalTo("PC")) {
    if (language().equalTo("mh")) {
      setRegion("MH");
    }
    else if (language().equalTo("pau")) {
      setRegion("PW");
    }
    else {
      setRegion("FM");
    }
  }
  else if (region().equalTo("810") ||
           region().equalTo("SU")) {
    if (language().equalTo("hy") ||
        (language().equalTo("und") && script().equalTo("Armn"))) {
      setRegion("AM");
    }
    else if (language().equalTo("az") ||
             language().equalTo("tkr") ||
             language().equalTo("tly") ||
             language().equalTo("ttt")) {
      setRegion("AZ");
    }
    else if (language().equalTo("be")) {
      setRegion("BY");
    }
    else if (language().equalTo("et") ||
             language().equalTo("vro")) {
      setRegion("EE");
    }
    else if (language().equalTo("ab") ||
             language().equalTo("ka") ||
             (language().equalTo("ku") && script().equalTo("Yezi")) ||
             language().equalTo("os") ||
             (language().equalTo("und") && script().equalTo("Geor")) ||
             (language().equalTo("und") && script().equalTo("Yezi")) ||
             language().equalTo("xmf")) {
      setRegion("GE");
    }
    else if (language().equalTo("ky")) {
      setRegion("KG");
    }
    else if (language().equalTo("kk") ||
             (language().equalTo("ug") && script().equalTo("Cyrl"))) {
      setRegion("KZ");
    }
    else if (language().equalTo("lt") ||
             language().equalTo("sgs")) {
      setRegion("LT");
    }
    else if (language().equalTo("ltg") ||
             language().equalTo("lv")) {
      setRegion("LV");
    }
    else if (language().equalTo("gag")) {
      setRegion("MD");
    }
    else if (language().equalTo("tg")) {
      setRegion("TJ");
    }
    else if (language().equalTo("tk")) {
      setRegion("TM");
    }
    else if (language().equalTo("crh") ||
             language().equalTo("got") ||
             language().equalTo("ji") ||
             language().equalTo("rue") ||
             language().equalTo("uk") ||
             (language().equalTo("und") && script().equalTo("Goth"))) {
      setRegion("UA");
    }
    else if (language().equalTo("kaa") ||
             language().equalTo("sog") ||
             (language().equalTo("und") && script().equalTo("Sogd")) ||
             (language().equalTo("und") && script().equalTo("Chrs")) ||
             (language().equalTo("und") && script().equalTo("Sogo")) ||
             language().equalTo("uz") ||
             language().equalTo("xco")) {
      setRegion("UZ");
    }
    else {
      setRegion("RU");
    }
  }
  else if (region().equalTo("890")) {
    if (language().equalTo("bs")) {
      setRegion("BA");
    }
    else if (language().equalTo("hr")) {
      setRegion("HR");
    }
    else if (language().equalTo("mk")) {
      setRegion("MK");
    }
    else if (language().equalTo("sl")) {
      setRegion("SI");
    }
    else {
      setRegion("RS");
    }
  }
}

static const char* ToCharPointer(const char* str) {
  return str;
}

static const char* ToCharPointer(const js::UniqueChars& str) {
  return str.get();
}

template <typename T, typename U = T>
static bool IsLessThan(const T& a, const U& b) {
  return strcmp(ToCharPointer(a), ToCharPointer(b)) < 0;
}

// Mappings from variant subtags to preferred values.
// Derived from CLDR Supplemental Data, version 37.
// https://unicode.org/Public/cldr/37/core.zip
bool js::intl::LanguageTag::performVariantMappings(JSContext* cx) {
  // The variant subtags need to be sorted for binary search.
  MOZ_ASSERT(std::is_sorted(variants_.begin(), variants_.end(),
                            IsLessThan<decltype(variants_)::ElementType>));

  auto insertVariantSortedIfNotPresent = [&](const char* variant) {
    auto* p = std::lower_bound(variants_.begin(), variants_.end(), variant,
                               IsLessThan<decltype(variants_)::ElementType,
                                          decltype(variant)>);

    // Don't insert the replacement when already present.
    if (p != variants_.end() && strcmp(p->get(), variant) == 0) {
      return true;
    }

    // Insert the preferred variant in sort order.
    auto preferred = DuplicateString(cx, variant);
    if (!preferred) {
      return false;
    }
    return !!variants_.insert(p, std::move(preferred));
  };

  for (size_t i = 0; i < variants_.length(); ) {
    auto& variant = variants_[i];
    MOZ_ASSERT(IsCanonicallyCasedVariantTag(mozilla::MakeStringSpan(variant.get())));

    if (strcmp(variant.get(), "aaland") == 0) {
      variants_.erase(variants_.begin() + i);
      setRegion("AX");
    }
    else if (strcmp(variant.get(), "arevela") == 0) {
      variants_.erase(variants_.begin() + i);
      setLanguage("hy");
    }
    else if (strcmp(variant.get(), "arevmda") == 0) {
      variants_.erase(variants_.begin() + i);
      setLanguage("hyw");
    }
    else if (strcmp(variant.get(), "heploc") == 0) {
      variants_.erase(variants_.begin() + i);
      if (!insertVariantSortedIfNotPresent("alalc97")) {
        return false;
      }
    }
    else if (strcmp(variant.get(), "polytoni") == 0) {
      variants_.erase(variants_.begin() + i);
      if (!insertVariantSortedIfNotPresent("polyton")) {
        return false;
      }
    }
    else {
      i++;
    }
  }
  return true;
}

// Canonicalize grandfathered locale identifiers.
// Derived from CLDR Supplemental Data, version 37.
// https://unicode.org/Public/cldr/37/core.zip
bool js::intl::LanguageTag::updateGrandfatheredMappings(JSContext* cx) {
  // We're mapping regular grandfathered tags to non-grandfathered form here.
  // Other tags remain unchanged.
  //
  // regular       = "art-lojban"
  //               / "cel-gaulish"
  //               / "no-bok"
  //               / "no-nyn"
  //               / "zh-guoyu"
  //               / "zh-hakka"
  //               / "zh-min"
  //               / "zh-min-nan"
  //               / "zh-xiang"
  //
  // Therefore we can quickly exclude most tags by checking every
  // |unicode_locale_id| subcomponent for characteristics not shared by any of
  // the regular grandfathered (RG) tags:
  //
  //   * Real-world |unicode_language_subtag|s are all two or three letters,
  //     so don't waste time running a useless |language.length > 3| fast-path.
  //   * No RG tag has a "script"-looking component.
  //   * No RG tag has a "region"-looking component.
  //   * The RG tags that match |unicode_locale_id| (art-lojban, cel-gaulish,
  //     zh-guoyu, zh-hakka, zh-xiang) have exactly one "variant". (no-bok,
  //     no-nyn, zh-min, and zh-min-nan require BCP47's extlang subtag
  //     that |unicode_locale_id| doesn't support.)
  //   * No RG tag contains |extensions| or |pu_extensions|.
  if (script().present() ||
      region().present() ||
      variants().length() != 1 ||
      extensions().length() != 0 ||
      privateuse()) {
    return true;
  }

  MOZ_ASSERT(IsCanonicallyCasedLanguageTag(language().span()));
  MOZ_ASSERT(IsCanonicallyCasedVariantTag(mozilla::MakeStringSpan(variants()[0].get())));

  auto variantEqualTo = [this](const char* variant) {
    return strcmp(variants()[0].get(), variant) == 0;
  };

  // art-lojban -> jbo
  if (language().equalTo("art") && variantEqualTo("lojban")) {
    setLanguage("jbo");
    clearVariants();
    return true;
  }

  // cel-gaulish -> xtg-x-cel-gaulish
  else if (language().equalTo("cel") && variantEqualTo("gaulish")) {
    setLanguage("xtg");
    clearVariants();

    auto privateuse = DuplicateString(cx, "x-cel-gaulish");
    if (!privateuse) {
      return false;
    }
    setPrivateuse(std::move(privateuse));
    return true;
  }

  // zh-guoyu -> zh
  else if (language().equalTo("zh") && variantEqualTo("guoyu")) {
    setLanguage("zh");
    clearVariants();
    return true;
  }

  // zh-hakka -> hak
  else if (language().equalTo("zh") && variantEqualTo("hakka")) {
    setLanguage("hak");
    clearVariants();
    return true;
  }

  // zh-xiang -> hsn
  else if (language().equalTo("zh") && variantEqualTo("xiang")) {
    setLanguage("hsn");
    clearVariants();
    return true;
  }

  return true;
}

template <size_t Length>
static inline bool IsUnicodeKey(
  mozilla::Span<const char> key, const char (&str)[Length]) {
  static_assert(Length == UnicodeKeyLength + 1,
                "Unicode extension key is two characters long");
  return memcmp(key.data(), str, Length - 1) == 0;
}

template <size_t Length>
static inline bool IsUnicodeType(
  mozilla::Span<const char> type, const char (&str)[Length]) {
  static_assert(Length > UnicodeKeyLength + 1,
                "Unicode extension type contains more than two characters");
  return type.size() == (Length - 1) &&
         memcmp(type.data(), str, Length - 1) == 0;
}

static int32_t CompareUnicodeType(const char* a, mozilla::Span<const char> b) {
  MOZ_ASSERT(!std::char_traits<char>::find(b.data(), b.size(), '\0'),
             "unexpected null-character in string");

  using UnsignedChar = unsigned char;
  for (size_t i = 0; i < b.size(); i++) {
    // |a| is zero-terminated and |b| doesn't contain a null-terminator. So if
    // we've reached the end of |a|, the below if-statement will always be true.
    // That ensures we don't read past the end of |a|.
    if (int32_t r = UnsignedChar(a[i]) - UnsignedChar(b[i])) {
      return r;
    }
  }

  // Return zero if both strings are equal or a negative number if |b| is a
  // prefix of |a|.
  return -int32_t(UnsignedChar(a[b.size()]));
}

template <size_t Length>
static inline const char* SearchUnicodeReplacement(
  const char* (&types)[Length], const char* (&aliases)[Length],
  mozilla::Span<const char> type) {

  auto p = std::lower_bound(std::begin(types), std::end(types), type,
                            [](const auto& a, const auto& b) {
    return CompareUnicodeType(a, b) < 0;
  });
  if (p != std::end(types) && CompareUnicodeType(*p, type) == 0) {
    return aliases[std::distance(std::begin(types), p)];
  }
  return nullptr;
}

/**
 * Mapping from deprecated BCP 47 Unicode extension types to their preferred
 * values.
 *
 * Spec: https://www.unicode.org/reports/tr35/#Unicode_Locale_Extension_Data_Files
 * Spec: https://www.unicode.org/reports/tr35/#t_Extension
 */
const char* js::intl::LanguageTag::replaceUnicodeExtensionType(
    mozilla::Span<const char> key, mozilla::Span<const char> type) {
  MOZ_ASSERT(key.size() == UnicodeKeyLength);
  MOZ_ASSERT(IsCanonicallyCasedUnicodeKey(key));

  MOZ_ASSERT(type.size() > UnicodeKeyLength);
  MOZ_ASSERT(IsCanonicallyCasedUnicodeType(type));

  if (IsUnicodeKey(key, "ca")) {
    if (IsUnicodeType(type, "ethiopic-amete-alem")) {
      return "ethioaa";
    }
    if (IsUnicodeType(type, "islamicc")) {
      return "islamic-civil";
    }
  }
  else if (IsUnicodeKey(key, "kb") ||
           IsUnicodeKey(key, "kc") ||
           IsUnicodeKey(key, "kh") ||
           IsUnicodeKey(key, "kk") ||
           IsUnicodeKey(key, "kn")) {
    if (IsUnicodeType(type, "yes")) {
      return "true";
    }
  }
  else if (IsUnicodeKey(key, "ks")) {
    if (IsUnicodeType(type, "primary")) {
      return "level1";
    }
    if (IsUnicodeType(type, "tertiary")) {
      return "level3";
    }
  }
  else if (IsUnicodeKey(key, "ms")) {
    if (IsUnicodeType(type, "imperial")) {
      return "uksystem";
    }
  }
  else if (IsUnicodeKey(key, "rg") ||
           IsUnicodeKey(key, "sd")) {
    static const char* types[117] = {
       "cn11",  "cn12",  "cn13",  "cn14",  "cn15",  "cn21",  "cn22",  "cn23",
       "cn31",  "cn32",  "cn33",  "cn34",  "cn35",  "cn36",  "cn37",  "cn41",
       "cn42",  "cn43",  "cn44",  "cn45",  "cn46",  "cn50",  "cn51",  "cn52",
       "cn53",  "cn54",  "cn61",  "cn62",  "cn63",  "cn64",  "cn65", "cz10a",
      "cz10b", "cz10c", "cz10d", "cz10e", "cz10f", "cz611", "cz612", "cz613",
      "cz614", "cz615", "cz621", "cz622", "cz623", "cz624", "cz626", "cz627",
       "czjc",  "czjm",  "czka",  "czkr",  "czli",  "czmo",  "czol",  "czpa",
       "czpl",  "czpr",  "czst",  "czus",  "czvy",  "czzl",   "fra",   "frb",
        "frc",   "frd",   "fre",   "frf",   "frg",   "frh",   "fri",   "frj",
        "frk",   "frl",   "frm",   "frn",   "fro",   "frp",   "frq",   "frr",
        "frs",   "frt",   "fru",   "frv",  "laxn",   "lud",   "lug",   "lul",
      "mrnkc",  "no23",   "nzn",   "nzs",  "omba",  "omsh",  "plds",  "plkp",
       "pllb",  "plld",  "pllu",  "plma",  "plmz",  "plop",  "plpd",  "plpk",
       "plpm",  "plsk",  "plsl",  "plwn",  "plwp",  "plzp", "tteto", "ttrcm",
      "ttwto", "twkhq", "twtnq", "twtpq", "twtxq",
    };
    static const char* aliases[117] = {
       "cnbj",  "cntj",  "cnhe",  "cnsx",  "cnmn",  "cnln",  "cnjl",  "cnhl",
       "cnsh",  "cnjs",  "cnzj",  "cnah",  "cnfj",  "cnjx",  "cnsd",  "cnha",
       "cnhb",  "cnhn",  "cngd",  "cngx",  "cnhi",  "cncq",  "cnsc",  "cngz",
       "cnyn",  "cnxz",  "cnsn",  "cngs",  "cnqh",  "cnnx",  "cnxj", "cz110",
      "cz111", "cz112", "cz113", "cz114", "cz115", "cz663", "cz632", "cz633",
      "cz634", "cz635", "cz641", "cz642", "cz643", "cz644", "cz646", "cz647",
       "cz31",  "cz64",  "cz41",  "cz52",  "cz51",  "cz80",  "cz71",  "cz53",
       "cz32",  "cz10",  "cz20",  "cz42",  "cz63",  "cz72", "frges", "frnaq",
      "frara", "frbfc", "frbre", "frcvl", "frges", "frcor", "frbfc", "fridf",
      "frocc", "frnaq", "frges", "frocc", "frhdf", "frnor", "frnor", "frpdl",
      "frhdf", "frnaq", "frpac", "frara",  "laxs",  "lucl",  "luec",  "luca",
       "mr13",  "no50", "nzauk", "nzcan",  "ombj",  "omsj",  "pl02",  "pl04",
       "pl08",  "pl10",  "pl06",  "pl12",  "pl14",  "pl16",  "pl20",  "pl18",
       "pl22",  "pl26",  "pl24",  "pl28",  "pl30",  "pl32", "tttob", "ttmrc",
      "tttob", "twkhh", "twtnn", "twnwt", "twtxg",
    };
    return SearchUnicodeReplacement(types, aliases, type);
  }
  else if (IsUnicodeKey(key, "tz")) {
    static const char* types[28] = {
         "aqams",    "cnckg",    "cnhrb",    "cnkhg",     "cuba",    "egypt",
          "eire",      "est",     "gmt0", "hongkong",      "hst",  "iceland",
          "iran",   "israel",  "jamaica",    "japan",    "libya",      "mst",
        "navajo",   "poland", "portugal",      "prc",      "roc",      "rok",
        "turkey",      "uct", "usnavajo",     "zulu",
    };
    static const char* aliases[28] = {
         "nzakl",    "cnsha",    "cnsha",    "cnurc",    "cuhav",    "egcai",
         "iedub",   "utcw05",      "gmt",    "hkhkg",   "utcw10",    "isrey",
         "irthr",  "jeruslm",    "jmkin",    "jptyo",    "lytip",   "utcw07",
         "usden",    "plwaw",    "ptlis",    "cnsha",    "twtpe",    "krsel",
         "trist",      "utc",    "usden",      "utc",
    };
    return SearchUnicodeReplacement(types, aliases, type);
  }
  return nullptr;
}

template <size_t Length>
static inline bool IsTransformKey(
  mozilla::Span<const char> key, const char (&str)[Length]) {
  static_assert(Length == TransformKeyLength + 1,
                "Transform extension key is two characters long");
  return memcmp(key.data(), str, Length - 1) == 0;
}

template <size_t Length>
static inline bool IsTransformType(
  mozilla::Span<const char> type, const char (&str)[Length]) {
  static_assert(Length > TransformKeyLength + 1,
                "Transform extension type contains more than two characters");
  return type.size() == (Length - 1) &&
         memcmp(type.data(), str, Length - 1) == 0;
}

/**
 * Mapping from deprecated BCP 47 Transform extension types to their preferred
 * values.
 *
 * Spec: https://www.unicode.org/reports/tr35/#Unicode_Locale_Extension_Data_Files
 * Spec: https://www.unicode.org/reports/tr35/#t_Extension
 */
const char* js::intl::LanguageTag::replaceTransformExtensionType(
    mozilla::Span<const char> key, mozilla::Span<const char> type) {
  MOZ_ASSERT(key.size() == TransformKeyLength);
  MOZ_ASSERT(IsCanonicallyCasedTransformKey(key));

  MOZ_ASSERT(type.size() > TransformKeyLength);
  MOZ_ASSERT(IsCanonicallyCasedTransformType(type));

  if (IsTransformKey(key, "d0")) {
    if (IsTransformType(type, "name")) {
      return "charname";
    }
  }
  else if (IsTransformKey(key, "m0")) {
    if (IsTransformType(type, "names")) {
      return "prprname";
    }
  }
  return nullptr;
}
