/src/serenity/Userland/Libraries/LibJS/Runtime/Intl/PluralRules.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2022-2023, Tim Flynn <trflynn89@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <AK/Variant.h> |
8 | | #include <LibJS/Runtime/Intl/PluralRules.h> |
9 | | #include <math.h> |
10 | | #include <stdlib.h> |
11 | | |
12 | | namespace JS::Intl { |
13 | | |
14 | | JS_DEFINE_ALLOCATOR(PluralRules); |
15 | | |
16 | | // 16 PluralRules Objects, https://tc39.es/ecma402/#pluralrules-objects |
17 | | PluralRules::PluralRules(Object& prototype) |
18 | 0 | : NumberFormatBase(prototype) |
19 | 0 | { |
20 | 0 | } |
21 | | |
22 | | // 16.5.1 GetOperands ( s ), https://tc39.es/ecma402/#sec-getoperands |
23 | | ::Locale::PluralOperands get_operands(StringView string) |
24 | 0 | { |
25 | | // 1.Let n be ! ToNumber(s). |
26 | 0 | auto number = string.to_number<double>(AK::TrimWhitespace::Yes).release_value(); |
27 | | |
28 | | // 2. Assert: n is finite. |
29 | 0 | VERIFY(isfinite(number)); |
30 | | |
31 | | // 3. Let dp be StringIndexOf(s, ".", 0). |
32 | 0 | auto decimal_point = string.find('.'); |
33 | |
|
34 | 0 | Variant<Empty, double, StringView> integer_part; |
35 | 0 | StringView fraction_slice; |
36 | | |
37 | | // 4. If dp = -1, then |
38 | 0 | if (!decimal_point.has_value()) { |
39 | | // a. Let intPart be n. |
40 | 0 | integer_part = number; |
41 | | |
42 | | // b. Let fracSlice be "". |
43 | 0 | } |
44 | | // 5. Else, |
45 | 0 | else { |
46 | | // a. Let intPart be the substring of s from 0 to dp. |
47 | 0 | integer_part = string.substring_view(0, *decimal_point); |
48 | | |
49 | | // b. Let fracSlice be the substring of s from dp + 1. |
50 | 0 | fraction_slice = string.substring_view(*decimal_point + 1); |
51 | 0 | } |
52 | | |
53 | | // 6. Let i be abs(! ToNumber(intPart)). |
54 | 0 | auto integer = integer_part.visit( |
55 | 0 | [](Empty) -> u64 { VERIFY_NOT_REACHED(); }, |
56 | 0 | [](double value) { |
57 | 0 | return static_cast<u64>(fabs(value)); |
58 | 0 | }, |
59 | 0 | [](StringView value) { |
60 | 0 | auto value_as_int = value.template to_number<i64>().value(); |
61 | 0 | return static_cast<u64>(value_as_int); |
62 | 0 | }); |
63 | | |
64 | | // 7. Let fracDigitCount be the length of fracSlice. |
65 | 0 | auto fraction_digit_count = fraction_slice.length(); |
66 | | |
67 | | // 8. Let f be ! ToNumber(fracSlice). |
68 | 0 | auto fraction = fraction_slice.is_empty() ? 0u : fraction_slice.template to_number<u64>().value(); |
69 | | |
70 | | // 9. Let significantFracSlice be the value of fracSlice stripped of trailing "0". |
71 | 0 | auto significant_fraction_slice = fraction_slice.trim("0"sv, TrimMode::Right); |
72 | | |
73 | | // 10. Let significantFracDigitCount be the length of significantFracSlice. |
74 | 0 | auto significant_fraction_digit_count = significant_fraction_slice.length(); |
75 | | |
76 | | // 11. Let significantFrac be ! ToNumber(significantFracSlice). |
77 | 0 | auto significant_fraction = significant_fraction_slice.is_empty() ? 0u : significant_fraction_slice.template to_number<u64>().value(); |
78 | | |
79 | | // 12. Return a new Record { [[Number]]: abs(n), [[IntegerDigits]]: i, [[FractionDigits]]: f, [[NumberOfFractionDigits]]: fracDigitCount, [[FractionDigitsWithoutTrailing]]: significantFrac, [[NumberOfFractionDigitsWithoutTrailing]]: significantFracDigitCount }. |
80 | 0 | return ::Locale::PluralOperands { |
81 | 0 | .number = fabs(number), |
82 | 0 | .integer_digits = integer, |
83 | 0 | .fraction_digits = fraction, |
84 | 0 | .number_of_fraction_digits = fraction_digit_count, |
85 | 0 | .fraction_digits_without_trailing = significant_fraction, |
86 | 0 | .number_of_fraction_digits_without_trailing = significant_fraction_digit_count, |
87 | 0 | }; |
88 | 0 | } |
89 | | |
90 | | // 16.5.2 PluralRuleSelect ( locale, type, n, operands ), https://tc39.es/ecma402/#sec-pluralruleselect |
91 | | ::Locale::PluralCategory plural_rule_select(StringView locale, ::Locale::PluralForm type, Value, ::Locale::PluralOperands operands) |
92 | 0 | { |
93 | 0 | return ::Locale::determine_plural_category(locale, type, move(operands)); |
94 | 0 | } |
95 | | |
96 | | // 16.5.3 ResolvePlural ( pluralRules, n ), https://tc39.es/ecma402/#sec-resolveplural |
97 | | ResolvedPlurality resolve_plural(PluralRules const& plural_rules, Value number) |
98 | 0 | { |
99 | 0 | return resolve_plural(plural_rules, plural_rules.type(), number); |
100 | 0 | } |
101 | | |
102 | | // Non-standard overload of ResolvePlural to allow using the AO without an Intl.PluralRules object. |
103 | | ResolvedPlurality resolve_plural(NumberFormatBase const& number_format, ::Locale::PluralForm type, Value number) |
104 | 0 | { |
105 | | // 1. Assert: Type(pluralRules) is Object. |
106 | | // 2. Assert: pluralRules has an [[InitializedPluralRules]] internal slot. |
107 | | // 3. Assert: Type(n) is Number. |
108 | | |
109 | | // 4. If n is not a finite Number, then |
110 | 0 | if (!number.is_finite_number()) { |
111 | | // a. Return "other". |
112 | 0 | return { ::Locale::PluralCategory::Other, String {} }; |
113 | 0 | } |
114 | | |
115 | | // 5. Let locale be pluralRules.[[Locale]]. |
116 | 0 | auto const& locale = number_format.locale(); |
117 | | |
118 | | // 6. Let type be pluralRules.[[Type]]. |
119 | | |
120 | | // 7. Let res be ! FormatNumericToString(pluralRules, n). |
121 | 0 | auto result = format_numeric_to_string(number_format, number); |
122 | | |
123 | | // 8. Let s be res.[[FormattedString]]. |
124 | 0 | auto string = move(result.formatted_string); |
125 | | |
126 | | // 9. Let operands be ! GetOperands(s). |
127 | 0 | auto operands = get_operands(string); |
128 | | |
129 | | // 10. Let p be ! PluralRuleSelect(locale, type, n, operands). |
130 | 0 | auto plural_category = plural_rule_select(locale, type, number, move(operands)); |
131 | | |
132 | | // 11. Return the Record { [[PluralCategory]]: p, [[FormattedString]]: s }. |
133 | 0 | return { plural_category, move(string) }; |
134 | 0 | } |
135 | | |
136 | | // 16.5.4 PluralRuleSelectRange ( locale, type, xp, yp ), https://tc39.es/ecma402/#sec-resolveplural |
137 | | ::Locale::PluralCategory plural_rule_select_range(StringView locale, ::Locale::PluralForm, ::Locale::PluralCategory start, ::Locale::PluralCategory end) |
138 | 0 | { |
139 | 0 | return ::Locale::determine_plural_range(locale, start, end); |
140 | 0 | } |
141 | | |
142 | | // 16.5.5 ResolvePluralRange ( pluralRules, x, y ), https://tc39.es/ecma402/#sec-resolveplural |
143 | | ThrowCompletionOr<::Locale::PluralCategory> resolve_plural_range(VM& vm, PluralRules const& plural_rules, Value start, Value end) |
144 | 0 | { |
145 | | // 1. Assert: Type(pluralRules) is Object. |
146 | | // 2. Assert: pluralRules has an [[InitializedPluralRules]] internal slot. |
147 | | // 3. Assert: Type(x) is Number. |
148 | | // 4. Assert: Type(y) is Number. |
149 | | |
150 | | // 5. If x is NaN or y is NaN, throw a RangeError exception. |
151 | 0 | if (start.is_nan()) |
152 | 0 | return vm.throw_completion<RangeError>(ErrorType::NumberIsNaN, "start"sv); |
153 | 0 | if (end.is_nan()) |
154 | 0 | return vm.throw_completion<RangeError>(ErrorType::NumberIsNaN, "end"sv); |
155 | | |
156 | | // 6. Let xp be ! ResolvePlural(pluralRules, x). |
157 | 0 | auto start_plurality = resolve_plural(plural_rules, start); |
158 | | |
159 | | // 7. Let yp be ! ResolvePlural(pluralRules, y). |
160 | 0 | auto end_plurality = resolve_plural(plural_rules, end); |
161 | | |
162 | | // 8. If xp.[[FormattedString]] is yp.[[FormattedString]], then |
163 | 0 | if (start_plurality.formatted_string == end_plurality.formatted_string) { |
164 | | // a. Return xp.[[PluralCategory]]. |
165 | 0 | return start_plurality.plural_category; |
166 | 0 | } |
167 | | |
168 | | // 9. Let locale be pluralRules.[[Locale]]. |
169 | 0 | auto const& locale = plural_rules.locale(); |
170 | | |
171 | | // 10. Let type be pluralRules.[[Type]]. |
172 | 0 | auto type = plural_rules.type(); |
173 | | |
174 | | // 11. Return ! PluralRuleSelectRange(locale, type, xp.[[PluralCategory]], yp.[[PluralCategory]]). |
175 | 0 | return plural_rule_select_range(locale, type, start_plurality.plural_category, end_plurality.plural_category); |
176 | 0 | } |
177 | | |
178 | | } |