
This proposal has been merged into the main ECMA-402 spec. See: https://github.com/tc39/ecma402/commit/4257160 Note this includes some editorial and normative changes made when the proposal was merged into the main spec, but are not in the proposal spec itself. In particular, the following AOs were changed: PartitionNumberRangePattern (normative) SetNumberFormatDigitOptions (editorial)
176 lines
6.9 KiB
C++
176 lines
6.9 KiB
C++
/*
|
|
* Copyright (c) 2022-2023, Tim Flynn <trflynn89@serenityos.org>
|
|
*
|
|
* 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 {
|
|
|
|
// 16 PluralRules Objects, https://tc39.es/ecma402/#pluralrules-objects
|
|
PluralRules::PluralRules(Object& prototype)
|
|
: NumberFormatBase(prototype)
|
|
{
|
|
}
|
|
|
|
// 16.5.1 GetOperands ( s ), https://tc39.es/ecma402/#sec-getoperands
|
|
::Locale::PluralOperands get_operands(StringView string)
|
|
{
|
|
// 1.Let n be ! ToNumber(s).
|
|
auto number = string.to_double(AK::TrimWhitespace::Yes).release_value();
|
|
|
|
// 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 ::Locale::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
|
|
::Locale::PluralCategory plural_rule_select(StringView locale, ::Locale::PluralForm type, Value, ::Locale::PluralOperands operands)
|
|
{
|
|
return ::Locale::determine_plural_category(locale, type, move(operands));
|
|
}
|
|
|
|
// 16.5.3 ResolvePlural ( pluralRules, n ), https://tc39.es/ecma402/#sec-resolveplural
|
|
ThrowCompletionOr<ResolvedPlurality> resolve_plural(VM& vm, PluralRules const& plural_rules, Value number)
|
|
{
|
|
return resolve_plural(vm, plural_rules, plural_rules.type(), number);
|
|
}
|
|
|
|
// Non-standard overload of ResolvePlural to allow using the AO without an Intl.PluralRules object.
|
|
ThrowCompletionOr<ResolvedPlurality> resolve_plural(VM& vm, NumberFormatBase const& number_format, ::Locale::PluralForm type, 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 ResolvedPlurality { ::Locale::PluralCategory::Other, String {} };
|
|
}
|
|
|
|
// 5. Let locale be pluralRules.[[Locale]].
|
|
auto const& locale = number_format.locale();
|
|
|
|
// 6. Let type be pluralRules.[[Type]].
|
|
|
|
// 7. Let res be ! FormatNumericToString(pluralRules, n).
|
|
auto result = MUST_OR_THROW_OOM(format_numeric_to_string(vm, number_format, number));
|
|
|
|
// 8. Let s be res.[[FormattedString]].
|
|
auto string = move(result.formatted_string);
|
|
|
|
// 9. Let operands be ! GetOperands(s).
|
|
auto operands = get_operands(string);
|
|
|
|
// 10. Let p be ! PluralRuleSelect(locale, type, n, operands).
|
|
auto plural_category = plural_rule_select(locale, type, number, move(operands));
|
|
|
|
// 11. Return the Record { [[PluralCategory]]: p, [[FormattedString]]: s }.
|
|
return ResolvedPlurality { plural_category, move(string) };
|
|
}
|
|
|
|
// 16.5.4 PluralRuleSelectRange ( locale, type, xp, yp ), https://tc39.es/ecma402/#sec-resolveplural
|
|
::Locale::PluralCategory plural_rule_select_range(StringView locale, ::Locale::PluralForm, ::Locale::PluralCategory start, ::Locale::PluralCategory end)
|
|
{
|
|
return ::Locale::determine_plural_range(locale, start, end);
|
|
}
|
|
|
|
// 16.5.5 ResolvePluralRange ( pluralRules, x, y ), https://tc39.es/ecma402/#sec-resolveplural
|
|
ThrowCompletionOr<::Locale::PluralCategory> resolve_plural_range(VM& vm, PluralRules const& plural_rules, Value start, Value end)
|
|
{
|
|
// 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>(ErrorType::IntlNumberIsNaN, "start"sv);
|
|
if (end.is_nan())
|
|
return vm.throw_completion<RangeError>(ErrorType::IntlNumberIsNaN, "end"sv);
|
|
|
|
// 6. Let xp be ! ResolvePlural(pluralRules, x).
|
|
auto start_plurality = MUST_OR_THROW_OOM(resolve_plural(vm, plural_rules, start));
|
|
|
|
// 7. Let yp be ! ResolvePlural(pluralRules, y).
|
|
auto end_plurality = MUST_OR_THROW_OOM(resolve_plural(vm, plural_rules, end));
|
|
|
|
// 8. If xp.[[FormattedString]] is yp.[[FormattedString]], then
|
|
if (start_plurality.formatted_string == end_plurality.formatted_string) {
|
|
// a. Return xp.[[PluralCategory]].
|
|
return start_plurality.plural_category;
|
|
}
|
|
|
|
// 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.[[PluralCategory]], yp.[[PluralCategory]]).
|
|
return plural_rule_select_range(locale, type, start_plurality.plural_category, end_plurality.plural_category);
|
|
}
|
|
|
|
}
|