Issue #1819 - Implement Intl.Locale proposal

This is according to spec for the mozilla71 cycle, a follow-up will further adjust to spec.

- Add Intl.Locale as native C++
- Port Unicode BCP 47 locale identifier parser to C++
- Port language tag parser to C++
- adjust make_intl_data to generate the data

Based-on: m-c 1433303, 1570370
This commit is contained in:
Martok 2023-06-29 23:07:51 +02:00 committed by roytam1
commit c10a29d96f
18 changed files with 5545 additions and 2600 deletions

View file

@ -913,7 +913,7 @@ struct JSClass {
// application.
#define JSCLASS_GLOBAL_APPLICATION_SLOTS 5
#define JSCLASS_GLOBAL_SLOT_COUNT \
(JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 49)
(JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 50)
#define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \
(JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n)))
#define JSCLASS_GLOBAL_FLAGS \

View file

@ -752,7 +752,7 @@ function String_toLocaleLowerCase() {
requestedLocale = undefined;
} else if (typeof locales === "string") {
// Steps 3, 5.
requestedLocale = ValidateAndCanonicalizeLanguageTag(locales);
requestedLocale = intl_ValidateAndCanonicalizeLanguageTag(locales, false);
} else {
// Step 3.
var requestedLocales = CanonicalizeLocaleList(locales);
@ -793,7 +793,7 @@ function String_toLocaleUpperCase() {
requestedLocale = undefined;
} else if (typeof locales === "string") {
// Steps 3, 5.
requestedLocale = ValidateAndCanonicalizeLanguageTag(locales);
requestedLocale = intl_ValidateAndCanonicalizeLanguageTag(locales, false);
} else {
// Step 3.
var requestedLocales = CanonicalizeLocaleList(locales);

View file

@ -89,9 +89,9 @@ static_assert(mozilla::IsSame<UChar, char16_t>::value,
// buffer's entire inline capacity before growing it and heap-allocating.
static const size_t INITIAL_CHAR_BUFFER_SIZE = 32;
template <typename ICUStringFunction, size_t InlineCapacity>
template <typename ICUStringFunction, typename CharT, size_t InlineCapacity>
static int32_t
CallICU(JSContext* cx, Vector<char16_t, InlineCapacity>& chars, const ICUStringFunction& strFn)
CallICU(JSContext* cx, Vector<CharT, InlineCapacity>& chars, const ICUStringFunction& strFn)
{
MOZ_ASSERT(chars.length() == 0);
MOZ_ALWAYS_TRUE(chars.resize(InlineCapacity));

File diff suppressed because it is too large Load diff

View file

@ -19,6 +19,7 @@
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/DateTimeFormat.h"
#include "builtin/intl/ICUHeader.h"
#include "builtin/intl/Locale.h"
#include "builtin/intl/NumberFormat.h"
#include "builtin/intl/PluralRules.h"
#include "builtin/intl/RelativeTimeFormat.h"
@ -459,6 +460,10 @@ GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global)
dateTimeFormatProto = CreateDateTimeFormatPrototype(cx, intl, global, &dateTimeFormat, DateTimeFormatOptions::Standard);
if (!dateTimeFormatProto)
return false;
RootedObject localeProto(cx);
localeProto = CreateLocalePrototype(cx, intl, global);
if (!localeProto)
return false;
RootedObject numberFormatProto(cx), numberFormat(cx);
numberFormatProto = CreateNumberFormatPrototype(cx, intl, global, &numberFormat);
if (!numberFormatProto)
@ -492,6 +497,7 @@ GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global)
global->setReservedSlot(COLLATOR_PROTO, ObjectValue(*collatorProto));
global->setReservedSlot(DATE_TIME_FORMAT, ObjectValue(*dateTimeFormat));
global->setReservedSlot(DATE_TIME_FORMAT_PROTO, ObjectValue(*dateTimeFormatProto));
global->setReservedSlot(LOCALE_PROTO, ObjectValue(*localeProto));
global->setReservedSlot(NUMBER_FORMAT, ObjectValue(*numberFormat));
global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*numberFormatProto));
global->setReservedSlot(PLURAL_RULES_PROTO, ObjectValue(*pluralRulesProto));

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,722 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* Structured representation of Unicode locale IDs used with Intl functions. */
#ifndef builtin_intl_LanguageTag_h
#define builtin_intl_LanguageTag_h
#include "mozilla/Assertions.h"
#include "mozilla/Range.h"
#include "mozilla/TextUtils.h"
#include "mozilla/TypedEnumBits.h"
#include "mozilla/Variant.h"
#include <algorithm>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <utility>
#include "jsalloc.h"
#include "js/Result.h"
#include "js/GCAPI.h"
#include "js/Utility.h"
#include "js/Vector.h"
struct JSContext;
class JSLinearString;
class JSString;
namespace js {
class StringBuffer;
namespace intl {
#ifdef DEBUG
/**
* Return true if |language| is a valid, case-normalized language subtag.
*/
template <typename CharT>
bool IsStructurallyValidLanguageTag(
const mozilla::Range<const CharT>& language);
/**
* Return true if |script| is a valid, case-normalized script subtag.
*/
template <typename CharT>
bool IsStructurallyValidScriptTag(const mozilla::Range<const CharT>& script);
/**
* Return true if |region| is a valid, case-normalized region subtag.
*/
template <typename CharT>
bool IsStructurallyValidRegionTag(const mozilla::Range<const CharT>& region);
/**
* Return true if |variant| is a valid, case-normalized variant subtag.
*/
bool IsStructurallyValidVariantTag(const mozilla::Range<const char>& variant);
/**
* Return true if |extension| is a valid, case-normalized Unicode extension
* subtag.
*/
bool IsStructurallyValidUnicodeExtensionTag(
const mozilla::Range<const char>& extension);
/**
* Return true if |privateUse| is a valid, case-normalized private-use subtag.
*/
bool IsStructurallyValidPrivateUseTag(
const mozilla::Range<const char>& privateUse);
#endif
template <typename CharT>
char AsciiToLowerCase(CharT c) {
MOZ_ASSERT(mozilla::IsAscii(c));
return mozilla::IsAsciiUppercaseAlpha(c) ? (c | 0x20) : c;
}
template <typename CharT>
char AsciiToUpperCase(CharT c) {
MOZ_ASSERT(mozilla::IsAscii(c));
return mozilla::IsAsciiLowercaseAlpha(c) ? (c & ~0x20) : c;
}
template <typename CharT>
void AsciiToLowerCase(CharT* chars, size_t length, char* dest) {
// Tell the analysis the |std::transform| function can't GC.
JS::AutoSuppressGCAnalysis nogc;
char (&fn)(CharT) = AsciiToLowerCase;
std::transform(chars, chars + length, dest, fn);
}
template <typename CharT>
void AsciiToUpperCase(CharT* chars, size_t length, char* dest) {
// Tell the analysis the |std::transform| function can't GC.
JS::AutoSuppressGCAnalysis nogc;
char (&fn)(CharT) = AsciiToUpperCase;
std::transform(chars, chars + length, dest, fn);
}
template <typename CharT>
void AsciiToTitleCase(CharT* chars, size_t length, char* dest) {
if (length > 0) {
AsciiToUpperCase(chars, 1, dest);
AsciiToLowerCase(chars + 1, length - 1, dest + 1);
}
}
// Constants for language subtag lengths.
namespace LanguageTagLimits {
// unicode_language_subtag = alpha{2,3} | alpha{5,8} ;
static constexpr size_t LanguageLength = 8;
// unicode_script_subtag = alpha{4} ;
static constexpr size_t ScriptLength = 4;
// unicode_region_subtag = (alpha{2} | digit{3}) ;
static constexpr size_t RegionLength = 3;
static constexpr size_t AlphaRegionLength = 2;
static constexpr size_t DigitRegionLength = 3;
// key = alphanum alpha ;
static constexpr size_t UnicodeKeyLength = 2;
// tkey = alpha digit ;
static constexpr size_t TransformKeyLength = 2;
} // namespace LanguageTagLimits
// Fixed size language subtag which is stored inline in LanguageTag.
template <size_t Length>
class LanguageTagSubtag final {
uint8_t length_ = 0;
char chars_[Length];
public:
LanguageTagSubtag() = default;
LanguageTagSubtag(const LanguageTagSubtag&) = delete;
LanguageTagSubtag& operator=(const LanguageTagSubtag&) = delete;
size_t length() const { return length_; }
mozilla::Range<const char> range() const { return {chars_, length_}; }
template <typename CharT>
void set(const mozilla::Range<const CharT>& str) {
MOZ_ASSERT(str.length() <= Length);
std::copy_n(str.begin().get(), str.length(), chars_);
length_ = str.length();
}
void toLowerCase() { AsciiToLowerCase(chars_, length(), chars_); }
void toUpperCase() { AsciiToUpperCase(chars_, length(), chars_); }
void toTitleCase() { AsciiToTitleCase(chars_, length(), chars_); }
template <size_t N>
bool equalTo(const char (&str)[N]) const {
static_assert(N - 1 <= Length,
"subtag literals must not exceed the maximum subtag length");
return length_ == N - 1 && memcmp(chars_, str, N - 1) == 0;
}
};
using LanguageSubtag = LanguageTagSubtag<LanguageTagLimits::LanguageLength>;
using ScriptSubtag = LanguageTagSubtag<LanguageTagLimits::ScriptLength>;
using RegionSubtag = LanguageTagSubtag<LanguageTagLimits::RegionLength>;
/**
* Object representing a language tag.
*
* All subtags are already in canonicalized case.
*/
class MOZ_STACK_CLASS LanguageTag final {
LanguageSubtag language_ = {};
ScriptSubtag script_ = {};
RegionSubtag region_ = {};
using VariantsVector = Vector<JS::UniqueChars, 2>;
using ExtensionsVector = Vector<JS::UniqueChars, 2>;
VariantsVector variants_;
ExtensionsVector extensions_;
JS::UniqueChars privateuse_ = nullptr;
friend class LanguageTagParser;
public:
// Flag to request canonicalized Unicode extensions.
enum class UnicodeExtensionCanonicalForm : bool { No, Yes };
private:
bool canonicalizeUnicodeExtension(
JSContext* cx, JS::UniqueChars& unicodeExtension,
UnicodeExtensionCanonicalForm canonicalForm);
bool canonicalizeTransformExtension(JSContext* cx,
JS::UniqueChars& transformExtension);
public:
static bool languageMapping(LanguageSubtag& language);
static bool complexLanguageMapping(const LanguageSubtag& language);
private:
static bool regionMapping(RegionSubtag& region);
static bool complexRegionMapping(const RegionSubtag& region);
void performComplexLanguageMappings();
void performComplexRegionMappings();
MOZ_MUST_USE bool updateGrandfatheredMappings(JSContext* cx);
static const char* replaceUnicodeExtensionType(
const mozilla::Range<const char>& key,
const mozilla::Range<const char>& type);
public:
explicit LanguageTag(JSContext* cx) : variants_(cx), extensions_(cx) {}
LanguageTag(const LanguageTag&) = delete;
LanguageTag& operator=(const LanguageTag&) = delete;
const LanguageSubtag& language() const { return language_; }
const ScriptSubtag& script() const { return script_; }
const RegionSubtag& region() const { return region_; }
const auto& variants() const { return variants_; }
const auto& extensions() const { return extensions_; }
const char* privateuse() const { return privateuse_.get(); }
/**
* Set the language subtag. The input must be a valid, case-normalized
* language subtag.
*/
template <size_t N>
void setLanguage(const char (&language)[N]) {
mozilla::Range<const char> range(language, N - 1);
MOZ_ASSERT(IsStructurallyValidLanguageTag(range));
language_.set(range);
}
/**
* Set the language subtag. The input must be a valid, case-normalized
* language subtag.
*/
void setLanguage(const LanguageSubtag& language) {
MOZ_ASSERT(IsStructurallyValidLanguageTag(language.range()));
language_.set(language.range());
}
/**
* Set the script subtag. The input must be a valid, case-normalized
* script subtag or the empty string.
*/
template <size_t N>
void setScript(const char (&script)[N]) {
mozilla::Range<const char> range(script, N - 1);
MOZ_ASSERT(IsStructurallyValidScriptTag(range));
script_.set(range);
}
/**
* Set the script subtag. The input must be a valid, case-normalized
* script subtag or the empty string.
*/
void setScript(const ScriptSubtag& script) {
MOZ_ASSERT(script.length() == 0 ||
IsStructurallyValidScriptTag(script.range()));
script_.set(script.range());
}
/**
* Set the region subtag. The input must be a valid, case-normalized
* region subtag or the empty string.
*/
template <size_t N>
void setRegion(const char (&region)[N]) {
mozilla::Range<const char> range(region, N - 1);
MOZ_ASSERT(IsStructurallyValidRegionTag(range));
region_.set(range);
}
/**
* Set the region subtag. The input must be a valid, case-normalized
* region subtag or the empty string.
*/
void setRegion(const RegionSubtag& region) {
MOZ_ASSERT(region.length() == 0 ||
IsStructurallyValidRegionTag(region.range()));
region_.set(region.range());
}
/**
* Removes all variant subtags.
*/
void clearVariants() { variants_.clearAndFree(); }
/**
* Set the Unicode extension subtag. The input must be a valid,
* case-normalized Unicode extension subtag.
*/
bool setUnicodeExtension(JS::UniqueChars extension);
/**
* Set the private-use subtag. The input must be a valid, case-normalized
* private-use subtag or the empty string.
*/
void setPrivateuse(JS::UniqueChars privateuse) {
MOZ_ASSERT(!privateuse ||
IsStructurallyValidPrivateUseTag(
{privateuse.get(), strlen(privateuse.get())}));
privateuse_ = std::move(privateuse);
}
/**
* Canonicalize the base-name subtags, that means the language, script,
* region, and variant subtags.
*/
bool canonicalizeBaseName(JSContext* cx);
/**
* Canonicalize all extension subtags.
*/
bool canonicalizeExtensions(JSContext* cx,
UnicodeExtensionCanonicalForm canonicalForm);
/**
* Canonicalizes the given structurally valid Unicode BCP 47 locale
* identifier, including regularized case of subtags. For example, the
* language tag Zh-haNS-bu-variant2-Variant1-u-ca-chinese-t-Zh-laTN-x-PRIVATE,
* where
*
* Zh ; 2*3ALPHA
* -haNS ; ["-" script]
* -bu ; ["-" region]
* -variant2 ; *("-" variant)
* -Variant1
* -u-ca-chinese ; *("-" extension)
* -t-Zh-laTN
* -x-PRIVATE ; ["-" privateuse]
*
* becomes zh-Hans-MM-variant1-variant2-t-zh-latn-u-ca-chinese-x-private
*
* UTS 35 specifies two different canonicalization algorithms. There's one to
* canonicalize BCP 47 language tags and other one to canonicalize Unicode
* locale identifiers. The latter one wasn't present when ECMA-402 was changed
* to use Unicode BCP 47 locale identifiers instead of BCP 47 language tags,
* so ECMA-402 currently only uses the former to canonicalize Unicode BCP 47
* locale identifiers.
*
* Spec: ECMAScript Internationalization API Specification, 6.2.3.
* Spec:
* https://unicode.org/reports/tr35/#Canonical_Unicode_Locale_Identifiers
* Spec: https://unicode.org/reports/tr35/#BCP_47_Language_Tag_Conversion
*/
bool canonicalize(JSContext* cx,
UnicodeExtensionCanonicalForm canonicalForm) {
return canonicalizeBaseName(cx) &&
canonicalizeExtensions(cx, canonicalForm);
}
/**
* Append the string representation of this language tag to the given
* string buffer.
*/
bool appendTo(JSContext* cx, StringBuffer& sb) const;
/**
* Add likely-subtags to the language tag.
*
* Spec: <https://www.unicode.org/reports/tr35/#Likely_Subtags>
*/
bool addLikelySubtags(JSContext* cx);
/**
* Remove likely-subtags from the language tag.
*
* Spec: <https://www.unicode.org/reports/tr35/#Likely_Subtags>
*/
bool removeLikelySubtags(JSContext* cx);
};
/**
* Parser for Unicode BCP 47 locale identifiers.
*
* <https://unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers>
*/
class MOZ_STACK_CLASS LanguageTagParser final {
public:
// Exposed as |public| for |MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS|.
enum class TokenKind : uint8_t {
None = 0b000,
Alpha = 0b001,
Digit = 0b010,
AlphaDigit = 0b011,
Error = 0b100
};
private:
class Token final {
size_t index_;
size_t length_;
TokenKind kind_;
public:
Token(TokenKind kind, size_t index, size_t length)
: index_(index), length_(length), kind_(kind) {}
TokenKind kind() const { return kind_; }
size_t index() const { return index_; }
size_t length() const { return length_; }
bool isError() const { return kind_ == TokenKind::Error; }
bool isNone() const { return kind_ == TokenKind::None; }
bool isAlpha() const { return kind_ == TokenKind::Alpha; }
bool isDigit() const { return kind_ == TokenKind::Digit; }
bool isAlphaDigit() const { return kind_ == TokenKind::AlphaDigit; }
};
using LocaleChars = mozilla::Variant<const JS::Latin1Char*, const char16_t*>;
const LocaleChars& locale_;
size_t length_;
size_t index_ = 0;
LanguageTagParser(const LocaleChars& locale, size_t length)
: locale_(locale), length_(length) {}
char16_t charAtUnchecked(size_t index) const {
if (locale_.is<const JS::Latin1Char*>()) {
return locale_.as<const JS::Latin1Char*>()[index];
}
return locale_.as<const char16_t*>()[index];
}
char charAt(size_t index) const {
char16_t c = charAtUnchecked(index);
MOZ_ASSERT(mozilla::IsAscii(c));
return c;
}
// Copy the token characters into |subtag|.
template <size_t N>
void copyChars(const Token& tok, LanguageTagSubtag<N>& subtag) const {
size_t index = tok.index();
size_t length = tok.length();
if (locale_.is<const JS::Latin1Char*>()) {
using T = const JS::Latin1Char;
subtag.set(mozilla::Range<T>(locale_.as<T*>() + index, length));
} else {
using T = const char16_t;
subtag.set(mozilla::Range<T>(locale_.as<T*>() + index, length));
}
}
// Create a string copy of |length| characters starting at |index|.
JS::UniqueChars chars(JSContext* cx, size_t index, size_t length) const;
// Create a string copy of the token characters.
JS::UniqueChars chars(JSContext* cx, const Token& tok) const {
return chars(cx, tok.index(), tok.length());
}
Token nextToken();
JS::UniqueChars extension(JSContext* cx, const Token& start,
const Token& end) const;
// unicode_language_subtag = alpha{2,3} | alpha{5,8} ;
//
// Four character language subtags are not allowed in Unicode BCP 47 locale
// identifiers. Also see the comparison to Unicode CLDR locale identifiers in
// <https://unicode.org/reports/tr35/#BCP_47_Conformance>.
bool isLanguage(const Token& tok) const {
return tok.isAlpha() && ((2 <= tok.length() && tok.length() <= 3) ||
(5 <= tok.length() && tok.length() <= 8));
}
// unicode_script_subtag = alpha{4} ;
bool isScript(const Token& tok) const {
return tok.isAlpha() && tok.length() == 4;
}
// unicode_region_subtag = (alpha{2} | digit{3}) ;
bool isRegion(const Token& tok) const {
return (tok.isAlpha() && tok.length() == 2) ||
(tok.isDigit() && tok.length() == 3);
}
// unicode_variant_subtag = (alphanum{5,8} | digit alphanum{3}) ;
bool isVariant(const Token& tok) const {
return (5 <= tok.length() && tok.length() <= 8) ||
(tok.length() == 4 && mozilla::IsAsciiDigit(charAt(tok.index())));
}
// Returns the code unit of the first character at the given singleton token.
// Always returns the lower case form of an alphabetical character.
char singletonKey(const Token& tok) const {
MOZ_ASSERT(tok.length() == 1);
char c = charAt(tok.index());
return mozilla::IsAsciiUppercaseAlpha(c) ? (c | 0x20) : c;
}
// extensions = unicode_locale_extensions |
// transformed_extensions |
// other_extensions ;
//
// unicode_locale_extensions = sep [uU] ((sep keyword)+ |
// (sep attribute)+ (sep keyword)*) ;
//
// transformed_extensions = sep [tT] ((sep tlang (sep tfield)*) |
// (sep tfield)+) ;
//
// other_extensions = sep [alphanum-[tTuUxX]] (sep alphanum{2,8})+ ;
bool isExtensionStart(const Token& tok) const {
return tok.length() == 1 && singletonKey(tok) != 'x';
}
// other_extensions = sep [alphanum-[tTuUxX]] (sep alphanum{2,8})+ ;
bool isOtherExtensionPart(const Token& tok) const {
return 2 <= tok.length() && tok.length() <= 8;
}
// unicode_locale_extensions = sep [uU] ((sep keyword)+ |
// (sep attribute)+ (sep keyword)*) ;
// keyword = key (sep type)? ;
bool isUnicodeExtensionPart(const Token& tok) const {
return isUnicodeExtensionKey(tok) || isUnicodeExtensionType(tok) ||
isUnicodeExtensionAttribute(tok);
}
// attribute = alphanum{3,8} ;
bool isUnicodeExtensionAttribute(const Token& tok) const {
return 3 <= tok.length() && tok.length() <= 8;
}
// key = alphanum alpha ;
bool isUnicodeExtensionKey(const Token& tok) const {
return tok.length() == 2 && mozilla::IsAsciiAlpha(charAt(tok.index() + 1));
}
// type = alphanum{3,8} (sep alphanum{3,8})* ;
bool isUnicodeExtensionType(const Token& tok) const {
return 3 <= tok.length() && tok.length() <= 8;
}
// tkey = alpha digit ;
bool isTransformExtensionKey(const Token& tok) const {
return tok.length() == 2 && mozilla::IsAsciiAlpha(charAt(tok.index())) &&
mozilla::IsAsciiDigit(charAt(tok.index() + 1));
}
// tvalue = (sep alphanum{3,8})+ ;
bool isTransformExtensionPart(const Token& tok) const {
return 3 <= tok.length() && tok.length() <= 8;
}
// pu_extensions = sep [xX] (sep alphanum{1,8})+ ;
bool isPrivateUseStart(const Token& tok) const {
return tok.length() == 1 && singletonKey(tok) == 'x';
}
// pu_extensions = sep [xX] (sep alphanum{1,8})+ ;
bool isPrivateUsePart(const Token& tok) const {
return 1 <= tok.length() && tok.length() <= 8;
}
enum class BaseNameParsing : bool { Normal, WithinTransformExtension };
// Helper function for use in |parseBaseName| and
// |parseTlangInTransformExtension|. Do not use this directly!
static JS::Result<bool> internalParseBaseName(JSContext* cx,
LanguageTagParser& ts,
LanguageTag& tag, Token& tok,
BaseNameParsing parseType);
// Parse the `unicode_language_id` production, i.e. the
// language/script/region/variants portion of a language tag, into |tag|,
// which will be filled with canonical-cased components (lowercase language,
// titlecase script, uppercase region, lowercased and alphabetized and
// deduplicated variants). |tok| must be the current token.
static JS::Result<bool> parseBaseName(JSContext* cx, LanguageTagParser& ts,
LanguageTag& tag, Token& tok) {
return internalParseBaseName(cx, ts, tag, tok, BaseNameParsing::Normal);
}
// Parse the `tlang` production within a parsed 't' transform extension.
// The precise requirements for "previously parsed" are:
//
// * the input begins from current token |tok| with a valid `tlang`
// * the `tlang` is wholly lowercase (*not* canonical case)
// * variant subtags in the `tlang` may contain duplicates and be
// unordered
//
// Return an error on internal failure. Otherwise, return a success value. If
// there was no `tlang`, then |tag.language().missing()|. But if there was a
// `tlang`, then |tag| is filled with subtags exactly as they appeared in the
// parse input: fully lowercase, variants in alphabetical order without
// duplicates.
static JS::Result<JS::Ok> parseTlangInTransformExtension(
JSContext* cx, LanguageTagParser& ts, LanguageTag& tag, Token& tok) {
MOZ_ASSERT(ts.isLanguage(tok));
return internalParseBaseName(cx, ts, tag, tok,
BaseNameParsing::WithinTransformExtension)
.map([](bool parsed) {
MOZ_ASSERT(parsed);
return JS::Ok();
});
}
friend class LanguageTag;
class Range final {
size_t begin_;
size_t length_;
public:
Range(size_t begin, size_t length) : begin_(begin), length_(length) {}
template <typename T>
T* begin(T* ptr) const {
return ptr + begin_;
}
size_t length() const { return length_; }
};
using TFieldVector = js::Vector<Range, 8>;
using AttributesVector = js::Vector<Range, 8>;
using KeywordsVector = js::Vector<Range, 8>;
// Parse |extension|, which must be a validated, fully lowercase
// `transformed_extensions` subtag, and fill |tag| and |fields| from the
// `tlang` and `tfield` components. Data in |tag| is lowercase, consistent
// with |extension|.
static JS::Result<bool> parseTransformExtension(
JSContext* cx, mozilla::Range<const char> extension, LanguageTag& tag,
TFieldVector& fields);
// Parse |extension|, which must be a validated, fully lowercase
// `unicode_locale_extensions` subtag, and fill |attributes| and |keywords|
// from the `attribute` and `keyword` components.
static JS::Result<bool> parseUnicodeExtension(
JSContext* cx, mozilla::Range<const char> extension,
AttributesVector& attributes, KeywordsVector& keywords);
public:
// Parse the input string as a language tag. Reports an error to the context
// if the input can't be parsed completely.
static bool parse(JSContext* cx, JSLinearString* locale, LanguageTag& tag);
// Parse the input string as a language tag. Returns Ok(true) if the input
// could be completely parsed, Ok(false) if the input couldn't be parsed,
// or Err() in case of internal error.
static JS::Result<bool> tryParse(JSContext* cx, JSLinearString* locale,
LanguageTag& tag);
// Parse the input string as the base-name parts (language, script, region,
// variants) of a language tag. Ignores any trailing characters.
static bool parseBaseName(JSContext* cx, mozilla::Range<const char> locale,
LanguageTag& tag);
// Return true iff |extension| can be parsed as a Unicode extension subtag.
static bool canParseUnicodeExtension(mozilla::Range<const char> extension);
// Return true iff |unicodeType| can be parsed as a Unicode extension type.
static bool canParseUnicodeExtensionType(JSLinearString* unicodeType);
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(LanguageTagParser::TokenKind)
/**
* Parse a string as a standalone |language| tag. If |str| is a standalone
* language tag, store it in case-normalized form in |result| and return true.
* Otherwise return false.
*/
MOZ_MUST_USE bool ParseStandaloneLanguagTag(JS::Handle<JSLinearString*> str,
LanguageSubtag& result);
/**
* Parse a string as a standalone |script| tag. If |str| is a standalone script
* tag, store it in case-normalized form in |result| and return true. Otherwise
* return false.
*/
MOZ_MUST_USE bool ParseStandaloneScriptTag(JS::Handle<JSLinearString*> str,
ScriptSubtag& result);
/**
* Parse a string as a standalone |region| tag. If |str| is a standalone region
* tag, store it in case-normalized form in |result| and return true. Otherwise
* return false.
*/
MOZ_MUST_USE bool ParseStandaloneRegionTag(JS::Handle<JSLinearString*> str,
RegionSubtag& result);
/**
* Parse a string as an ISO-639 language code. Return |nullptr| in the result if
* the input could not be parsed or the canonical form of the resulting language
* tag contains more than a single language subtag.
*/
JS::Result<JSString*> ParseStandaloneISO639LanguageTag(
JSContext* cx, JS::Handle<JSLinearString*> str);
} // namespace intl
} // namespace js
#endif /* builtin_intl_LanguageTag_h */

View file

@ -0,0 +1,790 @@
// Generated by make_intl_data.py. DO NOT EDIT.
// Version: CLDR-35.1
// URL: https://unicode.org/Public/cldr/35.1/core.zip
#include "mozilla/Assertions.h"
#include "mozilla/Range.h"
#include "mozilla/TextUtils.h"
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <iterator>
#include <type_traits>
#include "jscntxt.h"
#include "jsstr.h"
#include "builtin/intl/LanguageTag.h"
using namespace js::intl::LanguageTagLimits;
using ConstCharRange = mozilla::Range<const char>;
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.range().begin().get();
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.range().begin().get();
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;
}
// Mappings from language subtags to preferred values.
// Derived from CLDR Supplemental Data, version 35.1.
// https://unicode.org/Public/cldr/35.1/core.zip
bool js::intl::LanguageTag::languageMapping(LanguageSubtag& language) {
MOZ_ASSERT(IsStructurallyValidLanguageTag(language.range()));
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(ConstCharRange(replacement, strlen(replacement)));
return true;
}
return false;
}
if (language.length() == 3) {
static const char languages[340][4] = {
"aam", "aar", "abk", "adp", "afr", "aju", "aka", "alb", "als", "amh",
"ara", "arb", "arg", "arm", "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", "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", "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", "myt", "nad", "nau", "nav", "nbl", "ncp",
"nde", "ndo", "nep", "nld", "nno", "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[340] = {
"aas", "aa", "ab", "dz", "af", "jrb", "ak", "sq", "sq", "am",
"ar", "ar", "an", "hy", "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", "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", "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", "mry", "xny", "na", "nv", "nr", "kdz",
"nd", "ng", "ne", "nl", "nn", "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(ConstCharRange(replacement, strlen(replacement)));
return true;
}
return false;
}
return false;
}
// Language subtags with complex mappings.
// Derived from CLDR Supplemental Data, version 35.1.
// https://unicode.org/Public/cldr/35.1/core.zip
bool js::intl::LanguageTag::complexLanguageMapping(const LanguageSubtag& language) {
MOZ_ASSERT(IsStructurallyValidLanguageTag(language.range()));
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 35.1.
// https://unicode.org/Public/cldr/35.1/core.zip
bool js::intl::LanguageTag::regionMapping(RegionSubtag& region) {
MOZ_ASSERT(IsStructurallyValidRegionTag(region.range()));
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(ConstCharRange(replacement, strlen(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(ConstCharRange(replacement, strlen(replacement)));
return true;
}
return false;
}
}
// Region subtags with complex mappings.
// Derived from CLDR Supplemental Data, version 35.1.
// https://unicode.org/Public/cldr/35.1/core.zip
bool js::intl::LanguageTag::complexRegionMapping(const RegionSubtag& region) {
MOZ_ASSERT(IsStructurallyValidRegionTag(region.range()));
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 35.1.
// https://unicode.org/Public/cldr/35.1/core.zip
void js::intl::LanguageTag::performComplexLanguageMappings() {
MOZ_ASSERT(IsStructurallyValidLanguageTag(language().range()));
if (language().equalTo("cnr")) {
setLanguage("sr");
if (region().length() == 0) {
setRegion("ME");
}
}
else if (language().equalTo("drw") ||
language().equalTo("prs") ||
language().equalTo("tnf")) {
setLanguage("fa");
if (region().length() == 0) {
setRegion("AF");
}
}
else if (language().equalTo("hbs") ||
language().equalTo("sh")) {
setLanguage("sr");
if (script().length() == 0) {
setScript("Latn");
}
}
else if (language().equalTo("swc")) {
setLanguage("sw");
if (region().length() == 0) {
setRegion("CD");
}
}
}
// Region subtags with complex mappings.
// Derived from CLDR Supplemental Data, version 35.1.
// https://unicode.org/Public/cldr/35.1/core.zip
void js::intl::LanguageTag::performComplexRegionMappings() {
MOZ_ASSERT(IsStructurallyValidLanguageTag(language().range()));
MOZ_ASSERT(IsStructurallyValidRegionTag(region().range()));
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("os") ||
(language().equalTo("und") && script().equalTo("Geor")) ||
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("Sogo")) ||
language().equalTo("uz")) {
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("Syrc")) ||
(language().equalTo("und") && script().equalTo("Xsux")) ||
(language().equalTo("und") && script().equalTo("Hatr"))) {
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("os") ||
(language().equalTo("und") && script().equalTo("Geor")) ||
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("Sogo")) ||
language().equalTo("uz")) {
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");
}
}
}
// Canonicalize grandfathered locale identifiers.
// Derived from CLDR Supplemental Data, version 35.1.
// https://unicode.org/Public/cldr/35.1/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().length() != 0 ||
region().length() != 0 ||
variants().length() != 1 ||
extensions().length() != 0 ||
privateuse()) {
return true;
}
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(const ConstCharRange& key,
const char (&str)[Length]) {
static_assert(Length == UnicodeKeyLength + 1,
"Unicode extension key is two characters long");
return memcmp(key.begin().get(), str, Length - 1) == 0;
}
template <size_t Length>
static inline bool IsUnicodeType(const ConstCharRange& type,
const char (&str)[Length]) {
static_assert(Length > UnicodeKeyLength + 1,
"Unicode extension type contains more than two characters");
return type.length() == (Length - 1) &&
memcmp(type.begin().get(), str, Length - 1) == 0;
}
static int32_t CompareUnicodeType(const char* a, const ConstCharRange& b) {
#ifdef DEBUG
auto isNull = [](char c) {
return c == '\0';
};
#endif
MOZ_ASSERT(std::none_of(b.begin().get(), b.end().get(), isNull),
"unexpected null-character in string");
using UnsignedChar = unsigned char;
for (size_t i = 0; i < b.length(); 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.length()]));
};
template <size_t Length>
static inline const char* SearchReplacement(const char* (&types)[Length],
const char* (&aliases)[Length],
const ConstCharRange& 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
*/
const char* js::intl::LanguageTag::replaceUnicodeExtensionType(
const ConstCharRange& key, const ConstCharRange& type) {
#ifdef DEBUG
static auto isAsciiLowercaseAlphanumeric = [](char c) {
return mozilla::IsAsciiLowercaseAlpha(c) || mozilla::IsAsciiDigit(c);
};
static auto isAsciiLowercaseAlphanumericOrDash = [](char c) {
return isAsciiLowercaseAlphanumeric(c) || c == '-';
};
#endif
MOZ_ASSERT(key.length() == UnicodeKeyLength);
MOZ_ASSERT(std::all_of(key.begin().get(), key.end().get(),
isAsciiLowercaseAlphanumeric));
MOZ_ASSERT(type.length() > UnicodeKeyLength);
MOZ_ASSERT(std::all_of(type.begin().get(), type.end().get(),
isAsciiLowercaseAlphanumericOrDash));
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[116] = {
"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", "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[116] = {
"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", "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 SearchReplacement(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 SearchReplacement(types, aliases, type);
}
return nullptr;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,61 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef builtin_intl_Locale_h
#define builtin_intl_Locale_h
#include <stdint.h>
#include "builtin/SelfHostingDefines.h"
#include "js/Class.h"
#include "vm/NativeObject.h"
namespace js {
class GlobalObject;
class LocaleObject : public NativeObject {
public:
static const Class class_;
static constexpr uint32_t LANGUAGE_TAG_SLOT = 0;
static constexpr uint32_t BASENAME_SLOT = 1;
static constexpr uint32_t UNICODE_EXTENSION_SLOT = 2;
static constexpr uint32_t SLOT_COUNT = 3;
/**
* Returns the complete language tag, including any extensions and privateuse
* subtags.
*/
JSString* languageTag() const {
return getFixedSlot(LANGUAGE_TAG_SLOT).toString();
}
/**
* Returns the basename subtags, i.e. excluding any extensions and privateuse
* subtags.
*/
JSString* baseName() const { return getFixedSlot(BASENAME_SLOT).toString(); }
const Value& unicodeExtension() const {
return getFixedSlot(UNICODE_EXTENSION_SLOT);
}
};
extern JSObject* CreateLocalePrototype(JSContext* cx,
JS::Handle<JSObject*> Intl,
JS::Handle<GlobalObject*> global);
extern MOZ_MUST_USE bool intl_ValidateAndCanonicalizeLanguageTag(JSContext* cx,
unsigned argc,
Value* vp);
extern MOZ_MUST_USE bool intl_TryValidateAndCanonicalizeLanguageTag(
JSContext* cx, unsigned argc, Value* vp);
} // namespace js
#endif /* builtin_intl_Locale_h */

File diff suppressed because it is too large Load diff

View file

@ -118,6 +118,9 @@ main_deunified_sources = [
'builtin/intl/CommonFunctions.cpp',
'builtin/intl/DateTimeFormat.cpp',
'builtin/intl/IntlObject.cpp',
'builtin/intl/LanguageTag.cpp',
'builtin/intl/LanguageTagGenerated.cpp',
'builtin/intl/Locale.cpp',
'builtin/intl/NumberFormat.cpp',
'builtin/intl/PluralRules.cpp',
'builtin/intl/RelativeTimeFormat.cpp',
@ -709,7 +712,6 @@ selfhosted.inputs = [
'builtin/intl/CommonFunctions.js',
'builtin/intl/DateTimeFormat.js',
'builtin/intl/IntlObject.js',
'builtin/intl/LangTagMappingsGenerated.js',
'builtin/intl/NumberFormat.js',
'builtin/intl/PluralRules.js',
'builtin/intl/RelativeTimeFormat.js',

View file

@ -51,6 +51,7 @@
macro(byteOffset, byteOffset, "byteOffset") \
macro(bytes, bytes, "bytes") \
macro(BYTES_PER_ELEMENT, BYTES_PER_ELEMENT, "BYTES_PER_ELEMENT") \
macro(calendar, calendar, "calendar") \
macro(call, call, "call") \
macro(callContentFunction, callContentFunction, "callContentFunction") \
macro(callee, callee, "callee") \
@ -61,6 +62,7 @@
macro(catch, catch_, "catch") \
macro(class, class_, "class") \
macro(close, close, "close") \
macro(collation, collation, "collation") \
macro(Collator, Collator, "Collator") \
macro(CollatorCompareGet, CollatorCompareGet, "Intl_Collator_compare_get") \
macro(collections, collections, "collections") \
@ -177,6 +179,7 @@
macro(hasOwn, hasOwn, "hasOwn") \
macro(hasOwnProperty, hasOwnProperty, "hasOwnProperty") \
macro(hour, hour, "hour") \
macro(hourCycle, hourCycle, "hourCycle") \
macro(if, if_, "if") \
macro(ignoreCase, ignoreCase, "ignoreCase") \
macro(ignorePunctuation, ignorePunctuation, "ignorePunctuation") \
@ -190,6 +193,7 @@
macro(Infinity, Infinity, "Infinity") \
macro(InitializeCollator, InitializeCollator, "InitializeCollator") \
macro(InitializeDateTimeFormat, InitializeDateTimeFormat, "InitializeDateTimeFormat") \
macro(InitializeLocale, InitializeLocale, "InitializeLocale") \
macro(InitializeNumberFormat, InitializeNumberFormat, "InitializeNumberFormat") \
macro(InitializePluralRules, InitializePluralRules, "InitializePluralRules") \
macro(InitializeRelativeTimeFormat, InitializeRelativeTimeFormat, "InitializeRelativeTimeFormat") \
@ -218,6 +222,7 @@
macro(js, js, "js") \
macro(keys, keys, "keys") \
macro(label, label, "label") \
macro(language, language, "language") \
macro(lastIndex, lastIndex, "lastIndex") \
macro(LegacyGeneratorCloseInternal, LegacyGeneratorCloseInternal, "LegacyGeneratorCloseInternal") \
macro(length, length, "length") \
@ -226,6 +231,7 @@
macro(lineNumber, lineNumber, "lineNumber") \
macro(literal, literal, "literal") \
macro(loc, loc, "loc") \
macro(Locale, Locale, "Locale") \
macro(locale, locale, "locale") \
macro(lookupGetter, lookupGetter, "__lookupGetter__") \
macro(lookupSetter, lookupSetter, "__lookupSetter__") \
@ -263,6 +269,7 @@
macro(noStack, noStack, "noStack") \
macro(notes, notes, "notes") \
macro(NumberFormat, NumberFormat, "NumberFormat") \
macro(numberingSystem, numberingSystem, "numberingSystem") \
macro(NumberFormatFormatGet, NumberFormatFormatGet, "Intl_NumberFormat_format_get") \
macro(numeric, numeric, "numeric") \
macro(objectArguments, objectArguments, "[object Arguments]") \
@ -306,6 +313,7 @@
macro(reason, reason, "reason") \
macro(RegExpFlagsGetter, RegExpFlagsGetter, "RegExpFlagsGetter") \
macro(RegExpStringIterator, RegExpStringIterator, "RegExp String Iterator") \
macro(region, region, "region") \
macro(Reify, Reify, "Reify") \
macro(reject, reject, "reject") \
macro(rejected, rejected, "rejected") \

View file

@ -113,6 +113,7 @@ class GlobalObject : public NativeObject
DATE_TIME_FORMAT_PROTO,
PLURAL_RULES_PROTO,
RELATIVE_TIME_FORMAT_PROTO,
LOCALE_PROTO,
MODULE_PROTO,
IMPORT_ENTRY_PROTO,
EXPORT_ENTRY_PROTO,
@ -501,6 +502,11 @@ class GlobalObject : public NativeObject
return getOrCreateObject(cx, global, COLLATOR_PROTO, initIntlObject);
}
static JSObject*
getOrCreateLocalePrototype(JSContext* cx, Handle<GlobalObject*> global) {
return getOrCreateObject(cx, global, LOCALE_PROTO, initIntlObject);
}
static JSFunction*
getOrCreateNumberFormatConstructor(JSContext* cx, Handle<GlobalObject*> global) {
JSObject* obj = getOrCreateObject(cx, global, NUMBER_FORMAT, initIntlObject);

View file

@ -25,6 +25,7 @@
#include "builtin/intl/Collator.h"
#include "builtin/intl/DateTimeFormat.h"
#include "builtin/intl/IntlObject.h"
#include "builtin/intl/Locale.h"
#include "builtin/intl/NumberFormat.h"
#include "builtin/intl/PluralRules.h"
#include "builtin/intl/RelativeTimeFormat.h"
@ -2486,6 +2487,8 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("intl_SelectPluralRule", intl_SelectPluralRule, 2,0),
JS_FN("intl_toLocaleLowerCase", intl_toLocaleLowerCase, 2,0),
JS_FN("intl_toLocaleUpperCase", intl_toLocaleUpperCase, 2,0),
JS_FN("intl_ValidateAndCanonicalizeLanguageTag", intl_ValidateAndCanonicalizeLanguageTag, 2, 0),
JS_FN("intl_TryValidateAndCanonicalizeLanguageTag", intl_TryValidateAndCanonicalizeLanguageTag, 1, 0),
JS_FN("intl_RelativeTimeFormat_availableLocales", intl_RelativeTimeFormat_availableLocales, 0,0),
JS_FN("intl_FormatRelativeTime", intl_FormatRelativeTime, 3,0),

View file

@ -1131,6 +1131,20 @@ class StaticStrings
static bool isStatic(JSAtom* atom);
/* Return null if no static atom exists for the given (chars, length). */
MOZ_ALWAYS_INLINE JSAtom* lookup(const char* chars, size_t length) {
// Collapse calls for |const char*| into |const Latin1Char char*| to avoid
// excess instantiations.
return lookup(reinterpret_cast<const Latin1Char*>(chars), length);
}
template <typename CharT,
typename = typename std::enable_if<!std::is_const<CharT>::value>::type>
MOZ_ALWAYS_INLINE JSAtom* lookup(CharT* chars, size_t length) {
// Collapse the remaining |CharT*| to |const CharT*| to avoid excess
// instantiations.
return lookup(const_cast<const CharT*>(chars), length);
}
template <typename CharT>
JSAtom* lookup(const CharT* chars, size_t length) {
switch (length) {

View file

@ -111,11 +111,17 @@ StringBuffer::finishString()
JS_STATIC_ASSERT(JSFatInlineString::MAX_LENGTH_LATIN1 < Latin1CharBuffer::InlineLength);
if (isLatin1()) {
if (JSAtom* staticStr = cx->staticStrings().lookup(latin1Chars().begin(), len))
return staticStr;
if (JSInlineString::lengthFits<Latin1Char>(len)) {
mozilla::Range<const Latin1Char> range(latin1Chars().begin(), len);
return NewInlineString<CanGC>(cx, range);
}
} else {
if (JSAtom* staticStr = cx->staticStrings().lookup(twoByteChars().begin(), len))
return staticStr;
if (JSInlineString::lengthFits<char16_t>(len)) {
mozilla::Range<const char16_t> range(twoByteChars().begin(), len);
return NewInlineString<CanGC>(cx, range);