diff options
author | Timothy Flynn <trflynn89@pm.me> | 2022-07-11 11:28:10 -0400 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2022-07-12 00:43:34 +0100 |
commit | f089c11b5bc8365dcc87f9f6faf13c5dde121896 (patch) | |
tree | 18f4c37e3194009af85fcca6d08e899862a8187a /Userland | |
parent | a337b059ddf69d1611e59e3863912d1695f47ea7 (diff) | |
download | serenity-f089c11b5bc8365dcc87f9f6faf13c5dde121896.zip |
LibJS: Implement Intl.PluralRules.prototype.selectRange
Diffstat (limited to 'Userland')
7 files changed, 150 insertions, 0 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index 46e0dbaf2b..3f31a1bef4 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -409,6 +409,7 @@ namespace JS { P(secondsDisplay) \ P(segment) \ P(select) \ + P(selectRange) \ P(sensitivity) \ P(set) \ P(setBigInt64) \ diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index bb9698990e..f97f323626 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -43,8 +43,10 @@ M(IntlInvalidLanguageTag, "{} is not a structurally valid language tag") \ M(IntlInvalidTime, "Time value must be between -8.64E15 and 8.64E15") \ M(IntlInvalidUnit, "Unit {} is not a valid time unit") \ + M(IntlStartRangeAfterEndRange, "Range start {} is greater than range end {}") \ M(IntlStartTimeAfterEndTime, "Start time {} is after end time {}") \ M(IntlMinimumExceedsMaximum, "Minimum value {} is larger than maximum value {}") \ + M(IntlNumberIsNaN, "{} must not be NaN") \ M(IntlNumberIsNaNOrInfinity, "Number must not be NaN or Infinity") \ M(IntlNumberIsNaNOrOutOfRange, "Value {} is NaN or is not between {} and {}") \ M(IntlOptionUndefined, "Option {} must be defined when option {} is {}") \ diff --git a/Userland/Libraries/LibJS/Runtime/Intl/PluralRules.cpp b/Userland/Libraries/LibJS/Runtime/Intl/PluralRules.cpp index 0abb1ed2bd..fd6a86a0e6 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/PluralRules.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/PluralRules.cpp @@ -130,4 +130,46 @@ Unicode::PluralCategory resolve_plural(GlobalObject& global_object, NumberFormat return plural_rule_select(locale, type, number, move(operands)); } +// 1.1.5 PluralRuleSelectRange ( locale, type, xp, yp ), https://tc39.es/proposal-intl-numberformat-v3/out/pluralrules/proposed.html#sec-pluralruleselectrange +Unicode::PluralCategory plural_rule_select_range(StringView locale, Unicode::PluralForm, Unicode::PluralCategory start, Unicode::PluralCategory end) +{ + return Unicode::determine_plural_range(locale, start, end); +} + +// 1.1.6 ResolvePluralRange ( pluralRules, x, y ), https://tc39.es/proposal-intl-numberformat-v3/out/pluralrules/proposed.html#sec-resolvepluralrange +ThrowCompletionOr<Unicode::PluralCategory> resolve_plural_range(GlobalObject& global_object, PluralRules const& plural_rules, Value start, Value end) +{ + auto& vm = global_object.vm(); + + // 1. Assert: Type(pluralRules) is Object. + // 2. Assert: pluralRules has an [[InitializedPluralRules]] internal slot. + // 3. Assert: Type(x) is Number. + // 4. Assert: Type(y) is Number. + + // 5. If x is NaN or y is NaN, throw a RangeError exception. + if (start.is_nan()) + return vm.throw_completion<RangeError>(global_object, ErrorType::IntlNumberIsNaN, "start"sv); + if (end.is_nan()) + return vm.throw_completion<RangeError>(global_object, ErrorType::IntlNumberIsNaN, "end"sv); + + // 6. If x > y, throw a RangeError exception. + if (start.as_double() > end.as_double()) + return vm.throw_completion<RangeError>(global_object, ErrorType::IntlStartRangeAfterEndRange, start, end); + + // 7. Let xp be ! ResolvePlural(pluralRules, x). + auto start_plurality = resolve_plural(global_object, plural_rules, start); + + // 8. Let yp be ! ResolvePlural(pluralRules, y). + auto end_plurality = resolve_plural(global_object, plural_rules, end); + + // 9. Let locale be pluralRules.[[Locale]]. + auto const& locale = plural_rules.locale(); + + // 10. Let type be pluralRules.[[Type]]. + auto type = plural_rules.type(); + + // 11. Return ! PluralRuleSelectRange(locale, type, xp, yp). + return plural_rule_select_range(locale, type, start_plurality, end_plurality); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/PluralRules.h b/Userland/Libraries/LibJS/Runtime/Intl/PluralRules.h index 1c11b5087a..f8ce0d1c24 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/PluralRules.h +++ b/Userland/Libraries/LibJS/Runtime/Intl/PluralRules.h @@ -8,6 +8,7 @@ #include <AK/String.h> #include <AK/StringView.h> +#include <LibJS/Runtime/Completion.h> #include <LibJS/Runtime/Intl/NumberFormat.h> #include <LibJS/Runtime/Object.h> #include <LibUnicode/PluralRules.h> @@ -33,5 +34,7 @@ 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); Unicode::PluralCategory resolve_plural(GlobalObject& global_object, NumberFormatBase const& number_format, Unicode::PluralForm type, Value number); +Unicode::PluralCategory plural_rule_select_range(StringView locale, Unicode::PluralForm, Unicode::PluralCategory start, Unicode::PluralCategory end); +ThrowCompletionOr<Unicode::PluralCategory> resolve_plural_range(GlobalObject& global_object, PluralRules const& plural_rules, Value start, Value end); } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/PluralRulesPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Intl/PluralRulesPrototype.cpp index fe2241c695..ccc272b64c 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/PluralRulesPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/PluralRulesPrototype.cpp @@ -29,6 +29,7 @@ void PluralRulesPrototype::initialize(GlobalObject& global_object) u8 attr = Attribute::Writable | Attribute::Configurable; define_native_function(vm.names.select, select, 1, attr); + define_native_function(vm.names.selectRange, select_range, 2, attr); define_native_function(vm.names.resolvedOptions, resolved_options, 0, attr); } @@ -47,6 +48,33 @@ JS_DEFINE_NATIVE_FUNCTION(PluralRulesPrototype::select) return js_string(vm, Unicode::plural_category_to_string(plurality)); } +// 1.4.4 Intl.PluralRules.prototype.selectRange ( start, end ), https://tc39.es/proposal-intl-numberformat-v3/out/pluralrules/proposed.html#sec-intl.pluralrules.prototype.selectrange +JS_DEFINE_NATIVE_FUNCTION(PluralRulesPrototype::select_range) +{ + auto start = vm.argument(0); + auto end = vm.argument(1); + + // 1. Let pr be the this value. + // 2. Perform ? RequireInternalSlot(pr, [[InitializedPluralRules]]). + auto* plural_rules = TRY(typed_this_object(global_object)); + + // 3. If start is undefined or end is undefined, throw a TypeError exception. + if (start.is_undefined()) + return vm.throw_completion<TypeError>(global_object, ErrorType::IsUndefined, "start"sv); + if (end.is_undefined()) + return vm.throw_completion<TypeError>(global_object, ErrorType::IsUndefined, "end"sv); + + // 4. Let x be ? ToNumber(start). + auto x = TRY(start.to_number(global_object)); + + // 5. Let y be ? ToNumber(end). + auto y = TRY(end.to_number(global_object)); + + // 6. Return ? ResolvePluralRange(pr, x, y). + auto plurality = TRY(resolve_plural_range(global_object, *plural_rules, x, y)); + 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) { diff --git a/Userland/Libraries/LibJS/Runtime/Intl/PluralRulesPrototype.h b/Userland/Libraries/LibJS/Runtime/Intl/PluralRulesPrototype.h index dca4d31e69..298dbfae8e 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/PluralRulesPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/Intl/PluralRulesPrototype.h @@ -21,6 +21,7 @@ public: private: JS_DECLARE_NATIVE_FUNCTION(select); + JS_DECLARE_NATIVE_FUNCTION(select_range); JS_DECLARE_NATIVE_FUNCTION(resolved_options); }; diff --git a/Userland/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.prototype.selectRange.js b/Userland/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.prototype.selectRange.js new file mode 100644 index 0000000000..bea135ea6e --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.prototype.selectRange.js @@ -0,0 +1,73 @@ +describe("errors", () => { + test("called on non-PluralRules object", () => { + expect(() => { + Intl.PluralRules.prototype.selectRange(); + }).toThrowWithMessage(TypeError, "Not an object of type Intl.PluralRules"); + }); + + test("called without enough values", () => { + expect(() => { + new Intl.PluralRules().selectRange(); + }).toThrowWithMessage(TypeError, "start is undefined"); + + expect(() => { + new Intl.PluralRules().selectRange(1); + }).toThrowWithMessage(TypeError, "end is undefined"); + }); + + test("called with values that cannot be converted to numbers", () => { + expect(() => { + new Intl.PluralRules().selectRange(Symbol.hasInstance, 1); + }).toThrowWithMessage(TypeError, "Cannot convert symbol to number"); + + expect(() => { + new Intl.PluralRules().selectRange(1, Symbol.hasInstance); + }).toThrowWithMessage(TypeError, "Cannot convert symbol to number"); + }); + + test("called with invalid numbers", () => { + expect(() => { + new Intl.PluralRules().selectRange(NaN, 1); + }).toThrowWithMessage(RangeError, "start must not be NaN"); + + expect(() => { + new Intl.PluralRules().selectRange(1, NaN); + }).toThrowWithMessage(RangeError, "end must not be NaN"); + + expect(() => { + new Intl.PluralRules().selectRange(1, 0); + }).toThrowWithMessage(RangeError, "Range start 1 is greater than range end 0"); + }); +}); + +describe("correct behavior", () => { + test("basic functionality", () => { + const en = new Intl.PluralRules("en"); + expect(en.selectRange(1, 2)).toBe("other"); // one + other = other + expect(en.selectRange(0, 1)).toBe("other"); // other + one = other + expect(en.selectRange(2, 3)).toBe("other"); // other + other = other + + const pl = new Intl.PluralRules("pl"); + expect(pl.selectRange(1, 2)).toBe("few"); // one + few = few + expect(pl.selectRange(1, 5)).toBe("many"); // one + many = many + expect(pl.selectRange(1, 3.14)).toBe("other"); // one + other = other + expect(pl.selectRange(2, 2)).toBe("few"); // few + few = few + expect(pl.selectRange(2, 5)).toBe("many"); // few + many = many + expect(pl.selectRange(2, 3.14)).toBe("other"); // few + other = other + expect(pl.selectRange(0, 1)).toBe("one"); // many + one = one + expect(pl.selectRange(0, 2)).toBe("few"); // many + few = few + expect(pl.selectRange(0, 5)).toBe("many"); // many + many = many + expect(pl.selectRange(0, 3.14)).toBe("other"); // many + other = other + expect(pl.selectRange(0.14, 1)).toBe("one"); // other + one = one + expect(pl.selectRange(0.14, 2)).toBe("few"); // other + few = few + expect(pl.selectRange(0.14, 5)).toBe("many"); // other + many = many + expect(pl.selectRange(0.14, 3.14)).toBe("other"); // other + other = other + }); + + test("default to end of range", () => { + // "so" specifies "one" to be the integer 1, but does not specify any ranges. + const so = new Intl.PluralRules("so"); + expect(so.selectRange(0, 1)).toBe("one"); + expect(so.selectRange(1, 2)).toBe("other"); + }); +}); |