mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-25 17:25:08 +02:00
LibJS+LibUnicode: Support Intl.MathematicalValue in Intl.PluralRules
This is a normative change in the ECMA-402 spec. See: https://github.com/tc39/ecma402/commit/7344f42 The main difference here is that Intl.PluralRules now supports BigInt.
This commit is contained in:
Notes:
github-actions[bot]
2026-02-06 17:20:49 +00:00
Author: https://github.com/trflynn89 Commit: https://github.com/LadybirdBrowser/ladybird/commit/72a6f59df58 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/7789
@@ -32,30 +32,44 @@ ReadonlySpan<ResolutionOptionDescriptor> PluralRules::resolution_option_descript
|
||||
}
|
||||
|
||||
// 17.5.2 ResolvePlural ( pluralRules, n ), https://tc39.es/ecma402/#sec-resolveplural
|
||||
Unicode::PluralCategory resolve_plural(PluralRules const& plural_rules, Value number)
|
||||
Unicode::PluralCategory resolve_plural(PluralRules const& plural_rules, MathematicalValue const& number)
|
||||
{
|
||||
// 1. If n is not a finite Number, then
|
||||
if (!number.is_finite_number()) {
|
||||
// a. Let s be ! ToString(n).
|
||||
// 1. If n is NOT-A-NUMBER, then
|
||||
if (number.is_nan()) {
|
||||
// a. Let s be an ILD String value indicating the NaN value.
|
||||
// b. Return the Record { [[PluralCategory]]: "other", [[FormattedString]]: s }.
|
||||
return Unicode::PluralCategory::Other;
|
||||
}
|
||||
|
||||
// 2. Let res be FormatNumericToString(pluralRules, ℝ(n)).
|
||||
// 3. Let s be res.[[FormattedString]].
|
||||
// 4. Let locale be pluralRules.[[Locale]].
|
||||
// 5. Let type be pluralRules.[[Type]].
|
||||
// 6. Let notation be pluralRules.[[Notation]].
|
||||
// 7. Let compactDisplay be pluralRules.[[CompactDisplay]].
|
||||
// 8. Let p be PluralRuleSelect(locale, type, notation, compactDisplay, s).
|
||||
// 9. Return the Record { [[PluralCategory]]: p, [[FormattedString]]: s }.
|
||||
return plural_rules.formatter().select_plural(number.as_double());
|
||||
// 2. If n is POSITIVE-INFINITY, then
|
||||
if (number.is_positive_infinity()) {
|
||||
// a. Let s be an ILD String value indicating positive infinity.
|
||||
// b. Return the Record { [[PluralCategory]]: "other", [[FormattedString]]: s }.
|
||||
return Unicode::PluralCategory::Other;
|
||||
}
|
||||
|
||||
// 3. If n is NEGATIVE-INFINITY, then
|
||||
if (number.is_negative_infinity()) {
|
||||
// a. Let s be an ILD String value indicating negative infinity.
|
||||
// b. Return the Record { [[PluralCategory]]: "other", [[FormattedString]]: s }.
|
||||
return Unicode::PluralCategory::Other;
|
||||
}
|
||||
|
||||
// 4. Let res be FormatNumericToString(pluralRules, n).
|
||||
// 5. Let s be res.[[FormattedString]].
|
||||
// 6. Let locale be pluralRules.[[Locale]].
|
||||
// 7. Let type be pluralRules.[[Type]].
|
||||
// 8. Let notation be pluralRules.[[Notation]].
|
||||
// 9. Let compactDisplay be pluralRules.[[CompactDisplay]].
|
||||
// 10. Let p be PluralRuleSelect(locale, type, notation, compactDisplay, s).
|
||||
// 11. Return the Record { [[PluralCategory]]: p, [[FormattedString]]: s }.
|
||||
return plural_rules.formatter().select_plural(number.to_value());
|
||||
}
|
||||
|
||||
// 17.5.4 ResolvePluralRange ( pluralRules, x, y ), https://tc39.es/ecma402/#sec-resolveplural
|
||||
ThrowCompletionOr<Unicode::PluralCategory> resolve_plural_range(VM& vm, PluralRules const& plural_rules, Value start, Value end)
|
||||
// 17.5.4 ResolvePluralRange ( pluralRules, x, y ), https://tc39.es/ecma402/#sec-resolvepluralrange
|
||||
ThrowCompletionOr<Unicode::PluralCategory> resolve_plural_range(VM& vm, PluralRules const& plural_rules, MathematicalValue const& start, MathematicalValue const& end)
|
||||
{
|
||||
// 1. If x is NaN or y is NaN, throw a RangeError exception.
|
||||
// 1. If x is NOT-A-NUMBER or y is NOT-A-NUMBER, throw a RangeError exception.
|
||||
if (start.is_nan())
|
||||
return vm.throw_completion<RangeError>(ErrorType::NumberIsNaN, "start"sv);
|
||||
if (end.is_nan())
|
||||
@@ -70,7 +84,7 @@ ThrowCompletionOr<Unicode::PluralCategory> resolve_plural_range(VM& vm, PluralRu
|
||||
// 7. Let notation be pluralRules.[[Notation]].
|
||||
// 8. Let compactDisplay be pluralRules.[[CompactDisplay]].
|
||||
// 9. Return PluralRuleSelectRange(locale, type, notation, compactDisplay, xp.[[PluralCategory]], yp.[[PluralCategory]]).
|
||||
return plural_rules.formatter().select_plural_range(start.as_double(), end.as_double());
|
||||
return plural_rules.formatter().select_plural_range(start.to_value(), end.to_value());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ private:
|
||||
Unicode::PluralForm m_type { Unicode::PluralForm::Cardinal }; // [[Type]]
|
||||
};
|
||||
|
||||
Unicode::PluralCategory resolve_plural(PluralRules const&, Value number);
|
||||
ThrowCompletionOr<Unicode::PluralCategory> resolve_plural_range(VM&, PluralRules const&, Value start, Value end);
|
||||
Unicode::PluralCategory resolve_plural(PluralRules const&, MathematicalValue const& number);
|
||||
ThrowCompletionOr<Unicode::PluralCategory> resolve_plural_range(VM&, PluralRules const&, MathematicalValue const& start, MathematicalValue const& end);
|
||||
|
||||
}
|
||||
|
||||
@@ -91,12 +91,14 @@ JS_DEFINE_NATIVE_FUNCTION(PluralRulesPrototype::resolved_options)
|
||||
// 17.3.3 Intl.PluralRules.prototype.select ( value ), https://tc39.es/ecma402/#sec-intl.pluralrules.prototype.select
|
||||
JS_DEFINE_NATIVE_FUNCTION(PluralRulesPrototype::select)
|
||||
{
|
||||
auto value = vm.argument(0);
|
||||
|
||||
// 1. Let pr be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(pr, [[InitializedPluralRules]]).
|
||||
auto plural_rules = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. Let n be ? ToNumber(value).
|
||||
auto number = TRY(vm.argument(0).to_number(vm));
|
||||
// 3. Let n be ? ToIntlMathematicalValue(value).
|
||||
auto number = TRY(to_intl_mathematical_value(vm, value));
|
||||
|
||||
// 4. Return ! ResolvePlural(pr, n).[[PluralCategory]].
|
||||
auto plurality = resolve_plural(plural_rules, number);
|
||||
@@ -119,11 +121,11 @@ JS_DEFINE_NATIVE_FUNCTION(PluralRulesPrototype::select_range)
|
||||
if (end.is_undefined())
|
||||
return vm.throw_completion<TypeError>(ErrorType::IsUndefined, "end"sv);
|
||||
|
||||
// 4. Let x be ? ToNumber(start).
|
||||
auto x = TRY(start.to_number(vm));
|
||||
// 4. Let x be ? ToIntlMathematicalValue(start).
|
||||
auto x = TRY(to_intl_mathematical_value(vm, start));
|
||||
|
||||
// 5. Let y be ? ToNumber(end).
|
||||
auto y = TRY(end.to_number(vm));
|
||||
// 5. Let y be ? ToIntlMathematicalValue(end).
|
||||
auto y = TRY(to_intl_mathematical_value(vm, end));
|
||||
|
||||
// 6. Return ? ResolvePluralRange(pr, x, y).
|
||||
auto plurality = TRY(resolve_plural_range(vm, plural_rules, x, y));
|
||||
|
||||
@@ -658,7 +658,7 @@ public:
|
||||
VERIFY(icu_success(status));
|
||||
}
|
||||
|
||||
virtual PluralCategory select_plural(double value) const override
|
||||
virtual PluralCategory select_plural(Value const& value) const override
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
VERIFY(m_plural_rules);
|
||||
@@ -674,7 +674,7 @@ public:
|
||||
return plural_category_from_string(icu_string_to_utf16_view(result));
|
||||
}
|
||||
|
||||
virtual PluralCategory select_plural_range(double start, double end) const override
|
||||
virtual PluralCategory select_plural_range(Value const& start, Value const& end) const override
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
VERIFY(m_plural_rules);
|
||||
|
||||
@@ -163,8 +163,8 @@ public:
|
||||
virtual Vector<Partition> format_range_to_parts(Value const&, Value const&) const = 0;
|
||||
|
||||
virtual void create_plural_rules(PluralForm) = 0;
|
||||
virtual PluralCategory select_plural(double) const = 0;
|
||||
virtual PluralCategory select_plural_range(double, double) const = 0;
|
||||
virtual PluralCategory select_plural(Value const&) const = 0;
|
||||
virtual PluralCategory select_plural_range(Value const&, Value const&) const = 0;
|
||||
virtual Vector<PluralCategory> available_plural_categories() const = 0;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -33,66 +33,74 @@ describe("non-finite values", () => {
|
||||
});
|
||||
|
||||
describe("correct behavior", () => {
|
||||
const testPluralRules = (pluralRules, number, expected) => {
|
||||
expect(pluralRules.select(number)).toBe(expected);
|
||||
|
||||
if (Number.isInteger(number)) {
|
||||
expect(pluralRules.select(BigInt(number))).toBe(expected);
|
||||
}
|
||||
};
|
||||
|
||||
test("cardinal", () => {
|
||||
const en = new Intl.PluralRules("en", { type: "cardinal" });
|
||||
expect(en.select(0)).toBe("other");
|
||||
expect(en.select(1)).toBe("one");
|
||||
expect(en.select(2)).toBe("other");
|
||||
expect(en.select(3)).toBe("other");
|
||||
testPluralRules(en, 0, "other");
|
||||
testPluralRules(en, 1, "one");
|
||||
testPluralRules(en, 2, "other");
|
||||
testPluralRules(en, 3, "other");
|
||||
|
||||
// In "he":
|
||||
// "one" is specified to be the integer 1, and non-integers whose integer part is 0.
|
||||
// "two" is specified to be the integer 2.
|
||||
const he = new Intl.PluralRules("he", { type: "cardinal" });
|
||||
expect(he.select(0)).toBe("other");
|
||||
expect(he.select(1)).toBe("one");
|
||||
expect(he.select(0.1)).toBe("one");
|
||||
expect(he.select(0.2)).toBe("one");
|
||||
expect(he.select(0.8)).toBe("one");
|
||||
expect(he.select(0.9)).toBe("one");
|
||||
expect(he.select(2)).toBe("two");
|
||||
expect(he.select(10)).toBe("other");
|
||||
expect(he.select(19)).toBe("other");
|
||||
expect(he.select(20)).toBe("other");
|
||||
expect(he.select(21)).toBe("other");
|
||||
expect(he.select(29)).toBe("other");
|
||||
expect(he.select(30)).toBe("other");
|
||||
expect(he.select(31)).toBe("other");
|
||||
testPluralRules(he, 0, "other");
|
||||
testPluralRules(he, 1, "one");
|
||||
testPluralRules(he, 0.1, "one");
|
||||
testPluralRules(he, 0.2, "one");
|
||||
testPluralRules(he, 0.8, "one");
|
||||
testPluralRules(he, 0.9, "one");
|
||||
testPluralRules(he, 2, "two");
|
||||
testPluralRules(he, 10, "other");
|
||||
testPluralRules(he, 19, "other");
|
||||
testPluralRules(he, 20, "other");
|
||||
testPluralRules(he, 21, "other");
|
||||
testPluralRules(he, 29, "other");
|
||||
testPluralRules(he, 30, "other");
|
||||
testPluralRules(he, 31, "other");
|
||||
|
||||
// In "pl":
|
||||
// "few" is specified to be integers such that (i % 10 == 2..4 && i % 100 != 12..14).
|
||||
// "many" is specified to be all other integers != 1.
|
||||
// "other" is specified to be non-integers.
|
||||
const pl = new Intl.PluralRules("pl", { type: "cardinal" });
|
||||
expect(pl.select(0)).toBe("many");
|
||||
expect(pl.select(1)).toBe("one");
|
||||
expect(pl.select(2)).toBe("few");
|
||||
expect(pl.select(3)).toBe("few");
|
||||
expect(pl.select(4)).toBe("few");
|
||||
expect(pl.select(5)).toBe("many");
|
||||
expect(pl.select(12)).toBe("many");
|
||||
expect(pl.select(13)).toBe("many");
|
||||
expect(pl.select(14)).toBe("many");
|
||||
expect(pl.select(21)).toBe("many");
|
||||
expect(pl.select(22)).toBe("few");
|
||||
expect(pl.select(23)).toBe("few");
|
||||
expect(pl.select(24)).toBe("few");
|
||||
expect(pl.select(25)).toBe("many");
|
||||
expect(pl.select(3.14)).toBe("other");
|
||||
testPluralRules(pl, 0, "many");
|
||||
testPluralRules(pl, 1, "one");
|
||||
testPluralRules(pl, 2, "few");
|
||||
testPluralRules(pl, 3, "few");
|
||||
testPluralRules(pl, 4, "few");
|
||||
testPluralRules(pl, 5, "many");
|
||||
testPluralRules(pl, 12, "many");
|
||||
testPluralRules(pl, 13, "many");
|
||||
testPluralRules(pl, 14, "many");
|
||||
testPluralRules(pl, 21, "many");
|
||||
testPluralRules(pl, 22, "few");
|
||||
testPluralRules(pl, 23, "few");
|
||||
testPluralRules(pl, 24, "few");
|
||||
testPluralRules(pl, 25, "many");
|
||||
testPluralRules(pl, 3.14, "other");
|
||||
|
||||
// In "am":
|
||||
// "one" is specified to be the integers 0 and 1, and non-integers whose integer part is 0.
|
||||
const am = new Intl.PluralRules("am", { type: "cardinal" });
|
||||
expect(am.select(0)).toBe("one");
|
||||
expect(am.select(0.1)).toBe("one");
|
||||
expect(am.select(0.2)).toBe("one");
|
||||
expect(am.select(0.8)).toBe("one");
|
||||
expect(am.select(0.9)).toBe("one");
|
||||
expect(am.select(1)).toBe("one");
|
||||
expect(am.select(1.1)).toBe("other");
|
||||
expect(am.select(1.9)).toBe("other");
|
||||
expect(am.select(2)).toBe("other");
|
||||
expect(am.select(3)).toBe("other");
|
||||
testPluralRules(am, 0, "one");
|
||||
testPluralRules(am, 0.1, "one");
|
||||
testPluralRules(am, 0.2, "one");
|
||||
testPluralRules(am, 0.8, "one");
|
||||
testPluralRules(am, 0.9, "one");
|
||||
testPluralRules(am, 1, "one");
|
||||
testPluralRules(am, 1.1, "other");
|
||||
testPluralRules(am, 1.9, "other");
|
||||
testPluralRules(am, 2, "other");
|
||||
testPluralRules(am, 3, "other");
|
||||
});
|
||||
|
||||
test("ordinal", () => {
|
||||
@@ -101,43 +109,43 @@ describe("correct behavior", () => {
|
||||
// "two" is specified to be integers such that (i % 10 == 2), excluding 12.
|
||||
// "few" is specified to be integers such that (i % 10 == 3), excluding 13.
|
||||
const en = new Intl.PluralRules("en", { type: "ordinal" });
|
||||
expect(en.select(0)).toBe("other");
|
||||
expect(en.select(1)).toBe("one");
|
||||
expect(en.select(2)).toBe("two");
|
||||
expect(en.select(3)).toBe("few");
|
||||
expect(en.select(4)).toBe("other");
|
||||
expect(en.select(10)).toBe("other");
|
||||
expect(en.select(11)).toBe("other");
|
||||
expect(en.select(12)).toBe("other");
|
||||
expect(en.select(13)).toBe("other");
|
||||
expect(en.select(14)).toBe("other");
|
||||
expect(en.select(20)).toBe("other");
|
||||
expect(en.select(21)).toBe("one");
|
||||
expect(en.select(22)).toBe("two");
|
||||
expect(en.select(23)).toBe("few");
|
||||
expect(en.select(24)).toBe("other");
|
||||
testPluralRules(en, 0, "other");
|
||||
testPluralRules(en, 1, "one");
|
||||
testPluralRules(en, 2, "two");
|
||||
testPluralRules(en, 3, "few");
|
||||
testPluralRules(en, 4, "other");
|
||||
testPluralRules(en, 10, "other");
|
||||
testPluralRules(en, 11, "other");
|
||||
testPluralRules(en, 12, "other");
|
||||
testPluralRules(en, 13, "other");
|
||||
testPluralRules(en, 14, "other");
|
||||
testPluralRules(en, 20, "other");
|
||||
testPluralRules(en, 21, "one");
|
||||
testPluralRules(en, 22, "two");
|
||||
testPluralRules(en, 23, "few");
|
||||
testPluralRules(en, 24, "other");
|
||||
|
||||
// In "mk":
|
||||
// "one" is specified to be integers such that (i % 10 == 1 && i % 100 != 11).
|
||||
// "two" is specified to be integers such that (i % 10 == 2 && i % 100 != 12).
|
||||
// "many" is specified to be integers such that (i % 10 == 7,8 && i % 100 != 17,18).
|
||||
const mk = new Intl.PluralRules("mk", { type: "ordinal" });
|
||||
expect(mk.select(0)).toBe("other");
|
||||
expect(mk.select(1)).toBe("one");
|
||||
expect(mk.select(2)).toBe("two");
|
||||
expect(mk.select(3)).toBe("other");
|
||||
expect(mk.select(6)).toBe("other");
|
||||
expect(mk.select(7)).toBe("many");
|
||||
expect(mk.select(8)).toBe("many");
|
||||
expect(mk.select(9)).toBe("other");
|
||||
expect(mk.select(11)).toBe("other");
|
||||
expect(mk.select(12)).toBe("other");
|
||||
expect(mk.select(17)).toBe("other");
|
||||
expect(mk.select(18)).toBe("other");
|
||||
expect(mk.select(21)).toBe("one");
|
||||
expect(mk.select(22)).toBe("two");
|
||||
expect(mk.select(27)).toBe("many");
|
||||
expect(mk.select(28)).toBe("many");
|
||||
testPluralRules(mk, 0, "other");
|
||||
testPluralRules(mk, 1, "one");
|
||||
testPluralRules(mk, 2, "two");
|
||||
testPluralRules(mk, 3, "other");
|
||||
testPluralRules(mk, 6, "other");
|
||||
testPluralRules(mk, 7, "many");
|
||||
testPluralRules(mk, 8, "many");
|
||||
testPluralRules(mk, 9, "other");
|
||||
testPluralRules(mk, 11, "other");
|
||||
testPluralRules(mk, 12, "other");
|
||||
testPluralRules(mk, 17, "other");
|
||||
testPluralRules(mk, 18, "other");
|
||||
testPluralRules(mk, 21, "one");
|
||||
testPluralRules(mk, 22, "two");
|
||||
testPluralRules(mk, 27, "many");
|
||||
testPluralRules(mk, 28, "many");
|
||||
});
|
||||
|
||||
test("notation", () => {
|
||||
@@ -154,10 +162,10 @@ describe("correct behavior", () => {
|
||||
];
|
||||
|
||||
data.forEach(d => {
|
||||
expect(standard.select(d.value)).toBe(d.standard);
|
||||
expect(engineering.select(d.value)).toBe(d.engineering);
|
||||
expect(scientific.select(d.value)).toBe(d.scientific);
|
||||
expect(compact.select(d.value)).toBe(d.compact);
|
||||
testPluralRules(standard, d.value, d.standard);
|
||||
testPluralRules(engineering, d.value, d.engineering);
|
||||
testPluralRules(scientific, d.value, d.scientific);
|
||||
testPluralRules(compact, d.value, d.compact);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user