Commit dfab3f44 authored by Frank Tang's avatar Frank Tang Committed by V8 LUCI CQ

[intl] Part 2 of NumberFormat v3

Change NumberFormat.prototpe.resolvedOptions to return new options in v3.
Also fix a heap allocation assertion bug in GetStringOrBooleanOption
while the useGrouping option is an invalid argument.

https://github.com/tc39/proposal-intl-numberformat-v3

https://chromestatus.com/guide/edit/5707621009981440

Design Doc: https://docs.google.com/document/d/19jAogPBb6W4Samt8NWGZKu47iv0_KoQhBvLgQH3xvr8/edit

Bug: v8:10776
Change-Id: Iaeeb0398b77394db3c941a2706d44b734a1f9d8c
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3427298Reviewed-by: 's avatarShu-yu Guo <syg@chromium.org>
Commit-Queue: Frank Tang <ftang@chromium.org>
Cr-Commit-Position: refs/heads/main@{#79161}
parent 1b437aa8
......@@ -14,6 +14,7 @@
V(_, calendars_string, "calendars") \
V(_, cardinal_string, "cardinal") \
V(_, caseFirst_string, "caseFirst") \
V(_, ceil_string, "ceil") \
V(_, compare_string, "compare") \
V(_, collation_string, "collation") \
V(_, collations_string, "collations") \
......@@ -33,12 +34,14 @@
V(_, era_string, "era") \
V(_, eraYear_string, "eraYear") \
V(_, exceptZero_string, "exceptZero") \
V(_, expand_string, "expand") \
V(_, exponentInteger_string, "exponentInteger") \
V(_, exponentMinusSign_string, "exponentMinusSign") \
V(_, exponentSeparator_string, "exponentSeparator") \
V(_, fallback_string, "fallback") \
V(_, first_string, "first") \
V(_, firstDay_string, "firstDay") \
V(_, floor_string, "floor") \
V(_, format_string, "format") \
V(_, fraction_string, "fraction") \
V(_, fractionalSecond_string, "fractionalSecond") \
......@@ -51,6 +54,11 @@
V(_, h12_string, "h12") \
V(_, h23_string, "h23") \
V(_, h24_string, "h24") \
V(_, halfCeil_string, "halfCeil") \
V(_, halfEven_string, "halfEven") \
V(_, halfExpand_string, "halfExpand") \
V(_, halfFloor_string, "halfFloor") \
V(_, halfTrunc_string, "halfTrunc") \
V(_, hour12_string, "hour12") \
V(_, hourCycle_string, "hourCycle") \
V(_, hourCycles_string, "hourCycles") \
......@@ -62,6 +70,7 @@
V(_, kana_string, "kana") \
V(_, language_string, "language") \
V(_, languageDisplay_string, "languageDisplay") \
V(_, lessPrecision_string, "lessPrecision") \
V(_, letter_string, "letter") \
V(_, list_string, "list") \
V(_, literal_string, "literal") \
......@@ -71,11 +80,13 @@
V(_, ltr_string, "ltr") \
V(_, maximumFractionDigits_string, "maximumFractionDigits") \
V(_, maximumSignificantDigits_string, "maximumSignificantDigits") \
V(_, min2_string, "min2") \
V(_, minimalDays_string, "minimalDays") \
V(_, minimumFractionDigits_string, "minimumFractionDigits") \
V(_, minimumIntegerDigits_string, "minimumIntegerDigits") \
V(_, minimumSignificantDigits_string, "minimumSignificantDigits") \
V(_, minusSign_string, "minusSign") \
V(_, morePrecision_string, "morePrecision") \
V(_, nan_string, "nan") \
V(_, narrowSymbol_string, "narrowSymbol") \
V(_, negative_string, "negative") \
......@@ -92,6 +103,8 @@
V(_, quarter_string, "quarter") \
V(_, region_string, "region") \
V(_, relatedYear_string, "relatedYear") \
V(_, roundingMode_string, "roundingMode") \
V(_, roundingPriority_string, "roundingPriority") \
V(_, rtl_string, "rtl") \
V(_, scientific_string, "scientific") \
V(_, segment_string, "segment") \
......@@ -104,12 +117,15 @@
V(_, standard_string, "standard") \
V(_, startRange_string, "startRange") \
V(_, strict_string, "strict") \
V(_, stripIfInteger_string, "stripIfInteger") \
V(_, style_string, "style") \
V(_, term_string, "term") \
V(_, textInfo_string, "textInfo") \
V(_, timeStyle_string, "timeStyle") \
V(_, timeZones_string, "timeZones") \
V(_, timeZoneName_string, "timeZoneName") \
V(_, trailingZeroDisplay_string, "trailingZeroDisplay") \
V(_, trunc_string, "trunc") \
V(_, type_string, "type") \
V(_, unknown_string, "unknown") \
V(_, upper_string, "upper") \
......
......@@ -411,6 +411,35 @@ bool UseGroupingFromSkeleton(const icu::UnicodeString& skeleton) {
return skeleton.indexOf("group-off") == -1;
}
Handle<Object> UseGroupingFromSkeleton(Isolate* isolate,
const icu::UnicodeString& skeleton) {
Factory* factory = isolate->factory();
static const char* group = "group-";
int32_t start = skeleton.indexOf(group);
if (start >= 0) {
DCHECK_EQ(6, strlen(group));
icu::UnicodeString check = skeleton.tempSubString(start + 6);
// Ex: skeleton as
// .### rounding-mode-half-up group-off
if (check.startsWith("off")) {
return factory->false_value();
}
// Ex: skeleton as
// .### rounding-mode-half-up group-min2
if (check.startsWith("min2")) {
return ReadOnlyRoots(isolate).min2_string_handle();
}
// Ex: skeleton as
// .### rounding-mode-half-up group-on-aligned
if (check.startsWith("on-aligned")) {
return ReadOnlyRoots(isolate).always_string_handle();
}
}
// Ex: skeleton as
// .###
return ReadOnlyRoots(isolate).auto_string_handle();
}
// Parse currency code from skeleton. For example, skeleton as
// "currency/TWD .00 rounding-mode-half-up unit-width-full-name;"
const icu::UnicodeString CurrencyFromSkeleton(
......@@ -547,6 +576,109 @@ Handle<String> SignDisplayString(Isolate* isolate,
return ReadOnlyRoots(isolate).auto_string_handle();
}
// Return RoundingMode as string based on skeleton.
Handle<String> RoundingModeString(Isolate* isolate,
const icu::UnicodeString& skeleton) {
static const char* rounding_mode = "rounding-mode-";
int32_t start = skeleton.indexOf(rounding_mode);
if (start >= 0) {
DCHECK_EQ(14, strlen(rounding_mode));
icu::UnicodeString check = skeleton.tempSubString(start + 14);
// Ex: skeleton as
// .### rounding-mode-ceiling
if (check.startsWith("ceiling")) {
return ReadOnlyRoots(isolate).ceil_string_handle();
}
// Ex: skeleton as
// .### rounding-mode-down
if (check.startsWith("down")) {
return ReadOnlyRoots(isolate).trunc_string_handle();
}
// Ex: skeleton as
// .### rounding-mode-floor
if (check.startsWith("floor")) {
return ReadOnlyRoots(isolate).floor_string_handle();
}
// Ex: skeleton as
// .### rounding-mode-half-ceiling
if (check.startsWith("half-ceiling")) {
return ReadOnlyRoots(isolate).halfCeil_string_handle();
}
// Ex: skeleton as
// .### rounding-mode-half-down
if (check.startsWith("half-down")) {
return ReadOnlyRoots(isolate).halfTrunc_string_handle();
}
// Ex: skeleton as
// .### rounding-mode-half-floor
if (check.startsWith("half-floor")) {
return ReadOnlyRoots(isolate).halfFloor_string_handle();
}
// Ex: skeleton as
// .### rounding-mode-half-up
if (check.startsWith("half-up")) {
return ReadOnlyRoots(isolate).halfExpand_string_handle();
}
// Ex: skeleton as
// .### rounding-mode-up
if (check.startsWith("up")) {
return ReadOnlyRoots(isolate).expand_string_handle();
}
}
// Ex: skeleton as
// .###
return ReadOnlyRoots(isolate).halfEven_string_handle();
}
Handle<Object> RoundingIncrement(Isolate* isolate,
const icu::UnicodeString& skeleton) {
int32_t cur = skeleton.indexOf(u"precision-increment/");
if (cur < 0) return isolate->factory()->NewNumberFromInt(1);
cur += 20; // length of "precision-increment/"
int32_t increment = 0;
while (cur < skeleton.length()) {
char16_t c = skeleton[cur++];
if (c == u'.') continue;
if (!IsDecimalDigit(c)) break;
increment = increment * 10 + (c - '0');
}
return isolate->factory()->NewNumberFromInt(increment);
}
// Return RoundingPriority as string based on skeleton.
Handle<String> RoundingPriorityString(Isolate* isolate,
const icu::UnicodeString& skeleton) {
int32_t found;
// If #r or @r is followed by a SPACE or in the end of line.
if ((found = skeleton.indexOf("#r")) >= 0 ||
(found = skeleton.indexOf("@r")) >= 0) {
if (found + 2 == skeleton.length() || skeleton[found + 2] == ' ') {
return ReadOnlyRoots(isolate).morePrecision_string_handle();
}
}
// If #s or @s is followed by a SPACE or in the end of line.
if ((found = skeleton.indexOf("#s")) >= 0 ||
(found = skeleton.indexOf("@s")) >= 0) {
if (found + 2 == skeleton.length() || skeleton[found + 2] == ' ') {
return ReadOnlyRoots(isolate).morePrecision_string_handle();
}
}
return ReadOnlyRoots(isolate).auto_string_handle();
}
// Return trailingZeroDisplay as string based on skeleton.
Handle<String> TrailingZeroDisplayString(Isolate* isolate,
const icu::UnicodeString& skeleton) {
int32_t found;
if ((found = skeleton.indexOf("/w")) >= 0) {
if (found + 2 == skeleton.length() || skeleton[found + 2] == ' ') {
return ReadOnlyRoots(isolate).stripIfInteger_string_handle();
}
}
return ReadOnlyRoots(isolate).auto_string_handle();
}
} // anonymous namespace
// Return the minimum integer digits by counting the number of '0' after
......@@ -815,6 +947,11 @@ Handle<JSObject> JSNumberFormat::ResolvedOptions(
// [[Notation]] "notation"
// [[CompactDisplay]] "compactDisplay"
// [[SignDisplay]] "signDisplay"
//
// For v3
// [[RoundingMode]] "roundingMode"
// [[RoundingIncrement]] "roundingIncrement"
// [[TrailingZeroDisplay]] "trailingZeroDisplay"
CHECK(JSReceiver::CreateDataProperty(isolate, options,
factory->locale_string(), locale,
......@@ -895,11 +1032,18 @@ Handle<JSObject> JSNumberFormat::ResolvedOptions(
.FromJust());
}
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->useGrouping_string(),
factory->ToBoolean(UseGroupingFromSkeleton(skeleton)),
Just(kDontThrow))
.FromJust());
if (FLAG_harmony_intl_number_format_v3) {
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->useGrouping_string(),
UseGroupingFromSkeleton(isolate, skeleton), Just(kDontThrow))
.FromJust());
} else {
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->useGrouping_string(),
factory->ToBoolean(UseGroupingFromSkeleton(skeleton)),
Just(kDontThrow))
.FromJust());
}
Notation notation = NotationFromSkeleton(skeleton);
CHECK(JSReceiver::CreateDataProperty(
......@@ -917,6 +1061,24 @@ Handle<JSObject> JSNumberFormat::ResolvedOptions(
isolate, options, factory->signDisplay_string(),
SignDisplayString(isolate, skeleton), Just(kDontThrow))
.FromJust());
if (FLAG_harmony_intl_number_format_v3) {
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->roundingMode_string(),
RoundingModeString(isolate, skeleton), Just(kDontThrow))
.FromJust());
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->roundingIncrement_string(),
RoundingIncrement(isolate, skeleton), Just(kDontThrow))
.FromJust());
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->trailingZeroDisplay_string(),
TrailingZeroDisplayString(isolate, skeleton), Just(kDontThrow))
.FromJust());
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->roundingPriority_string(),
RoundingPriorityString(isolate, skeleton), Just(kDontThrow))
.FromJust());
}
return options;
}
......
......@@ -108,24 +108,26 @@ V8_WARN_UNUSED_RESULT static Maybe<T> GetStringOrBooleanOption(
// RangeError exception.
// 8. Return value.
value_str = String::Flatten(isolate, value_str);
DisallowGarbageCollection no_gc;
const String::FlatContent& flat = value_str->GetFlatContent(no_gc);
int32_t length = value_str->length();
for (size_t i = 0; i < str_values.size(); i++) {
if (static_cast<int32_t>(strlen(str_values.at(i))) == length) {
if (flat.IsOneByte()) {
if (CompareCharsEqual(str_values.at(i), flat.ToOneByteVector().begin(),
length)) {
return Just(enum_values[i]);
}
} else {
if (CompareCharsEqual(str_values.at(i), flat.ToUC16Vector().begin(),
length)) {
return Just(enum_values[i]);
{
DisallowGarbageCollection no_gc;
const String::FlatContent& flat = value_str->GetFlatContent(no_gc);
int32_t length = value_str->length();
for (size_t i = 0; i < str_values.size(); i++) {
if (static_cast<int32_t>(strlen(str_values.at(i))) == length) {
if (flat.IsOneByte()) {
if (CompareCharsEqual(str_values.at(i),
flat.ToOneByteVector().begin(), length)) {
return Just(enum_values[i]);
}
} else {
if (CompareCharsEqual(str_values.at(i), flat.ToUC16Vector().begin(),
length)) {
return Just(enum_values[i]);
}
}
}
}
}
} // end of no_gc
THROW_NEW_ERROR_RETURN_VALUE(
isolate,
NewRangeError(MessageTemplate::kValueOutOfRange, value,
......
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-intl-number-format-v3
// Check the rounding behavior.
// Based on https://tc39.es/proposal-intl-numberformat-v3/out/numberformat/diff.html#table-intl-rounding-modes
let inputs = [-1.5, 0.4, 0.5, 0.6, 1.5];
let expectations = {
"ceil": ["-1", "1", "1", "1", "2"],
"floor": ["-2", "0", "0", "0", "1"],
"expand": ["-2", "1", "1", "1", "2"],
"trunc": ["-1", "0", "0", "0", "1"],
"halfCeil": ["-1", "0", "1", "1", "2"],
"halfFloor": ["-2", "0", "0", "1", "1"],
"halfExpand": ["-2", "0", "1", "1", "2"],
"halfTrunc": ["-1", "0", "0", "1", "1"],
"halfEven": ["-2", "0", "0", "1", "2"],
};
Object.keys(expectations).forEach(function(roundingMode) {
let exp = expectations[roundingMode];
let idx = 0;
let nf = new Intl.NumberFormat("en", {roundingMode, maximumFractionDigits: 0});
assertEquals(roundingMode, nf.resolvedOptions().roundingMode);
inputs.forEach(function(input) {
let msg = "input: " + input + " with roundingMode: " + roundingMode;
assertEquals(exp[idx++], nf.format(input), msg);
})
});
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-intl-number-format-v3
let validRoundingMode = [
"ceil",
"floor",
"expand",
"halfCeil",
"halfExpand",
"halfFloor",
"halfTrunc",
"halfEven",
"trunc",
];
let invalidRoundingMode = [
"ceiling",
"down",
"Down",
"flooring",
"halfDown",
"halfUp",
"halfup",
"halfeven",
"halfdown",
"half-up",
"half-even",
"half-down",
"up",
"Up",
];
validRoundingMode.forEach(function(roundingMode) {
let nf = new Intl.NumberFormat(undefined, {roundingMode});
assertEquals(roundingMode, nf.resolvedOptions().roundingMode);
});
invalidRoundingMode.forEach(function(roundingMode) {
assertThrows(() => {
let nf = new Intl.NumberFormat(undefined, {roundingMode}); });
});
// Check default is "halfExpand"
assertEquals("halfExpand", (new Intl.NumberFormat().resolvedOptions().roundingMode));
assertEquals("halfExpand", (new Intl.NumberFormat(
undefined, {roundingMode: undefined}).resolvedOptions().roundingMode));
// Check roundingMode is read once after reading signDisplay
let read = [];
let options = {
get signDisplay() { read.push('signDisplay'); return undefined; },
get roundingMode() { read.push('roundingMode'); return undefined; },
};
assertDoesNotThrow(() => new Intl.NumberFormat(undefined, options));
assertEquals("signDisplay,roundingMode", read.join(","));
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-intl-number-format-v3
let defaultFmt = new Intl.NumberFormat("en",
{ minimumFractionDigits: 2, maximumFractionDigits: 2 });
let autoFmt = new Intl.NumberFormat("en",
{ minimumFractionDigits: 2, maximumFractionDigits: 2,
trailingZeroDisplay: 'auto'});
let stripIfIntegerFmt = new Intl.NumberFormat("en",
{ minimumFractionDigits: 2, maximumFractionDigits: 2,
trailingZeroDisplay: 'stripIfInteger'});
assertEquals("auto", defaultFmt.resolvedOptions().trailingZeroDisplay);
assertEquals("auto", autoFmt.resolvedOptions().trailingZeroDisplay);
assertEquals("stripIfInteger",
stripIfIntegerFmt.resolvedOptions().trailingZeroDisplay);
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-intl-number-format-v3
let validUseGrouping = [
"min2",
"auto",
"always",
false,
];
let invalidUseGrouping = [
"min-2",
"true",
];
validUseGrouping.forEach(function(useGrouping) {
let nf = new Intl.NumberFormat(undefined, {useGrouping});
assertEquals(useGrouping, nf.resolvedOptions().useGrouping);
});
invalidUseGrouping.forEach(function(useGrouping) {
assertThrows(() => {
let nf = new Intl.NumberFormat(undefined, {useGrouping}); });
});
// useGrouping: undefined get "auto"
assertEquals("auto",
(new Intl.NumberFormat()).resolvedOptions().useGrouping);
assertEquals("auto",
(new Intl.NumberFormat(undefined, {useGrouping: undefined}))
.resolvedOptions().useGrouping);
// useGrouping: true get "always"
assertEquals("always",
(new Intl.NumberFormat(undefined, {useGrouping: true}))
.resolvedOptions().useGrouping);
// useGrouping: false get false
// useGrouping: "" get false
assertEquals(false,
(new Intl.NumberFormat(undefined, {useGrouping: false}))
.resolvedOptions().useGrouping);
assertEquals(false,
(new Intl.NumberFormat(undefined, {useGrouping: ""}))
.resolvedOptions().useGrouping);
// Some locales with default minimumGroupingDigits
let mgd1 = ["en"];
// Some locales with default minimumGroupingDigits{"2"}
let mgd2 = ["es", "pl", "lv"];
let all = mgd1.concat(mgd2);
// Check "always"
all.forEach(function(locale) {
let off = new Intl.NumberFormat(locale, {useGrouping: false});
let msg = "locale: " + locale + " useGrouping: false";
// In useGrouping: false, no grouping.
assertEquals(3, off.format(123).length, msg);
assertEquals(4, off.format(1234).length, msg);
assertEquals(5, off.format(12345).length, msg);
assertEquals(6, off.format(123456).length, msg);
assertEquals(7, off.format(1234567).length, msg);
});
// Check false
all.forEach(function(locale) {
let always = new Intl.NumberFormat(locale, {useGrouping: "always"});
let msg = "locale: " + locale + " useGrouping: 'always'";
assertEquals(3, always.format(123).length);
// In useGrouping: "always", has grouping when more than 3 digits..
assertEquals(4 + 1, always.format(1234).length, msg);
assertEquals(5 + 1, always.format(12345).length, msg);
assertEquals(6 + 1, always.format(123456).length, msg);
assertEquals(7 + 2, always.format(1234567).length, msg);
});
// Check "min2"
all.forEach(function(locale) {
let always = new Intl.NumberFormat(locale, {useGrouping: "min2"});
let msg = "locale: " + locale + " useGrouping: 'min2'";
assertEquals(3, always.format(123).length);
// In useGrouping: "min2", no grouping for 4 digits but has grouping
// when more than 4 digits..
assertEquals(4, always.format(1234).length, msg);
assertEquals(5 + 1, always.format(12345).length, msg);
assertEquals(6 + 1, always.format(123456).length, msg);
assertEquals(7 + 2, always.format(1234567).length, msg);
});
// Check "auto"
mgd1.forEach(function(locale) {
let auto = new Intl.NumberFormat(locale, {useGrouping: "auto"});
let msg = "locale: " + locale + " useGrouping: 'auto'";
assertEquals(3, auto.format(123).length, msg);
assertEquals(4 + 1, auto.format(1234).length, msg);
assertEquals(5 + 1, auto.format(12345).length, msg);
assertEquals(6 + 1, auto.format(123456).length, msg);
assertEquals(7 + 2, auto.format(1234567).length, msg);
});
mgd2.forEach(function(locale) {
let auto = new Intl.NumberFormat(locale, {useGrouping: "auto"});
let msg = "locale: " + locale + " useGrouping: 'auto'";
assertEquals(3, auto.format(123).length, msg);
// In useGrouping: "auto", since these locales has
// minimumGroupingDigits{"2"}, no grouping for 4 digits but has grouping
// when more than 4 digits..
assertEquals(4, auto.format(1234).length, msg);
assertEquals(5 + 1, auto.format(12345).length, msg);
assertEquals(6 + 1, auto.format(123456).length, msg);
assertEquals(7 + 2, auto.format(1234567).length, msg);
});
......@@ -2549,12 +2549,6 @@
'intl402/NumberFormat/prototype/formatRangeToParts/prop-desc': [FAIL],
'intl402/NumberFormat/prototype/formatRangeToParts/x-greater-than-y-throws': [FAIL],
# NumberFormat.prototype.resolvedOptions
'intl402/NumberFormat/constructor-trailingZeroDisplay': [FAIL],
'intl402/NumberFormat/prototype/resolvedOptions/basic': [FAIL],
'intl402/NumberFormat/prototype/resolvedOptions/roundingMode': [FAIL],
'intl402/NumberFormat/test-option-useGrouping': [FAIL],
'intl402/NumberFormat/test-option-useGrouping-extended': [FAIL],
# PluralRules.prototype.selectRange
'intl402/PluralRules/prototype/selectRange/default-en-us': [FAIL],
'intl402/PluralRules/prototype/selectRange/invoked-as-func': [FAIL],
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment