mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-21 12:05:15 +00:00
LibJS: Implement Intl.PluralRules.prototype.select
This commit is contained in:
parent
f11cb7c075
commit
36abcd820d
Notes:
sideshowbarker
2024-07-17 10:10:18 +09:00
Author: https://github.com/trflynn89 Commit: https://github.com/SerenityOS/serenity/commit/36abcd820d Pull-request: https://github.com/SerenityOS/serenity/pull/14512 Reviewed-by: https://github.com/IdanHo Reviewed-by: https://github.com/alimpfard Reviewed-by: https://github.com/linusg
6 changed files with 270 additions and 0 deletions
|
@ -408,6 +408,7 @@ namespace JS {
|
|||
P(seconds) \
|
||||
P(secondsDisplay) \
|
||||
P(segment) \
|
||||
P(select) \
|
||||
P(sensitivity) \
|
||||
P(set) \
|
||||
P(setBigInt64) \
|
||||
|
|
|
@ -4,7 +4,10 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Variant.h>
|
||||
#include <LibJS/Runtime/Intl/PluralRules.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace JS::Intl {
|
||||
|
||||
|
@ -14,4 +17,112 @@ PluralRules::PluralRules(Object& prototype)
|
|||
{
|
||||
}
|
||||
|
||||
// 16.5.1 GetOperands ( s ), https://tc39.es/ecma402/#sec-getoperands
|
||||
Unicode::PluralOperands get_operands(String const& string)
|
||||
{
|
||||
// 1.Let n be ! ToNumber(s).
|
||||
char* end { nullptr };
|
||||
auto number = strtod(string.characters(), &end);
|
||||
VERIFY(!*end);
|
||||
|
||||
// 2. Assert: n is finite.
|
||||
VERIFY(isfinite(number));
|
||||
|
||||
// 3. Let dp be StringIndexOf(s, ".", 0).
|
||||
auto decimal_point = string.find('.');
|
||||
|
||||
Variant<Empty, double, StringView> integer_part;
|
||||
StringView fraction_slice;
|
||||
|
||||
// 4. If dp = -1, then
|
||||
if (!decimal_point.has_value()) {
|
||||
// a. Let intPart be n.
|
||||
integer_part = number;
|
||||
|
||||
// b. Let fracSlice be "".
|
||||
}
|
||||
// 5. Else,
|
||||
else {
|
||||
// a. Let intPart be the substring of s from 0 to dp.
|
||||
integer_part = string.substring_view(0, *decimal_point);
|
||||
|
||||
// b. Let fracSlice be the substring of s from dp + 1.
|
||||
fraction_slice = string.substring_view(*decimal_point + 1);
|
||||
}
|
||||
|
||||
// 6. Let i be abs(! ToNumber(intPart)).
|
||||
auto integer = integer_part.visit(
|
||||
[](Empty) -> u64 { VERIFY_NOT_REACHED(); },
|
||||
[](double value) {
|
||||
return static_cast<u64>(fabs(value));
|
||||
},
|
||||
[](StringView value) {
|
||||
auto value_as_int = value.template to_int<i64>().value();
|
||||
return static_cast<u64>(value_as_int);
|
||||
});
|
||||
|
||||
// 7. Let fracDigitCount be the length of fracSlice.
|
||||
auto fraction_digit_count = fraction_slice.length();
|
||||
|
||||
// 8. Let f be ! ToNumber(fracSlice).
|
||||
auto fraction = fraction_slice.is_empty() ? 0u : fraction_slice.template to_uint<u64>().value();
|
||||
|
||||
// 9. Let significantFracSlice be the value of fracSlice stripped of trailing "0".
|
||||
auto significant_fraction_slice = fraction_slice.trim("0"sv, TrimMode::Right);
|
||||
|
||||
// 10. Let significantFracDigitCount be the length of significantFracSlice.
|
||||
auto significant_fraction_digit_count = significant_fraction_slice.length();
|
||||
|
||||
// 11. Let significantFrac be ! ToNumber(significantFracSlice).
|
||||
auto significant_fraction = significant_fraction_slice.is_empty() ? 0u : significant_fraction_slice.template to_uint<u64>().value();
|
||||
|
||||
// 12. Return a new Record { [[Number]]: abs(n), [[IntegerDigits]]: i, [[FractionDigits]]: f, [[NumberOfFractionDigits]]: fracDigitCount, [[FractionDigitsWithoutTrailing]]: significantFrac, [[NumberOfFractionDigitsWithoutTrailing]]: significantFracDigitCount }.
|
||||
return Unicode::PluralOperands {
|
||||
.number = fabs(number),
|
||||
.integer_digits = integer,
|
||||
.fraction_digits = fraction,
|
||||
.number_of_fraction_digits = fraction_digit_count,
|
||||
.fraction_digits_without_trailing = significant_fraction,
|
||||
.number_of_fraction_digits_without_trailing = significant_fraction_digit_count,
|
||||
};
|
||||
}
|
||||
|
||||
// 16.5.2 PluralRuleSelect ( locale, type, n, operands ), https://tc39.es/ecma402/#sec-pluralruleselect
|
||||
Unicode::PluralCategory plural_rule_select(StringView locale, Unicode::PluralForm type, Value, Unicode::PluralOperands operands)
|
||||
{
|
||||
return Unicode::determine_plural_category(locale, type, move(operands));
|
||||
}
|
||||
|
||||
// 16.5.3 ResolvePlural ( pluralRules, n ), https://tc39.es/ecma402/#sec-resolveplural
|
||||
Unicode::PluralCategory resolve_plural(GlobalObject& global_object, PluralRules const& plural_rules, Value number)
|
||||
{
|
||||
// 1. Assert: Type(pluralRules) is Object.
|
||||
// 2. Assert: pluralRules has an [[InitializedPluralRules]] internal slot.
|
||||
// 3. Assert: Type(n) is Number.
|
||||
|
||||
// 4. If n is not a finite Number, then
|
||||
if (!number.is_finite_number()) {
|
||||
// a. Return "other".
|
||||
return Unicode::plural_category_from_string("other"sv).value();
|
||||
}
|
||||
|
||||
// 5. Let locale be pluralRules.[[Locale]].
|
||||
auto const& locale = plural_rules.locale();
|
||||
|
||||
// 6. Let type be pluralRules.[[Type]].
|
||||
auto type = plural_rules.type();
|
||||
|
||||
// 7. Let res be ! FormatNumericToString(pluralRules, n).
|
||||
auto result = format_numeric_to_string(global_object, plural_rules, number);
|
||||
|
||||
// 8. Let s be res.[[FormattedString]].
|
||||
auto const& string = result.formatted_string;
|
||||
|
||||
// 9. Let operands be ! GetOperands(s).
|
||||
auto operands = get_operands(string);
|
||||
|
||||
// 10. Return ! PluralRuleSelect(locale, type, n, operands).
|
||||
return plural_rule_select(locale, type, number, move(operands));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -29,4 +29,8 @@ private:
|
|||
Unicode::PluralForm m_type { Unicode::PluralForm::Cardinal }; // [[Type]]
|
||||
};
|
||||
|
||||
Unicode::PluralOperands get_operands(String const& string);
|
||||
Unicode::PluralCategory plural_rule_select(StringView locale, Unicode::PluralForm type, Value number, Unicode::PluralOperands operands);
|
||||
Unicode::PluralCategory resolve_plural(GlobalObject& global_object, PluralRules const& plural_rules, Value number);
|
||||
|
||||
}
|
||||
|
|
|
@ -28,9 +28,25 @@ void PluralRulesPrototype::initialize(GlobalObject& global_object)
|
|||
define_direct_property(*vm.well_known_symbol_to_string_tag(), js_string(vm, "Intl.PluralRules"sv), Attribute::Configurable);
|
||||
|
||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
define_native_function(vm.names.select, select, 1, attr);
|
||||
define_native_function(vm.names.resolvedOptions, resolved_options, 0, attr);
|
||||
}
|
||||
|
||||
// 16.3.3 Intl.PluralRules.prototype.select ( value ), https://tc39.es/ecma402/#sec-intl.pluralrules.prototype.select
|
||||
JS_DEFINE_NATIVE_FUNCTION(PluralRulesPrototype::select)
|
||||
{
|
||||
// 1. Let pr be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(pr, [[InitializedPluralRules]]).
|
||||
auto* plural_rules = TRY(typed_this_object(global_object));
|
||||
|
||||
// 3. Let n be ? ToNumber(value).
|
||||
auto number = TRY(vm.argument(0).to_number(global_object));
|
||||
|
||||
// 4. Return ! ResolvePlural(pr, n).
|
||||
auto plurality = resolve_plural(global_object, *plural_rules, number);
|
||||
return js_string(vm, Unicode::plural_category_to_string(plurality));
|
||||
}
|
||||
|
||||
// 16.3.4 Intl.PluralRules.prototype.resolvedOptions ( ), https://tc39.es/ecma402/#sec-intl.pluralrules.prototype.resolvedoptions
|
||||
JS_DEFINE_NATIVE_FUNCTION(PluralRulesPrototype::resolved_options)
|
||||
{
|
||||
|
|
|
@ -20,6 +20,7 @@ public:
|
|||
virtual ~PluralRulesPrototype() override = default;
|
||||
|
||||
private:
|
||||
JS_DECLARE_NATIVE_FUNCTION(select);
|
||||
JS_DECLARE_NATIVE_FUNCTION(resolved_options);
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
describe("errors", () => {
|
||||
test("called on non-PluralRules object", () => {
|
||||
expect(() => {
|
||||
Intl.PluralRules.prototype.select();
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Intl.PluralRules");
|
||||
});
|
||||
|
||||
test("called with value that cannot be converted to a number", () => {
|
||||
expect(() => {
|
||||
new Intl.PluralRules().select(Symbol.hasInstance);
|
||||
}).toThrowWithMessage(TypeError, "Cannot convert symbol to number");
|
||||
});
|
||||
});
|
||||
|
||||
describe("non-finite values", () => {
|
||||
test("NaN", () => {
|
||||
expect(new Intl.PluralRules("en").select(NaN)).toBe("other");
|
||||
expect(new Intl.PluralRules("ar").select(NaN)).toBe("other");
|
||||
expect(new Intl.PluralRules("pl").select(NaN)).toBe("other");
|
||||
});
|
||||
|
||||
test("Infinity", () => {
|
||||
expect(new Intl.PluralRules("en").select(Infinity)).toBe("other");
|
||||
expect(new Intl.PluralRules("ar").select(Infinity)).toBe("other");
|
||||
expect(new Intl.PluralRules("pl").select(Infinity)).toBe("other");
|
||||
});
|
||||
|
||||
test("-Infinity", () => {
|
||||
expect(new Intl.PluralRules("en").select(-Infinity)).toBe("other");
|
||||
expect(new Intl.PluralRules("ar").select(-Infinity)).toBe("other");
|
||||
expect(new Intl.PluralRules("pl").select(-Infinity)).toBe("other");
|
||||
});
|
||||
});
|
||||
|
||||
describe("correct behavior", () => {
|
||||
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");
|
||||
|
||||
// In "he":
|
||||
// "many" is specified to be integers larger than 10 which are multiples of 10.
|
||||
const he = new Intl.PluralRules("he", { type: "cardinal" });
|
||||
expect(he.select(0)).toBe("other");
|
||||
expect(he.select(1)).toBe("one");
|
||||
expect(en.select(2)).toBe("other");
|
||||
expect(he.select(10)).toBe("other");
|
||||
expect(he.select(19)).toBe("other");
|
||||
expect(he.select(20)).toBe("many");
|
||||
expect(he.select(21)).toBe("other");
|
||||
expect(he.select(29)).toBe("other");
|
||||
expect(he.select(30)).toBe("many");
|
||||
expect(he.select(31)).toBe("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");
|
||||
|
||||
// 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");
|
||||
});
|
||||
|
||||
test("ordinal", () => {
|
||||
// In "en":
|
||||
// "one" is specified to be integers such that (i % 10 == 1), excluding 11.
|
||||
// "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");
|
||||
|
||||
// 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");
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue