Diff
Modified: trunk/JSTests/ChangeLog (281374 => 281375)
--- trunk/JSTests/ChangeLog 2021-08-21 14:33:08 UTC (rev 281374)
+++ trunk/JSTests/ChangeLog 2021-08-21 16:17:02 UTC (rev 281375)
@@ -1,5 +1,21 @@
2021-08-21 Yusuke Suzuki <[email protected]>
+ [JSC] Intl.DisplayNames v2
+ https://bugs.webkit.org/show_bug.cgi?id=227832
+
+ Reviewed by Ross Kirsling.
+
+ * stress/intl-displaynames-v2.js: Added.
+ (shouldBe):
+ (shouldThrow):
+ (vm.icuVersion):
+ * stress/intl-displaynames.js:
+ (vm.icuVersion):
+ * test262/config.yaml:
+ * test262/expectations.yaml:
+
+2021-08-21 Yusuke Suzuki <[email protected]>
+
[JSC] Intl Locale Info
https://bugs.webkit.org/show_bug.cgi?id=227830
Added: trunk/JSTests/stress/intl-displaynames-v2.js (0 => 281375)
--- trunk/JSTests/stress/intl-displaynames-v2.js (rev 0)
+++ trunk/JSTests/stress/intl-displaynames-v2.js 2021-08-21 16:17:02 UTC (rev 281375)
@@ -0,0 +1,121 @@
+function shouldBe(actual, expected) {
+ if (actual !== expected)
+ throw new Error('bad value: ' + actual);
+}
+
+function shouldThrow(func, errorMessage) {
+ var errorThrown = false;
+ var error = null;
+ try {
+ func();
+ } catch (e) {
+ errorThrown = true;
+ error = e;
+ }
+ if (!errorThrown)
+ throw new Error('not thrown');
+ if (String(error) !== errorMessage)
+ throw new Error(`bad error: ${String(error)}`);
+}
+
+if ($vm.icuVersion() >= 61) {
+ {
+ let displayNames = new Intl.DisplayNames(undefined, {type: 'calendar'});
+ shouldThrow(() => {
+ displayNames.of('');
+ }, `RangeError: argument is not a calendar code`);
+ shouldThrow(() => {
+ displayNames.of('-');
+ }, `RangeError: argument is not a calendar code`);
+ shouldThrow(() => {
+ displayNames.of('aaaaa-');
+ }, `RangeError: argument is not a calendar code`);
+ shouldThrow(() => {
+ displayNames.of('_');
+ }, `RangeError: argument is not a calendar code`);
+ shouldThrow(() => {
+ displayNames.of('aaaaa_');
+ }, `RangeError: argument is not a calendar code`); }
+ {
+ let dn = new Intl.DisplayNames("en", {type: "calendar"})
+ shouldBe(dn.of("roc"), "Minguo Calendar");
+ shouldBe(dn.of("persian"), "Persian Calendar");
+ shouldBe(dn.of("gregory"), "Gregorian Calendar");
+ shouldBe(dn.of("ethioaa"), "Ethiopic Amete Alem Calendar");
+ shouldBe(dn.of("japanese"), "Japanese Calendar");
+ shouldBe(dn.of("dangi"), "Dangi Calendar");
+ shouldBe(dn.of("chinese"), "Chinese Calendar");
+ }
+ {
+ let dn = new Intl.DisplayNames("zh", {type: "calendar"})
+ shouldBe(dn.of("roc"), "民国纪年");
+ shouldBe(dn.of("persian"), "波斯历");
+ shouldBe(dn.of("gregory"), "公历");
+ shouldBe(dn.of("ethioaa"), "埃塞俄比亚阿米特阿莱姆日历");
+ shouldBe(dn.of("japanese"), "和历");
+ shouldBe(dn.of("dangi"), "檀纪历");
+ shouldBe(dn.of("chinese"), "农历");
+ }
+ {
+ let dn = new Intl.DisplayNames("zh", {type: "dateTimeField"})
+ shouldBe(dn.of("era"), "纪元");
+ shouldBe(dn.of("year"), "年");
+ shouldBe(dn.of("month"), "月");
+ shouldBe(dn.of("quarter"), "季度");
+ shouldBe(dn.of("weekOfYear"), "周");
+ shouldBe(dn.of("weekday"), "工作日");
+ shouldBe(dn.of("dayPeriod"), "上午/下午");
+ shouldBe(dn.of("day"), "日");
+ shouldBe(dn.of("hour"), "小时");
+ shouldBe(dn.of("minute"), "分钟");
+ shouldBe(dn.of("second"), "秒");
+ }
+ {
+ let dn = new Intl.DisplayNames("es", {type: "dateTimeField"})
+ shouldBe(dn.of("era"), "era");
+ shouldBe(dn.of("year"), "año");
+ shouldBe(dn.of("month"), "mes");
+ shouldBe(dn.of("quarter"), "trimestre");
+ shouldBe(dn.of("weekOfYear"), "semana");
+ shouldBe(dn.of("weekday"), "día de la semana");
+ let dayPeriod = dn.of("dayPeriod");
+ shouldBe(dayPeriod === "a. m./p. m." || dayPeriod === "a. m./p. m.", true);
+ shouldBe(dn.of("day"), "día");
+ shouldBe(dn.of("hour"), "hora");
+ shouldBe(dn.of("minute"), "minuto");
+ shouldBe(dn.of("second"), "segundo");
+ }
+ {
+ let dn1 = new Intl.DisplayNames("en", {type: "language"})
+ shouldBe(dn1.of("en"), "English");
+ shouldBe(dn1.of("en-GB"), "British English");
+ shouldBe(dn1.of("en-US"), "American English");
+ shouldBe(dn1.of("en-AU"), "Australian English");
+ shouldBe(dn1.of("en-CA"), "Canadian English");
+ shouldBe(dn1.of("zh"), "Chinese");
+ shouldBe(dn1.of("zh-Hant") === "Traditional Chinese" || dn1.of("zh-Hant") === "Chinese, Traditional", true);
+ shouldBe(dn1.of("zh-Hans") === "Simplified Chinese" || dn1.of("zh-Hans") === "Chinese, Simplified", true);
+ }
+ {
+ let dn2 = new Intl.DisplayNames("en", {type: "language", languageDisplay: "dialect"})
+ shouldBe(dn2.of("en"), "English");
+ shouldBe(dn2.of("en-GB"), "British English");
+ shouldBe(dn2.of("en-US"), "American English");
+ shouldBe(dn2.of("en-AU"), "Australian English");
+ shouldBe(dn2.of("en-CA"), "Canadian English");
+ shouldBe(dn2.of("zh"), "Chinese");
+ shouldBe(dn2.of("zh-Hant") === "Traditional Chinese" || dn2.of("zh-Hant") === "Chinese, Traditional", true);
+ shouldBe(dn2.of("zh-Hans") === "Simplified Chinese" || dn2.of("zh-Hans") === "Chinese, Simplified", true);
+ }
+ {
+ let dn3 = new Intl.DisplayNames("en", {type: "language", languageDisplay: "standard"})
+ shouldBe(dn3.of("en"), "English");
+ shouldBe(dn3.of("en-GB") === "English (United Kingdom)" || dn3.of("en-GB") === "English (UK)", true);
+ shouldBe(dn3.of("en-AU"), "English (Australia)");
+ shouldBe(dn3.of("en-CA"), "English (Canada)");
+ shouldBe(dn3.of("en-US") === "English (United States)" || dn3.of("en-US") === "English (US)", true);
+ shouldBe(dn3.of("zh"), "Chinese");
+ shouldBe(dn3.of("zh-Hant") === "Chinese (Traditional)" || dn3.of("zh-Hant") === "Chinese, Traditional", true);
+ shouldBe(dn3.of("zh-Hans") === "Chinese (Simplified)" || dn3.of("zh-Hans") === "Chinese, Simplified", true);
+ }
+}
Modified: trunk/JSTests/stress/intl-displaynames.js (281374 => 281375)
--- trunk/JSTests/stress/intl-displaynames.js 2021-08-21 14:33:08 UTC (rev 281374)
+++ trunk/JSTests/stress/intl-displaynames.js 2021-08-21 16:17:02 UTC (rev 281375)
@@ -178,7 +178,7 @@
return 'en-AA';
}
};
- shouldBe(languageNames.of(object), object);
+ shouldBe(languageNames.of(object), 'en-AA');
}
languageNames = new Intl.DisplayNames(['en'], {type: 'language', fallback: 'none'});
Modified: trunk/JSTests/test262/config.yaml (281374 => 281375)
--- trunk/JSTests/test262/config.yaml 2021-08-21 14:33:08 UTC (rev 281374)
+++ trunk/JSTests/test262/config.yaml 2021-08-21 16:17:02 UTC (rev 281375)
@@ -30,7 +30,6 @@
- import-assertions
- json-modules
- class-static-block
- - Intl.DisplayNames-v2
- callable-boundary-realms
paths:
files:
Modified: trunk/JSTests/test262/expectations.yaml (281374 => 281375)
--- trunk/JSTests/test262/expectations.yaml 2021-08-21 14:33:08 UTC (rev 281374)
+++ trunk/JSTests/test262/expectations.yaml 2021-08-21 16:17:02 UTC (rev 281375)
@@ -609,9 +609,6 @@
default: 'Test262Error: An initialized binding is not created prior to evaluation Expected a ReferenceError to be thrown but no exception was thrown at all'
test/annexB/language/global-code/switch-dflt-global-skip-early-err.js:
default: "SyntaxError: Cannot declare a function that shadows a let/const/class/function variable 'f' in strict mode."
-test/built-ins/Array/prototype/Symbol.unscopables/value.js:
- default: 'Test262Error: `findLast` property value Expected SameValue(«undefined», «true») to be true'
- strict mode: 'Test262Error: `findLast` property value Expected SameValue(«undefined», «true») to be true'
test/built-ins/Date/UTC/fp-evaluation-order.js:
default: 'Test262Error: order of operations / precision in MakeTime Expected SameValue(«NaN», «29312») to be true'
strict mode: 'Test262Error: order of operations / precision in MakeTime Expected SameValue(«NaN», «29312») to be true'
Modified: trunk/Source/_javascript_Core/ChangeLog (281374 => 281375)
--- trunk/Source/_javascript_Core/ChangeLog 2021-08-21 14:33:08 UTC (rev 281374)
+++ trunk/Source/_javascript_Core/ChangeLog 2021-08-21 16:17:02 UTC (rev 281375)
@@ -1,5 +1,39 @@
2021-08-21 Yusuke Suzuki <[email protected]>
+ [JSC] Intl.DisplayNames v2
+ https://bugs.webkit.org/show_bug.cgi?id=227832
+
+ Reviewed by Ross Kirsling.
+
+ This patch implements Intl.DisplayNames v2[1].
+ Newly added names are calendar names and date time field names.
+ For the language name, language display option is added.
+
+ [1]: https://github.com/tc39/intl-displaynames-v2
+
+ * runtime/CommonIdentifiers.h:
+ * runtime/IntlCache.cpp:
+ (JSC::IntlCache::getFieldDisplayName):
+ * runtime/IntlCache.h:
+ * runtime/IntlDisplayNames.cpp:
+ (JSC::IntlDisplayNames::initializeDisplayNames):
+ (JSC::IntlDisplayNames::of const):
+ (JSC::IntlDisplayNames::resolvedOptions const):
+ (JSC::IntlDisplayNames::typeString):
+ (JSC::IntlDisplayNames::languageDisplayString):
+ * runtime/IntlDisplayNames.h:
+ * runtime/IntlObject.cpp:
+ (JSC::isUnicodeLocaleIdentifierType):
+ (JSC::canonicalizeUnicodeLocaleID):
+ (JSC::canonicalizeLocaleList):
+ (JSC::defaultLocale):
+ (JSC::mapBCP47ToICUCalendarKeyword):
+ (JSC::mapICUCollationKeywordToBCP47):
+ (JSC::canonicalizeLanguageTag): Deleted.
+ * runtime/IntlObject.h:
+
+2021-08-21 Yusuke Suzuki <[email protected]>
+
[JSC] Intl Locale Info
https://bugs.webkit.org/show_bug.cgi?id=227830
Modified: trunk/Source/_javascript_Core/runtime/CommonIdentifiers.h (281374 => 281375)
--- trunk/Source/_javascript_Core/runtime/CommonIdentifiers.h 2021-08-21 14:33:08 UTC (rev 281374)
+++ trunk/Source/_javascript_Core/runtime/CommonIdentifiers.h 2021-08-21 16:17:02 UTC (rev 281375)
@@ -116,6 +116,7 @@
macro(exec) \
macro(executionCount) \
macro(exitKind) \
+ macro(fallback) \
macro(flags) \
macro(forEach) \
macro(formatMatcher) \
@@ -154,6 +155,7 @@
macro(jettisonReason) \
macro(join) \
macro(language) \
+ macro(languageDisplay) \
macro(lastIndex) \
macro(length) \
macro(line) \
Modified: trunk/Source/_javascript_Core/runtime/IntlCache.cpp (281374 => 281375)
--- trunk/Source/_javascript_Core/runtime/IntlCache.cpp 2021-08-21 14:33:08 UTC (rev 281374)
+++ trunk/Source/_javascript_Core/runtime/IntlCache.cpp 2021-08-21 16:17:02 UTC (rev 281375)
@@ -53,4 +53,16 @@
return patternBuffer;
}
+Vector<UChar, 32> IntlCache::getFieldDisplayName(const CString& locale, UDateTimePatternField field, UDateTimePGDisplayWidth width, UErrorCode& status)
+{
+ auto sharedGenerator = getSharedPatternGenerator(locale, status);
+ if (U_FAILURE(status))
+ return { };
+ Vector<UChar, 32> buffer;
+ status = callBufferProducingFunction(udatpg_getFieldDisplayName, sharedGenerator, field, width, buffer);
+ if (U_FAILURE(status))
+ return { };
+ return buffer;
+}
+
} // namespace JSC
Modified: trunk/Source/_javascript_Core/runtime/IntlCache.h (281374 => 281375)
--- trunk/Source/_javascript_Core/runtime/IntlCache.h 2021-08-21 14:33:08 UTC (rev 281374)
+++ trunk/Source/_javascript_Core/runtime/IntlCache.h 2021-08-21 16:17:02 UTC (rev 281375)
@@ -39,6 +39,7 @@
IntlCache() = default;
Vector<UChar, 32> getBestDateTimePattern(const CString& locale, const UChar* skeleton, unsigned skeletonSize, UErrorCode&);
+ Vector<UChar, 32> getFieldDisplayName(const CString& locale, UDateTimePatternField, UDateTimePGDisplayWidth, UErrorCode&);
private:
UDateTimePatternGenerator* getSharedPatternGenerator(const CString& locale, UErrorCode& status)
Modified: trunk/Source/_javascript_Core/runtime/IntlDisplayNames.cpp (281374 => 281375)
--- trunk/Source/_javascript_Core/runtime/IntlDisplayNames.cpp 2021-08-21 14:33:08 UTC (rev 281374)
+++ trunk/Source/_javascript_Core/runtime/IntlDisplayNames.cpp 2021-08-21 16:17:02 UTC (rev 281375)
@@ -26,6 +26,7 @@
#include "config.h"
#include "IntlDisplayNames.h"
+#include "IntlCache.h"
#include "IntlObjectInlines.h"
#include "JSCInlines.h"
#include "ObjectConstructor.h"
@@ -95,7 +96,7 @@
m_style = intlOption<Style>(globalObject, options, vm.propertyNames->style, { { "narrow"_s, Style::Narrow }, { "short"_s, Style::Short }, { "long"_s, Style::Long } }, "style must be either \"narrow\", \"short\", or \"long\""_s, Style::Long);
RETURN_IF_EXCEPTION(scope, void());
- auto type = intlOption<std::optional<Type>>(globalObject, options, vm.propertyNames->type, { { "language"_s, Type::Language }, { "region"_s, Type::Region }, { "script"_s, Type::Script }, { "currency"_s, Type::Currency } }, "type must be either \"language\", \"region\", \"script\", or \"currency\""_s, std::nullopt);
+ auto type = intlOption<std::optional<Type>>(globalObject, options, vm.propertyNames->type, { { "language"_s, Type::Language }, { "region"_s, Type::Region }, { "script"_s, Type::Script }, { "currency"_s, Type::Currency }, { "calendar"_s, Type::Calendar }, { "dateTimeField"_s, Type::DateTimeField } }, "type must be either \"language\", \"region\", \"script\", \"currency\", \"calendar\", or \"dateTimeField\""_s, std::nullopt);
RETURN_IF_EXCEPTION(scope, void());
if (!type) {
throwTypeError(globalObject, scope, "type must not be undefined"_s);
@@ -103,17 +104,19 @@
}
m_type = type.value();
- m_fallback = intlOption<Fallback>(globalObject, options, Identifier::fromString(vm, "fallback"), { { "code"_s, Fallback::Code }, { "none"_s, Fallback::None } }, "fallback must be either \"code\" or \"none\""_s, Fallback::Code);
+ m_fallback = intlOption<Fallback>(globalObject, options, vm.propertyNames->fallback, { { "code"_s, Fallback::Code }, { "none"_s, Fallback::None } }, "fallback must be either \"code\" or \"none\""_s, Fallback::Code);
RETURN_IF_EXCEPTION(scope, void());
+ m_languageDisplay = intlOption<LanguageDisplay>(globalObject, options, vm.propertyNames->languageDisplay, { { "dialect"_s, LanguageDisplay::Dialect }, { "standard"_s, LanguageDisplay::Standard } }, "languageDisplay must be either \"dialect\" or \"standard\""_s, LanguageDisplay::Dialect);
+ RETURN_IF_EXCEPTION(scope, void());
+
#if HAVE(ICU_U_LOCALE_DISPLAY_NAMES)
UErrorCode status = U_ZERO_ERROR;
UDisplayContext contexts[] = {
// en_GB displays as 'English (United Kingdom)' (Standard Names) or 'British English' (Dialect Names).
- // We use Dialect Names here, aligned to the examples in the spec draft and V8's behavior.
// https://github.com/tc39/proposal-intl-displaynames#language-display-names
- UDISPCTX_DIALECT_NAMES,
+ (m_type == Type::Language && m_languageDisplay == LanguageDisplay::Standard) ? UDISPCTX_STANDARD_NAMES : UDISPCTX_DIALECT_NAMES,
// Capitailization mode can be picked from several options. Possibly either UDISPCTX_CAPITALIZATION_NONE or UDISPCTX_CAPITALIZATION_FOR_STANDALONE is
// preferable in Intl.DisplayNames. We use UDISPCTX_CAPITALIZATION_FOR_STANDALONE because it makes standalone date format better (fr "Juillet 2008" in ICU test suites),
@@ -153,10 +156,82 @@
auto code = codeValue.toWTFString(globalObject);
RETURN_IF_EXCEPTION(scope, { });
+ // https://tc39.es/proposal-intl-displaynames/#sec-canonicalcodefordisplaynames
+ auto canonicalizeCodeForDisplayNames = [](Type type, String&& code) -> CString {
+ ASSERT(code.isAllASCII());
+ switch (type) {
+ case Type::Language: {
+ return canonicalizeUnicodeLocaleID(code.ascii()).ascii();
+ }
+ case Type::Region: {
+ // Let code be the result of mapping code to upper case as described in 6.1.
+ auto result = code.ascii();
+ char* mutableData = result.mutableData();
+ for (unsigned index = 0; index < result.length(); ++index)
+ mutableData[index] = toASCIIUpper(mutableData[index]);
+ return result;
+ }
+ case Type::Script: {
+ // Let code be the result of mapping the first character in code to upper case, and mapping the second, third and fourth character in code to lower case, as described in 6.1.
+ auto result = code.ascii();
+ char* mutableData = result.mutableData();
+ if (result.length() >= 1)
+ mutableData[0] = toASCIIUpper(mutableData[0]);
+ for (unsigned index = 1; index < result.length(); ++index)
+ mutableData[index] = toASCIILower(mutableData[index]);
+ return result;
+ }
+ case Type::Currency:
+ ASSERT_NOT_REACHED();
+ break;
+ case Type::Calendar: {
+ // Let code be the result of mapping code to lower case as described in 6.1.
+ String lowered = code.convertToASCIILowercase();
+ if (auto mapped = mapBCP47ToICUCalendarKeyword(lowered))
+ lowered = WTFMove(mapped.value());
+ return lowered.ascii();
+ }
+ case Type::DateTimeField: {
+ ASSERT_NOT_REACHED();
+ break;
+ }
+ }
+ return { };
+ };
+
Vector<UChar, 32> buffer;
UErrorCode status = U_ZERO_ERROR;
-
- if (m_type == Type::Currency) {
+ CString canonicalCode;
+ switch (m_type) {
+ case Type::Language: {
+ if (!isUnicodeLanguageId(code)) {
+ throwRangeError(globalObject, scope, "argument is not a language id"_s);
+ return { };
+ }
+ canonicalCode = canonicalizeCodeForDisplayNames(m_type, WTFMove(code));
+ // Do not use uldn_languageDisplayName since it is not expected one for this "language" type. It returns "en-US" for "en-US" code, instead of "American English".
+ status = callBufferProducingFunction(uldn_localeDisplayName, m_displayNames.get(), canonicalCode.data(), buffer);
+ break;
+ }
+ case Type::Region: {
+ if (!isUnicodeRegionSubtag(code)) {
+ throwRangeError(globalObject, scope, "argument is not a region subtag"_s);
+ return { };
+ }
+ canonicalCode = canonicalizeCodeForDisplayNames(m_type, WTFMove(code));
+ status = callBufferProducingFunction(uldn_regionDisplayName, m_displayNames.get(), canonicalCode.data(), buffer);
+ break;
+ }
+ case Type::Script: {
+ if (!isUnicodeScriptSubtag(code)) {
+ throwRangeError(globalObject, scope, "argument is not a script subtag"_s);
+ return { };
+ }
+ canonicalCode = canonicalizeCodeForDisplayNames(m_type, WTFMove(code));
+ status = callBufferProducingFunction(uldn_scriptDisplayName, m_displayNames.get(), canonicalCode.data(), buffer);
+ break;
+ }
+ case Type::Currency: {
// We do not use uldn_keyValueDisplayName + "currency". This is because of the following reasons.
// 1. ICU does not respect UDISPCTX_LENGTH_FULL / UDISPCTX_LENGTH_SHORT in its implementation.
// 2. There is no way to set "narrow" style in ULocaleDisplayNames while currency have "narrow" symbol style.
@@ -202,84 +277,82 @@
// ICU API document.
// > Returns pointer to display string of 'len' UChars. If the resource data contains no entry for 'currency', then 'currency' itself is returned.
if (status == U_USING_DEFAULT_WARNING && result == currency)
- return (m_fallback == Fallback::None) ? jsUndefined() : codeValue;
+ return (m_fallback == Fallback::None) ? jsUndefined() : jsString(vm, String(currency, 3));
return jsString(vm, String(result, length));
}
+ case Type::Calendar: {
+ // a. If code does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
+ if (!isUnicodeLocaleIdentifierType(code)) {
+ throwRangeError(globalObject, scope, "argument is not a calendar code"_s);
+ return { };
+ }
+ canonicalCode = canonicalizeCodeForDisplayNames(m_type, WTFMove(code));
+ status = callBufferProducingFunction(uldn_keyValueDisplayName, m_displayNames.get(), "calendar", canonicalCode.data(), buffer);
+ break;
+ }
+ case Type::DateTimeField: {
+ // We do not use uldn_keyValueDisplayName since it cannot handle narrow length.
+ // Instead, we use udatpg_getFieldDisplayName.
- // https://tc39.es/proposal-intl-displaynames/#sec-canonicalcodefordisplaynames
- auto canonicalizeCodeForDisplayNames = [](Type type, const String& code) -> CString {
- ASSERT(code.isAllASCII());
- auto result = code.ascii();
- char* mutableData = result.mutableData();
- switch (type) {
- case Type::Language: {
- // Let code be the result of mapping code to lower case as described in 6.1.
- for (unsigned index = 0; index < result.length(); ++index)
- mutableData[index] = toASCIILower(mutableData[index]);
- break;
+ // https://tc39.es/intl-displaynames-v2/#sec-isvaliddatetimefieldcode
+ auto isValidDateTimeFieldCode = [](const String& code) -> std::optional<UDateTimePatternField> {
+ if (code == "era"_s)
+ return UDATPG_ERA_FIELD;
+ if (code == "year"_s)
+ return UDATPG_YEAR_FIELD;
+ if (code == "quarter"_s)
+ return UDATPG_QUARTER_FIELD;
+ if (code == "month"_s)
+ return UDATPG_MONTH_FIELD;
+ if (code == "weekOfYear"_s)
+ return UDATPG_WEEK_OF_YEAR_FIELD;
+ if (code == "weekday"_s)
+ return UDATPG_WEEKDAY_FIELD;
+ if (code == "day"_s)
+ return UDATPG_DAY_FIELD;
+ if (code == "dayPeriod"_s)
+ return UDATPG_DAYPERIOD_FIELD;
+ if (code == "hour"_s)
+ return UDATPG_HOUR_FIELD;
+ if (code == "minute"_s)
+ return UDATPG_MINUTE_FIELD;
+ if (code == "second"_s)
+ return UDATPG_SECOND_FIELD;
+ if (code == "timeZoneName"_s)
+ return UDATPG_ZONE_FIELD;
+ return std::nullopt;
+ };
+
+ auto field = isValidDateTimeFieldCode(code);
+ if (!field) {
+ throwRangeError(globalObject, scope, "argument is not a dateTimeField code"_s);
+ return { };
}
- case Type::Region: {
- // Let code be the result of mapping code to upper case as described in 6.1.
- for (unsigned index = 0; index < result.length(); ++index)
- mutableData[index] = toASCIIUpper(mutableData[index]);
+
+ UDateTimePGDisplayWidth style = UDATPG_WIDE;
+ switch (m_style) {
+ case Style::Long:
+ style = UDATPG_WIDE;
break;
- }
- case Type::Script: {
- // Let code be the result of mapping the first character in code to upper case, and mapping the second, third and fourth character in code to lower case, as described in 6.1.
- if (result.length() >= 1)
- mutableData[0] = toASCIIUpper(mutableData[0]);
- for (unsigned index = 1; index < result.length(); ++index)
- mutableData[index] = toASCIILower(mutableData[index]);
+ case Style::Short:
+ style = UDATPG_ABBREVIATED;
break;
- }
- case Type::Currency:
- ASSERT_NOT_REACHED();
+ case Style::Narrow:
+ style = UDATPG_NARROW;
break;
}
- return result;
- };
- switch (m_type) {
- case Type::Language: {
- // If code does not matches the unicode_language_id production, throw a RangeError exception
- if (!isUnicodeLanguageId(code)) {
- throwRangeError(globalObject, scope, "argument is not a language id"_s);
- return { };
- }
- auto language = canonicalizeCodeForDisplayNames(m_type, code);
- // Do not use uldn_languageDisplayName since it is not expected one for this "language" type. It returns "en-US" for "en-US" code, instead of "American English".
- status = callBufferProducingFunction(uldn_localeDisplayName, m_displayNames.get(), language.data(), buffer);
- break;
+ buffer = vm.intlCache().getFieldDisplayName(m_localeCString.data(), field.value(), style, status);
+ if (U_FAILURE(status))
+ return (m_fallback == Fallback::None) ? jsUndefined() : jsString(vm, code);
+ return jsString(vm, String(buffer));
}
- case Type::Region: {
- // If code does not matches the unicode_region_subtag production, throw a RangeError exception
- if (!isUnicodeRegionSubtag(code)) {
- throwRangeError(globalObject, scope, "argument is not a region subtag"_s);
- return { };
- }
- auto region = canonicalizeCodeForDisplayNames(m_type, code);
- status = callBufferProducingFunction(uldn_regionDisplayName, m_displayNames.get(), region.data(), buffer);
- break;
}
- case Type::Script: {
- // If code does not matches the unicode_script_subtag production, throw a RangeError exception
- if (!isUnicodeScriptSubtag(code)) {
- throwRangeError(globalObject, scope, "argument is not a script subtag"_s);
- return { };
- }
- auto script = canonicalizeCodeForDisplayNames(m_type, code);
- status = callBufferProducingFunction(uldn_scriptDisplayName, m_displayNames.get(), script.data(), buffer);
- break;
- }
- case Type::Currency:
- ASSERT_NOT_REACHED();
- break;
- }
if (U_FAILURE(status)) {
// uldn_localeDisplayName, uldn_regionDisplayName, and uldn_scriptDisplayName return U_ILLEGAL_ARGUMENT_ERROR if the display-name is not found.
// We should return undefined if fallback is "none". Otherwise, we should return input value.
if (status == U_ILLEGAL_ARGUMENT_ERROR)
- return (m_fallback == Fallback::None) ? jsUndefined() : codeValue;
+ return (m_fallback == Fallback::None) ? jsUndefined() : jsString(vm, String(canonicalCode.data(), canonicalCode.length()));
return throwTypeError(globalObject, scope, "Failed to query a display name."_s);
}
return jsString(vm, String(buffer));
@@ -298,7 +371,9 @@
options->putDirect(vm, vm.propertyNames->locale, jsString(vm, m_locale));
options->putDirect(vm, vm.propertyNames->style, jsNontrivialString(vm, styleString(m_style)));
options->putDirect(vm, vm.propertyNames->type, jsNontrivialString(vm, typeString(m_type)));
- options->putDirect(vm, Identifier::fromString(vm, "fallback"), jsNontrivialString(vm, fallbackString(m_fallback)));
+ options->putDirect(vm, vm.propertyNames->fallback, jsNontrivialString(vm, fallbackString(m_fallback)));
+ if (m_type == Type::Language)
+ options->putDirect(vm, vm.propertyNames->languageDisplay, jsNontrivialString(vm, languageDisplayString(m_languageDisplay)));
return options;
}
@@ -327,6 +402,10 @@
return "script"_s;
case Type::Currency:
return "currency"_s;
+ case Type::Calendar:
+ return "calendar"_s;
+ case Type::DateTimeField:
+ return "dateTimeField"_s;
}
ASSERT_NOT_REACHED();
return ASCIILiteral::null();
@@ -344,4 +423,16 @@
return ASCIILiteral::null();
}
+ASCIILiteral IntlDisplayNames::languageDisplayString(LanguageDisplay languageDisplay)
+{
+ switch (languageDisplay) {
+ case LanguageDisplay::Dialect:
+ return "dialect"_s;
+ case LanguageDisplay::Standard:
+ return "standard"_s;
+ }
+ ASSERT_NOT_REACHED();
+ return ASCIILiteral::null();
+}
+
} // namespace JSC
Modified: trunk/Source/_javascript_Core/runtime/IntlDisplayNames.h (281374 => 281375)
--- trunk/Source/_javascript_Core/runtime/IntlDisplayNames.h 2021-08-21 14:33:08 UTC (rev 281374)
+++ trunk/Source/_javascript_Core/runtime/IntlDisplayNames.h 2021-08-21 16:17:02 UTC (rev 281375)
@@ -72,12 +72,14 @@
void finishCreation(VM&);
enum class Style : uint8_t { Narrow, Short, Long };
- enum class Type : uint8_t { Language, Region, Script, Currency };
+ enum class Type : uint8_t { Language, Region, Script, Currency, Calendar, DateTimeField };
enum class Fallback : uint8_t { Code, None };
+ enum class LanguageDisplay : uint8_t { Dialect, Standard };
static ASCIILiteral styleString(Style);
static ASCIILiteral typeString(Type);
static ASCIILiteral fallbackString(Fallback);
+ static ASCIILiteral languageDisplayString(LanguageDisplay);
using ULocaleDisplayNamesDeleter = ICUDeleter<uldn_close>;
std::unique_ptr<ULocaleDisplayNames, ULocaleDisplayNamesDeleter> m_displayNames;
@@ -88,6 +90,7 @@
Style m_style { Style::Long };
Type m_type { Type::Language };
Fallback m_fallback { Fallback::Code };
+ LanguageDisplay m_languageDisplay { LanguageDisplay::Dialect };
};
} // namespace JSC
Modified: trunk/Source/_javascript_Core/runtime/IntlObject.cpp (281374 => 281375)
--- trunk/Source/_javascript_Core/runtime/IntlObject.cpp 2021-08-21 14:33:08 UTC (rev 281374)
+++ trunk/Source/_javascript_Core/runtime/IntlObject.cpp 2021-08-21 16:17:02 UTC (rev 281375)
@@ -67,6 +67,7 @@
#include <wtf/NeverDestroyed.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/text/StringImpl.h>
+#include <wtf/text/StringParsingBuffer.h>
#include <wtf/unicode/icu/ICUHelpers.h>
namespace JSC {
@@ -609,24 +610,29 @@
// http://www.unicode.org/reports/tr35/#Unicode_locale_identifier
bool isUnicodeLocaleIdentifierType(StringView string)
{
- ASSERT(!string.isNull());
-
- for (auto part : string.splitAllowingEmptyEntries('-')) {
- auto length = part.length();
- if (length < 3 || length > 8)
- return false;
-
- for (auto character : part.codeUnits()) {
- if (!isASCIIAlphanumeric(character))
+ // Matching the Unicode Locale Identifier type nonterminal.
+ // Because the spec abstract operation is not mentioning to BCP-47 conformance for this matching,
+ // '-' and '_' separators are allowed while BCP-47 only accepts '-'.
+ // On the other hand, IsStructurallyValidLanguageTag explicitly mentions to BCP-47.
+ return readCharactersForParsing(string, [](auto buffer) -> bool {
+ while (true) {
+ auto begin = buffer.position();
+ while (buffer.hasCharactersRemaining() && isASCIIAlphanumeric(*buffer))
+ ++buffer;
+ unsigned length = buffer.position() - begin;
+ if (length < 3 || length > 8)
return false;
+ if (!buffer.hasCharactersRemaining())
+ return true;
+ if (*buffer != '-' && *buffer != '_')
+ return false;
+ ++buffer;
}
- }
-
- return true;
+ });
}
// https://tc39.es/ecma402/#sec-canonicalizeunicodelocaleid
-static String canonicalizeLanguageTag(const CString& tag)
+String canonicalizeUnicodeLocaleID(const CString& tag)
{
auto buffer = localeIDBufferForLanguageTagWithNullTerminator(tag);
if (buffer.isEmpty())
@@ -702,7 +708,7 @@
if (isStructurallyValidLanguageTag(tag)) {
ASSERT(tag.isAllASCII());
- String canonicalizedTag = canonicalizeLanguageTag(tag.ascii());
+ String canonicalizedTag = canonicalizeUnicodeLocaleID(tag.ascii());
if (!canonicalizedTag.isNull()) {
if (seenSet.add(canonicalizedTag).isNewEntry)
seen.append(canonicalizedTag);
@@ -739,7 +745,7 @@
// be determined by WebCore-specific logic like some WK settings. Usually this will return the
// same thing as userPreferredLanguages()[0].
if (auto defaultLanguage = globalObject->globalObjectMethodTable()->defaultLanguage) {
- String locale = canonicalizeLanguageTag(defaultLanguage().utf8());
+ String locale = canonicalizeUnicodeLocaleID(defaultLanguage().utf8());
if (!locale.isEmpty())
return locale;
}
@@ -746,7 +752,7 @@
Vector<String> languages = userPreferredLanguages();
for (const auto& language : languages) {
- String locale = canonicalizeLanguageTag(language.utf8());
+ String locale = canonicalizeUnicodeLocaleID(language.utf8());
if (!locale.isEmpty())
return locale;
}
@@ -1469,9 +1475,19 @@
return std::nullopt;
}
+std::optional<String> mapBCP47ToICUCalendarKeyword(const String& calendar)
+{
+ if (calendar == "gregory"_s)
+ return "gregorian"_s;
+ if (calendar == "islamicc"_s)
+ return "islamic-civil"_s;
+ if (calendar == "ethioaa"_s)
+ return "ethiopic-amete-alem"_s;
+ return std::nullopt;
+}
+
std::optional<String> mapICUCollationKeywordToBCP47(const String& collation)
{
- // Map keyword values to BCP 47 equivalents.
if (collation == "dictionary"_s)
return "dict"_s;
if (collation == "gb2312han"_s)
Modified: trunk/Source/_javascript_Core/runtime/IntlObject.h (281374 => 281375)
--- trunk/Source/_javascript_Core/runtime/IntlObject.h 2021-08-21 14:33:08 UTC (rev 281374)
+++ trunk/Source/_javascript_Core/runtime/IntlObject.h 2021-08-21 16:17:02 UTC (rev 281375)
@@ -127,6 +127,7 @@
bool isUnicodeVariantSubtag(StringView);
bool isUnicodeLanguageId(StringView);
bool isStructurallyValidLanguageTag(StringView);
+String canonicalizeUnicodeLocaleID(const CString& languageTag);
bool isWellFormedCurrencyCode(StringView);
@@ -138,5 +139,6 @@
std::optional<String> mapICUCollationKeywordToBCP47(const String&);
std::optional<String> mapICUCalendarKeywordToBCP47(const String&);
+std::optional<String> mapBCP47ToICUCalendarKeyword(const String&);
} // namespace JSC