summaryrefslogtreecommitdiff
path: root/Userland/Libraries
diff options
context:
space:
mode:
authorTimothy Flynn <trflynn89@pm.me>2022-07-11 11:28:10 -0400
committerLinus Groh <mail@linusgroh.de>2022-07-12 00:43:34 +0100
commitf089c11b5bc8365dcc87f9f6faf13c5dde121896 (patch)
tree18f4c37e3194009af85fcca6d08e899862a8187a /Userland/Libraries
parenta337b059ddf69d1611e59e3863912d1695f47ea7 (diff)
downloadserenity-f089c11b5bc8365dcc87f9f6faf13c5dde121896.zip
LibJS: Implement Intl.PluralRules.prototype.selectRange
Diffstat (limited to 'Userland/Libraries')
-rw-r--r--Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h1
-rw-r--r--Userland/Libraries/LibJS/Runtime/ErrorTypes.h2
-rw-r--r--Userland/Libraries/LibJS/Runtime/Intl/PluralRules.cpp42
-rw-r--r--Userland/Libraries/LibJS/Runtime/Intl/PluralRules.h3
-rw-r--r--Userland/Libraries/LibJS/Runtime/Intl/PluralRulesPrototype.cpp28
-rw-r--r--Userland/Libraries/LibJS/Runtime/Intl/PluralRulesPrototype.h1
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Intl/PluralRules/PluralRules.prototype.selectRange.js73
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");
+ });
+});