mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-09 01:29:17 +00:00
LibJS+LibLocale: Begin replacing number formatting with ICU
This uses ICU for the Intl.NumberFormat `format` and `formatToParts` prototypes. It does not yet port the range formatter prototypes. Most of the new code in LibLocale/NumberFormat is simply mapping from ECMA-402 types to ICU types. Beyond that, the only algorithmic change is that we have to mutate the output from ICU for `formatToParts` to match what is expected by ECMA-402. This is explained in NumberFormat.cpp in `flatten_partitions`. This lets us remove most data from our number format generator. All that remains are numbering system digits and symbols, which are relied upon still for other interfaces (e.g. Intl.DateTimeFormat). So they will be removed in a future patch. Note: All of the changes to the test files in this patch are now aligned with both Chrome and Safari.
This commit is contained in:
parent
cbd566a354
commit
67f3de2320
Notes:
sideshowbarker
2024-07-17 08:45:34 +09:00
Author: https://github.com/trflynn89
Commit: 67f3de2320
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/124
24 changed files with 1168 additions and 2966 deletions
|
@ -24,9 +24,6 @@ set(CLDR_LOCALES_PATH "${CLDR_PATH}/${CLDR_LOCALES_SOURCE}")
|
|||
set(CLDR_NUMBERS_SOURCE cldr-numbers-modern)
|
||||
set(CLDR_NUMBERS_PATH "${CLDR_PATH}/${CLDR_NUMBERS_SOURCE}")
|
||||
|
||||
set(CLDR_UNITS_SOURCE cldr-units-modern)
|
||||
set(CLDR_UNITS_PATH "${CLDR_PATH}/${CLDR_UNITS_SOURCE}")
|
||||
|
||||
if (ENABLE_UNICODE_DATABASE_DOWNLOAD)
|
||||
remove_path_if_version_changed("${CLDR_VERSION}" "${CLDR_VERSION_FILE}" "${CLDR_PATH}")
|
||||
|
||||
|
@ -37,7 +34,6 @@ if (ENABLE_UNICODE_DATABASE_DOWNLOAD)
|
|||
extract_path("${CLDR_PATH}" "${CLDR_ZIP_PATH}" "${CLDR_DATES_SOURCE}/**" "${CLDR_DATES_PATH}")
|
||||
extract_path("${CLDR_PATH}" "${CLDR_ZIP_PATH}" "${CLDR_LOCALES_SOURCE}/**" "${CLDR_LOCALES_PATH}")
|
||||
extract_path("${CLDR_PATH}" "${CLDR_ZIP_PATH}" "${CLDR_NUMBERS_SOURCE}/**" "${CLDR_NUMBERS_PATH}")
|
||||
extract_path("${CLDR_PATH}" "${CLDR_ZIP_PATH}" "${CLDR_UNITS_SOURCE}/**" "${CLDR_UNITS_PATH}")
|
||||
else()
|
||||
message(STATUS "Skipping download of ${CLDR_ZIP_URL}, expecting the archive to have been extracted to ${CLDR_PATH}")
|
||||
endif()
|
||||
|
@ -79,7 +75,7 @@ if (ENABLE_UNICODE_DATABASE_DOWNLOAD)
|
|||
"${CLDR_VERSION_FILE}"
|
||||
"${NUMBER_FORMAT_DATA_HEADER}"
|
||||
"${NUMBER_FORMAT_DATA_IMPLEMENTATION}"
|
||||
arguments -r "${CLDR_CORE_PATH}" -n "${CLDR_NUMBERS_PATH}" -u "${CLDR_UNITS_PATH}"
|
||||
arguments -r "${CLDR_CORE_PATH}" -n "${CLDR_NUMBERS_PATH}"
|
||||
)
|
||||
invoke_generator(
|
||||
"PluralRulesData"
|
||||
|
|
|
@ -5,11 +5,8 @@
|
|||
*/
|
||||
|
||||
#include "../LibUnicode/GeneratorUtil.h" // FIXME: Move this somewhere common.
|
||||
#include <AK/AllOf.h>
|
||||
#include <AK/Array.h>
|
||||
#include <AK/ByteString.h>
|
||||
#include <AK/CharacterTypes.h>
|
||||
#include <AK/Find.h>
|
||||
#include <AK/Format.h>
|
||||
#include <AK/HashFunctions.h>
|
||||
#include <AK/HashMap.h>
|
||||
|
@ -25,144 +22,23 @@
|
|||
#include <LibCore/ArgsParser.h>
|
||||
#include <LibCore/Directory.h>
|
||||
#include <LibFileSystem/FileSystem.h>
|
||||
#include <LibJS/Runtime/Intl/SingleUnitIdentifiers.h>
|
||||
#include <LibLocale/Locale.h>
|
||||
#include <LibLocale/NumberFormat.h>
|
||||
#include <LibLocale/PluralRules.h>
|
||||
#include <math.h>
|
||||
|
||||
enum class NumberFormatType {
|
||||
Standard,
|
||||
Compact,
|
||||
};
|
||||
|
||||
struct NumberFormat : public Locale::NumberFormat {
|
||||
using Base = Locale::NumberFormat;
|
||||
|
||||
unsigned hash() const
|
||||
{
|
||||
auto hash = pair_int_hash(magnitude, exponent);
|
||||
hash = pair_int_hash(hash, to_underlying(plurality));
|
||||
hash = pair_int_hash(hash, zero_format_index);
|
||||
hash = pair_int_hash(hash, positive_format_index);
|
||||
hash = pair_int_hash(hash, negative_format_index);
|
||||
|
||||
for (auto index : identifier_indices)
|
||||
hash = pair_int_hash(hash, index);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
bool operator==(NumberFormat const& other) const
|
||||
{
|
||||
return (magnitude == other.magnitude)
|
||||
&& (exponent == other.exponent)
|
||||
&& (plurality == other.plurality)
|
||||
&& (zero_format_index == other.zero_format_index)
|
||||
&& (positive_format_index == other.positive_format_index)
|
||||
&& (negative_format_index == other.negative_format_index)
|
||||
&& (identifier_indices == other.identifier_indices);
|
||||
}
|
||||
|
||||
size_t zero_format_index { 0 };
|
||||
size_t positive_format_index { 0 };
|
||||
size_t negative_format_index { 0 };
|
||||
Vector<size_t> identifier_indices {};
|
||||
};
|
||||
|
||||
template<>
|
||||
struct AK::Formatter<NumberFormat> : Formatter<FormatString> {
|
||||
ErrorOr<void> format(FormatBuilder& builder, NumberFormat const& format)
|
||||
{
|
||||
StringBuilder identifier_indices;
|
||||
identifier_indices.join(", "sv, format.identifier_indices);
|
||||
|
||||
return Formatter<FormatString>::format(builder,
|
||||
"{{ {}, {}, {}, {}, {}, {}, {{ {} }} }}"sv,
|
||||
format.magnitude,
|
||||
format.exponent,
|
||||
to_underlying(format.plurality),
|
||||
format.zero_format_index,
|
||||
format.positive_format_index,
|
||||
format.negative_format_index,
|
||||
identifier_indices.to_byte_string());
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct AK::Traits<NumberFormat> : public DefaultTraits<NumberFormat> {
|
||||
static unsigned hash(NumberFormat const& f) { return f.hash(); }
|
||||
};
|
||||
|
||||
using NumberFormatList = Vector<size_t>;
|
||||
using NumericSymbolList = Vector<size_t>;
|
||||
|
||||
struct NumberSystem {
|
||||
unsigned hash() const
|
||||
{
|
||||
auto hash = int_hash(symbols);
|
||||
hash = pair_int_hash(hash, primary_grouping_size);
|
||||
hash = pair_int_hash(hash, secondary_grouping_size);
|
||||
hash = pair_int_hash(hash, decimal_format);
|
||||
hash = pair_int_hash(hash, decimal_long_formats);
|
||||
hash = pair_int_hash(hash, decimal_short_formats);
|
||||
hash = pair_int_hash(hash, currency_format);
|
||||
hash = pair_int_hash(hash, accounting_format);
|
||||
hash = pair_int_hash(hash, currency_unit_formats);
|
||||
hash = pair_int_hash(hash, percent_format);
|
||||
hash = pair_int_hash(hash, scientific_format);
|
||||
return hash;
|
||||
}
|
||||
|
||||
bool operator==(NumberSystem const& other) const
|
||||
{
|
||||
return (symbols == other.symbols)
|
||||
&& (primary_grouping_size == other.primary_grouping_size)
|
||||
&& (secondary_grouping_size == other.secondary_grouping_size)
|
||||
&& (decimal_format == other.decimal_format)
|
||||
&& (decimal_long_formats == other.decimal_long_formats)
|
||||
&& (decimal_short_formats == other.decimal_short_formats)
|
||||
&& (currency_format == other.currency_format)
|
||||
&& (accounting_format == other.accounting_format)
|
||||
&& (currency_unit_formats == other.currency_unit_formats)
|
||||
&& (percent_format == other.percent_format)
|
||||
&& (scientific_format == other.scientific_format);
|
||||
}
|
||||
unsigned hash() const { return int_hash(symbols); }
|
||||
bool operator==(NumberSystem const& other) const { return (symbols == other.symbols); }
|
||||
|
||||
size_t symbols { 0 };
|
||||
|
||||
u8 primary_grouping_size { 0 };
|
||||
u8 secondary_grouping_size { 0 };
|
||||
|
||||
size_t decimal_format { 0 };
|
||||
size_t decimal_long_formats { 0 };
|
||||
size_t decimal_short_formats { 0 };
|
||||
|
||||
size_t currency_format { 0 };
|
||||
size_t accounting_format { 0 };
|
||||
size_t currency_unit_formats { 0 };
|
||||
|
||||
size_t percent_format { 0 };
|
||||
size_t scientific_format { 0 };
|
||||
};
|
||||
|
||||
template<>
|
||||
struct AK::Formatter<NumberSystem> : Formatter<FormatString> {
|
||||
ErrorOr<void> format(FormatBuilder& builder, NumberSystem const& system)
|
||||
{
|
||||
return Formatter<FormatString>::format(builder,
|
||||
"{{ {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {} }}"sv,
|
||||
system.symbols,
|
||||
system.primary_grouping_size,
|
||||
system.secondary_grouping_size,
|
||||
system.decimal_format,
|
||||
system.decimal_long_formats,
|
||||
system.decimal_short_formats,
|
||||
system.currency_format,
|
||||
system.accounting_format,
|
||||
system.currency_unit_formats,
|
||||
system.percent_format,
|
||||
system.scientific_format);
|
||||
return Formatter<FormatString>::format(builder, "{{ {} }}"sv, system.symbols);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -171,67 +47,19 @@ struct AK::Traits<NumberSystem> : public DefaultTraits<NumberSystem> {
|
|||
static unsigned hash(NumberSystem const& s) { return s.hash(); }
|
||||
};
|
||||
|
||||
struct Unit {
|
||||
unsigned hash() const
|
||||
{
|
||||
auto hash = int_hash(unit);
|
||||
hash = pair_int_hash(hash, long_formats);
|
||||
hash = pair_int_hash(hash, short_formats);
|
||||
hash = pair_int_hash(hash, narrow_formats);
|
||||
return hash;
|
||||
}
|
||||
|
||||
bool operator==(Unit const& other) const
|
||||
{
|
||||
return (unit == other.unit)
|
||||
&& (long_formats == other.long_formats)
|
||||
&& (short_formats == other.short_formats)
|
||||
&& (narrow_formats == other.narrow_formats);
|
||||
}
|
||||
|
||||
size_t unit { 0 };
|
||||
size_t long_formats { 0 };
|
||||
size_t short_formats { 0 };
|
||||
size_t narrow_formats { 0 };
|
||||
};
|
||||
|
||||
template<>
|
||||
struct AK::Formatter<Unit> : Formatter<FormatString> {
|
||||
ErrorOr<void> format(FormatBuilder& builder, Unit const& system)
|
||||
{
|
||||
return Formatter<FormatString>::format(builder,
|
||||
"{{ {}, {}, {}, {} }}"sv,
|
||||
system.unit,
|
||||
system.long_formats,
|
||||
system.short_formats,
|
||||
system.narrow_formats);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct AK::Traits<Unit> : public DefaultTraits<Unit> {
|
||||
static unsigned hash(Unit const& u) { return u.hash(); }
|
||||
};
|
||||
|
||||
struct LocaleData {
|
||||
Vector<size_t> number_systems;
|
||||
HashMap<ByteString, size_t> units {};
|
||||
u8 minimum_grouping_digits { 0 };
|
||||
};
|
||||
|
||||
struct CLDR {
|
||||
UniqueStringStorage unique_strings;
|
||||
UniqueStorage<NumberFormat> unique_formats;
|
||||
UniqueStorage<NumberFormatList> unique_format_lists;
|
||||
UniqueStorage<NumericSymbolList> unique_symbols;
|
||||
UniqueStorage<NumberSystem> unique_systems;
|
||||
UniqueStorage<Unit> unique_units;
|
||||
|
||||
HashMap<ByteString, Array<u32, 10>> number_system_digits;
|
||||
Vector<ByteString> number_systems;
|
||||
|
||||
HashMap<ByteString, LocaleData> locales;
|
||||
size_t max_identifier_count { 0 };
|
||||
};
|
||||
|
||||
static ErrorOr<void> parse_number_system_digits(ByteString core_supplemental_path, CLDR& cldr)
|
||||
|
@ -266,144 +94,6 @@ static ErrorOr<void> parse_number_system_digits(ByteString core_supplemental_pat
|
|||
return {};
|
||||
}
|
||||
|
||||
static ByteString parse_identifiers(ByteString pattern, StringView replacement, CLDR& cldr, NumberFormat& format)
|
||||
{
|
||||
static constexpr Utf8View whitespace { "\u0020\u00a0\u200f"sv };
|
||||
|
||||
while (true) {
|
||||
Utf8View utf8_pattern { pattern };
|
||||
Optional<size_t> start_index;
|
||||
Optional<size_t> end_index;
|
||||
bool inside_replacement = false;
|
||||
|
||||
for (auto it = utf8_pattern.begin(); it != utf8_pattern.end(); ++it) {
|
||||
if (*it == '{') {
|
||||
if (start_index.has_value()) {
|
||||
end_index = utf8_pattern.byte_offset_of(it);
|
||||
break;
|
||||
}
|
||||
|
||||
inside_replacement = true;
|
||||
} else if (*it == '}') {
|
||||
inside_replacement = false;
|
||||
} else if (!inside_replacement && !start_index.has_value() && !whitespace.contains(*it)) {
|
||||
start_index = utf8_pattern.byte_offset_of(it);
|
||||
}
|
||||
}
|
||||
|
||||
if (!start_index.has_value())
|
||||
return pattern;
|
||||
|
||||
end_index = end_index.value_or(pattern.length());
|
||||
|
||||
utf8_pattern = utf8_pattern.substring_view(*start_index, *end_index - *start_index);
|
||||
utf8_pattern = utf8_pattern.trim(whitespace);
|
||||
|
||||
auto identifier = utf8_pattern.as_string().replace("'.'"sv, "."sv, ReplaceMode::FirstOnly);
|
||||
auto identifier_index = cldr.unique_strings.ensure(move(identifier));
|
||||
size_t replacement_index = 0;
|
||||
|
||||
if (auto index = format.identifier_indices.find_first_index(identifier_index); index.has_value()) {
|
||||
replacement_index = *index;
|
||||
} else {
|
||||
replacement_index = format.identifier_indices.size();
|
||||
format.identifier_indices.append(identifier_index);
|
||||
|
||||
cldr.max_identifier_count = max(cldr.max_identifier_count, format.identifier_indices.size());
|
||||
}
|
||||
|
||||
pattern = ByteString::formatted("{}{{{}:{}}}{}",
|
||||
*start_index > 0 ? pattern.substring_view(0, *start_index) : ""sv,
|
||||
replacement,
|
||||
replacement_index,
|
||||
pattern.substring_view(*start_index + utf8_pattern.byte_length()));
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_number_pattern(Vector<ByteString> patterns, CLDR& cldr, NumberFormatType type, NumberFormat& format, NumberSystem* number_system_for_groupings = nullptr)
|
||||
{
|
||||
// https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns
|
||||
// https://cldr.unicode.org/translation/number-currency-formats/number-and-currency-patterns
|
||||
VERIFY((patterns.size() == 1) || (patterns.size() == 2));
|
||||
|
||||
auto replace_patterns = [&](ByteString pattern) {
|
||||
static HashMap<StringView, StringView> replacements = {
|
||||
{ "{0}"sv, "{number}"sv },
|
||||
{ "{1}"sv, "{currency}"sv },
|
||||
{ "%"sv, "{percentSign}"sv },
|
||||
{ "+"sv, "{plusSign}"sv },
|
||||
{ "-"sv, "{minusSign}"sv },
|
||||
{ "¤"sv, "{currency}"sv }, // U+00A4 Currency Sign
|
||||
{ "E"sv, "{scientificSeparator}"sv },
|
||||
};
|
||||
|
||||
for (auto const& replacement : replacements)
|
||||
pattern = pattern.replace(replacement.key, replacement.value, ReplaceMode::All);
|
||||
|
||||
if (auto start_number_index = pattern.find_any_of("#0"sv, ByteString::SearchDirection::Forward); start_number_index.has_value()) {
|
||||
auto end_number_index = *start_number_index + 1;
|
||||
|
||||
for (; end_number_index < pattern.length(); ++end_number_index) {
|
||||
auto ch = pattern[end_number_index];
|
||||
if ((ch != '#') && (ch != '0') && (ch != ',') && (ch != '.'))
|
||||
break;
|
||||
}
|
||||
|
||||
if (number_system_for_groupings) {
|
||||
auto number_pattern = pattern.substring_view(*start_number_index, end_number_index - *start_number_index);
|
||||
|
||||
auto group_separators = number_pattern.find_all(","sv);
|
||||
VERIFY((group_separators.size() == 1) || (group_separators.size() == 2));
|
||||
|
||||
auto decimal = number_pattern.find('.');
|
||||
VERIFY(decimal.has_value());
|
||||
|
||||
if (group_separators.size() == 1) {
|
||||
number_system_for_groupings->primary_grouping_size = *decimal - group_separators[0] - 1;
|
||||
number_system_for_groupings->secondary_grouping_size = number_system_for_groupings->primary_grouping_size;
|
||||
} else {
|
||||
number_system_for_groupings->primary_grouping_size = *decimal - group_separators[1] - 1;
|
||||
number_system_for_groupings->secondary_grouping_size = group_separators[1] - group_separators[0] - 1;
|
||||
}
|
||||
}
|
||||
|
||||
pattern = ByteString::formatted("{}{{number}}{}",
|
||||
*start_number_index > 0 ? pattern.substring_view(0, *start_number_index) : ""sv,
|
||||
pattern.substring_view(end_number_index));
|
||||
|
||||
// This is specifically handled here rather than in the replacements HashMap above so
|
||||
// that we do not errantly replace zeroes in number patterns.
|
||||
if (pattern.contains(*replacements.get("E"sv)))
|
||||
pattern = pattern.replace("0"sv, "{scientificExponent}"sv, ReplaceMode::FirstOnly);
|
||||
}
|
||||
|
||||
if (type == NumberFormatType::Compact)
|
||||
return parse_identifiers(move(pattern), "compactIdentifier"sv, cldr, format);
|
||||
|
||||
return pattern;
|
||||
};
|
||||
|
||||
auto zero_format = replace_patterns(move(patterns[0]));
|
||||
format.positive_format_index = cldr.unique_strings.ensure(ByteString::formatted("{{plusSign}}{}", zero_format));
|
||||
|
||||
if (patterns.size() == 2) {
|
||||
auto negative_format = replace_patterns(move(patterns[1]));
|
||||
format.negative_format_index = cldr.unique_strings.ensure(move(negative_format));
|
||||
} else {
|
||||
format.negative_format_index = cldr.unique_strings.ensure(ByteString::formatted("{{minusSign}}{}", zero_format));
|
||||
}
|
||||
|
||||
format.zero_format_index = cldr.unique_strings.ensure(move(zero_format));
|
||||
}
|
||||
|
||||
static void parse_number_pattern(Vector<ByteString> patterns, CLDR& cldr, NumberFormatType type, size_t& format_index, NumberSystem* number_system_for_groupings = nullptr)
|
||||
{
|
||||
NumberFormat format {};
|
||||
parse_number_pattern(move(patterns), cldr, type, format, number_system_for_groupings);
|
||||
|
||||
format_index = cldr.unique_formats.ensure(move(format));
|
||||
}
|
||||
|
||||
static ErrorOr<void> parse_number_systems(ByteString locale_numbers_path, CLDR& cldr, LocaleData& locale)
|
||||
{
|
||||
LexicalPath numbers_path(move(locale_numbers_path));
|
||||
|
@ -429,42 +119,6 @@ static ErrorOr<void> parse_number_systems(ByteString locale_numbers_path, CLDR&
|
|||
return number_system.value();
|
||||
};
|
||||
|
||||
auto parse_number_format = [&](auto const& format_object) {
|
||||
Vector<size_t> result;
|
||||
result.ensure_capacity(format_object.size());
|
||||
|
||||
format_object.for_each_member([&](auto const& key, JsonValue const& value) {
|
||||
auto split_key = key.split_view('-');
|
||||
if (split_key.size() != 3)
|
||||
return;
|
||||
|
||||
auto patterns = value.as_string().split(';');
|
||||
NumberFormat format {};
|
||||
|
||||
if (auto type = split_key[0].template to_number<u64>(); type.has_value()) {
|
||||
VERIFY(*type % 10 == 0);
|
||||
format.magnitude = static_cast<u8>(log10(*type));
|
||||
|
||||
if (patterns[0] != "0"sv) {
|
||||
auto number_of_zeroes_in_pattern = patterns[0].count("0"sv);
|
||||
VERIFY(format.magnitude >= number_of_zeroes_in_pattern);
|
||||
|
||||
format.exponent = format.magnitude + 1 - number_of_zeroes_in_pattern;
|
||||
}
|
||||
} else {
|
||||
VERIFY(split_key[0] == "unitPattern"sv);
|
||||
}
|
||||
|
||||
format.plurality = Locale::plural_category_from_string(split_key[2]);
|
||||
parse_number_pattern(move(patterns), cldr, NumberFormatType::Compact, format);
|
||||
|
||||
auto format_index = cldr.unique_formats.ensure(move(format));
|
||||
result.append(format_index);
|
||||
});
|
||||
|
||||
return cldr.unique_format_lists.ensure(move(result));
|
||||
};
|
||||
|
||||
auto numeric_symbol_from_string = [&](StringView numeric_symbol) -> Optional<Locale::NumericSymbol> {
|
||||
if (numeric_symbol == "approximatelySign"sv)
|
||||
return Locale::NumericSymbol::ApproximatelySign;
|
||||
|
@ -491,10 +145,6 @@ static ErrorOr<void> parse_number_systems(ByteString locale_numbers_path, CLDR&
|
|||
|
||||
locale_numbers_object.for_each_member([&](auto const& key, JsonValue const& value) {
|
||||
constexpr auto symbols_prefix = "symbols-numberSystem-"sv;
|
||||
constexpr auto decimal_formats_prefix = "decimalFormats-numberSystem-"sv;
|
||||
constexpr auto currency_formats_prefix = "currencyFormats-numberSystem-"sv;
|
||||
constexpr auto percent_formats_prefix = "percentFormats-numberSystem-"sv;
|
||||
constexpr auto scientific_formats_prefix = "scientificFormats-numberSystem-"sv;
|
||||
constexpr auto misc_patterns_prefix = "miscPatterns-numberSystem-"sv;
|
||||
|
||||
if (key.starts_with(symbols_prefix)) {
|
||||
|
@ -532,41 +182,6 @@ static ErrorOr<void> parse_number_systems(ByteString locale_numbers_path, CLDR&
|
|||
symbols[to_underlying(Locale::NumericSymbol::RangeSeparator)] = symbol_index;
|
||||
|
||||
number_system.symbols = cldr.unique_symbols.ensure(move(symbols));
|
||||
} else if (key.starts_with(decimal_formats_prefix)) {
|
||||
auto system = key.substring(decimal_formats_prefix.length());
|
||||
auto& number_system = ensure_number_system(system);
|
||||
|
||||
auto format_object = value.as_object().get_byte_string("standard"sv).value();
|
||||
parse_number_pattern(format_object.split(';'), cldr, NumberFormatType::Standard, number_system.decimal_format, &number_system);
|
||||
|
||||
auto const& long_format = value.as_object().get_object("long"sv)->get_object("decimalFormat"sv).value();
|
||||
number_system.decimal_long_formats = parse_number_format(long_format);
|
||||
|
||||
auto const& short_format = value.as_object().get_object("short"sv)->get_object("decimalFormat"sv).value();
|
||||
number_system.decimal_short_formats = parse_number_format(short_format);
|
||||
} else if (key.starts_with(currency_formats_prefix)) {
|
||||
auto system = key.substring(currency_formats_prefix.length());
|
||||
auto& number_system = ensure_number_system(system);
|
||||
|
||||
auto format_object = value.as_object().get_byte_string("standard"sv).value();
|
||||
parse_number_pattern(format_object.split(';'), cldr, NumberFormatType::Standard, number_system.currency_format);
|
||||
|
||||
format_object = value.as_object().get_byte_string("accounting"sv).value();
|
||||
parse_number_pattern(format_object.split(';'), cldr, NumberFormatType::Standard, number_system.accounting_format);
|
||||
|
||||
number_system.currency_unit_formats = parse_number_format(value.as_object());
|
||||
} else if (key.starts_with(percent_formats_prefix)) {
|
||||
auto system = key.substring(percent_formats_prefix.length());
|
||||
auto& number_system = ensure_number_system(system);
|
||||
|
||||
auto format_object = value.as_object().get_byte_string("standard"sv).value();
|
||||
parse_number_pattern(format_object.split(';'), cldr, NumberFormatType::Standard, number_system.percent_format);
|
||||
} else if (key.starts_with(scientific_formats_prefix)) {
|
||||
auto system = key.substring(scientific_formats_prefix.length());
|
||||
auto& number_system = ensure_number_system(system);
|
||||
|
||||
auto format_object = value.as_object().get_byte_string("standard"sv).value();
|
||||
parse_number_pattern(format_object.split(';'), cldr, NumberFormatType::Standard, number_system.scientific_format);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -580,114 +195,11 @@ static ErrorOr<void> parse_number_systems(ByteString locale_numbers_path, CLDR&
|
|||
locale.number_systems.append(system_index);
|
||||
}
|
||||
|
||||
locale.minimum_grouping_digits = minimum_grouping_digits.template to_number<u8>().value();
|
||||
// locale.minimum_grouping_digits = minimum_grouping_digits.template to_number<u8>().value();
|
||||
return {};
|
||||
}
|
||||
|
||||
static ErrorOr<void> parse_units(ByteString locale_units_path, CLDR& cldr, LocaleData& locale)
|
||||
{
|
||||
LexicalPath units_path(move(locale_units_path));
|
||||
units_path = units_path.append("units.json"sv);
|
||||
|
||||
auto locale_units = TRY(read_json_file(units_path.string()));
|
||||
auto const& main_object = locale_units.as_object().get_object("main"sv).value();
|
||||
auto const& locale_object = main_object.get_object(units_path.parent().basename()).value();
|
||||
auto const& locale_units_object = locale_object.get_object("units"sv).value();
|
||||
auto const& long_object = locale_units_object.get_object("long"sv).value();
|
||||
auto const& short_object = locale_units_object.get_object("short"sv).value();
|
||||
auto const& narrow_object = locale_units_object.get_object("narrow"sv).value();
|
||||
|
||||
HashMap<ByteString, Unit> units;
|
||||
|
||||
auto ensure_unit = [&](auto const& unit) -> Unit& {
|
||||
return units.ensure(unit, [&]() {
|
||||
auto unit_index = cldr.unique_strings.ensure(unit);
|
||||
return Unit { .unit = unit_index };
|
||||
});
|
||||
};
|
||||
|
||||
auto is_sanctioned_unit = [](StringView unit_name) {
|
||||
// LibUnicode generally tries to avoid being directly dependent on ECMA-402, but this rather significantly reduces the amount
|
||||
// of data generated here, and ECMA-402 is currently the only consumer of this data.
|
||||
constexpr auto sanctioned_units = JS::Intl::sanctioned_single_unit_identifiers();
|
||||
return find(sanctioned_units.begin(), sanctioned_units.end(), unit_name) != sanctioned_units.end();
|
||||
};
|
||||
|
||||
auto parse_units_object = [&](auto const& units_object, Locale::Style style) {
|
||||
constexpr auto unit_pattern_prefix = "unitPattern-count-"sv;
|
||||
constexpr auto combined_unit_separator = "-per-"sv;
|
||||
|
||||
units_object.for_each_member([&](auto const& key, JsonValue const& value) {
|
||||
auto end_of_category = key.find('-');
|
||||
if (!end_of_category.has_value())
|
||||
return;
|
||||
|
||||
auto unit_name = key.substring(*end_of_category + 1);
|
||||
|
||||
if (!is_sanctioned_unit(unit_name)) {
|
||||
auto indices = unit_name.find_all(combined_unit_separator);
|
||||
if (indices.size() != 1)
|
||||
return;
|
||||
|
||||
auto numerator = unit_name.substring_view(0, indices[0]);
|
||||
auto denominator = unit_name.substring_view(indices[0] + combined_unit_separator.length());
|
||||
if (!is_sanctioned_unit(numerator) || !is_sanctioned_unit(denominator))
|
||||
return;
|
||||
}
|
||||
|
||||
auto& unit = ensure_unit(unit_name);
|
||||
NumberFormatList formats;
|
||||
|
||||
value.as_object().for_each_member([&](auto const& unit_key, JsonValue const& pattern_value) {
|
||||
if (!unit_key.starts_with(unit_pattern_prefix))
|
||||
return;
|
||||
|
||||
NumberFormat format {};
|
||||
|
||||
auto plurality = unit_key.substring_view(unit_pattern_prefix.length());
|
||||
format.plurality = Locale::plural_category_from_string(plurality);
|
||||
|
||||
auto zero_format = pattern_value.as_string().replace("{0}"sv, "{number}"sv, ReplaceMode::FirstOnly);
|
||||
zero_format = parse_identifiers(zero_format, "unitIdentifier"sv, cldr, format);
|
||||
|
||||
format.positive_format_index = cldr.unique_strings.ensure(zero_format.replace("{number}"sv, "{plusSign}{number}"sv, ReplaceMode::FirstOnly));
|
||||
format.negative_format_index = cldr.unique_strings.ensure(zero_format.replace("{number}"sv, "{minusSign}{number}"sv, ReplaceMode::FirstOnly));
|
||||
format.zero_format_index = cldr.unique_strings.ensure(move(zero_format));
|
||||
|
||||
formats.append(cldr.unique_formats.ensure(move(format)));
|
||||
});
|
||||
|
||||
auto number_format_list_index = cldr.unique_format_lists.ensure(move(formats));
|
||||
|
||||
switch (style) {
|
||||
case Locale::Style::Long:
|
||||
unit.long_formats = number_format_list_index;
|
||||
break;
|
||||
case Locale::Style::Short:
|
||||
unit.short_formats = number_format_list_index;
|
||||
break;
|
||||
case Locale::Style::Narrow:
|
||||
unit.narrow_formats = number_format_list_index;
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
parse_units_object(long_object, Locale::Style::Long);
|
||||
parse_units_object(short_object, Locale::Style::Short);
|
||||
parse_units_object(narrow_object, Locale::Style::Narrow);
|
||||
|
||||
for (auto& unit : units) {
|
||||
auto unit_index = cldr.unique_units.ensure(move(unit.value));
|
||||
locale.units.set(unit.key, unit_index);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static ErrorOr<void> parse_all_locales(ByteString core_path, ByteString numbers_path, ByteString units_path, CLDR& cldr)
|
||||
static ErrorOr<void> parse_all_locales(ByteString core_path, ByteString numbers_path, CLDR& cldr)
|
||||
{
|
||||
LexicalPath core_supplemental_path(move(core_path));
|
||||
core_supplemental_path = core_supplemental_path.append("supplemental"sv);
|
||||
|
@ -717,19 +229,10 @@ static ErrorOr<void> parse_all_locales(ByteString core_path, ByteString numbers_
|
|||
return IterationDecision::Continue;
|
||||
}));
|
||||
|
||||
TRY(Core::Directory::for_each_entry(TRY(String::formatted("{}/main", units_path)), Core::DirIterator::SkipParentAndBaseDir, [&](auto& entry, auto& directory) -> ErrorOr<IterationDecision> {
|
||||
auto units_path = LexicalPath::join(directory.path().string(), entry.name).string();
|
||||
auto language = TRY(remove_variants_from_path(units_path));
|
||||
|
||||
auto& locale = cldr.locales.ensure(language);
|
||||
TRY(parse_units(units_path, cldr, locale));
|
||||
return IterationDecision::Continue;
|
||||
}));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static ByteString format_identifier(StringView, ByteString identifier)
|
||||
static ByteString format_identifier(StringView, ByteString const& identifier)
|
||||
{
|
||||
return identifier.to_titlecase();
|
||||
}
|
||||
|
@ -762,23 +265,17 @@ static ErrorOr<void> generate_unicode_locale_implementation(Core::InputBufferedF
|
|||
StringBuilder builder;
|
||||
SourceGenerator generator { builder };
|
||||
generator.set("string_index_type"sv, cldr.unique_strings.type_that_fits());
|
||||
generator.set("number_format_index_type"sv, cldr.unique_formats.type_that_fits());
|
||||
generator.set("number_format_list_index_type"sv, cldr.unique_format_lists.type_that_fits());
|
||||
generator.set("numeric_symbol_list_index_type"sv, cldr.unique_symbols.type_that_fits());
|
||||
generator.set("identifier_count", ByteString::number(cldr.max_identifier_count));
|
||||
|
||||
generator.append(R"~~~(
|
||||
#include <AK/Array.h>
|
||||
#include <AK/BinarySearch.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/Span.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibLocale/Locale.h>
|
||||
#include <LibLocale/LocaleData.h>
|
||||
#include <LibLocale/NumberFormat.h>
|
||||
#include <LibLocale/NumberFormatData.h>
|
||||
#include <LibLocale/PluralRules.h>
|
||||
|
||||
namespace Locale {
|
||||
)~~~");
|
||||
|
@ -786,82 +283,19 @@ namespace Locale {
|
|||
cldr.unique_strings.generate(generator);
|
||||
|
||||
generator.append(R"~~~(
|
||||
struct NumberFormatImpl {
|
||||
NumberFormat to_unicode_number_format() const {
|
||||
NumberFormat number_format {};
|
||||
|
||||
number_format.magnitude = magnitude;
|
||||
number_format.exponent = exponent;
|
||||
number_format.plurality = static_cast<PluralCategory>(plurality);
|
||||
number_format.zero_format = decode_string(zero_format);
|
||||
number_format.positive_format = decode_string(positive_format);
|
||||
number_format.negative_format = decode_string(negative_format);
|
||||
|
||||
number_format.identifiers.ensure_capacity(identifiers.size());
|
||||
for (@string_index_type@ identifier : identifiers)
|
||||
number_format.identifiers.unchecked_append(decode_string(identifier));
|
||||
|
||||
return number_format;
|
||||
}
|
||||
|
||||
u8 magnitude { 0 };
|
||||
u8 exponent { 0 };
|
||||
u8 plurality { 0 };
|
||||
@string_index_type@ zero_format { 0 };
|
||||
@string_index_type@ positive_format { 0 };
|
||||
@string_index_type@ negative_format { 0 };
|
||||
Array<@string_index_type@, @identifier_count@> identifiers {};
|
||||
};
|
||||
|
||||
struct NumberSystemData {
|
||||
@numeric_symbol_list_index_type@ symbols { 0 };
|
||||
|
||||
u8 primary_grouping_size { 0 };
|
||||
u8 secondary_grouping_size { 0 };
|
||||
|
||||
@number_format_index_type@ decimal_format { 0 };
|
||||
@number_format_list_index_type@ decimal_long_formats { 0 };
|
||||
@number_format_list_index_type@ decimal_short_formats { 0 };
|
||||
|
||||
@number_format_index_type@ currency_format { 0 };
|
||||
@number_format_index_type@ accounting_format { 0 };
|
||||
@number_format_list_index_type@ currency_unit_formats { 0 };
|
||||
|
||||
@number_format_index_type@ percent_format { 0 };
|
||||
@number_format_index_type@ scientific_format { 0 };
|
||||
};
|
||||
|
||||
struct Unit {
|
||||
@string_index_type@ unit { 0 };
|
||||
@number_format_list_index_type@ long_formats { 0 };
|
||||
@number_format_list_index_type@ short_formats { 0 };
|
||||
@number_format_list_index_type@ narrow_formats { 0 };
|
||||
};
|
||||
)~~~");
|
||||
|
||||
cldr.unique_formats.generate(generator, "NumberFormatImpl"sv, "s_number_formats"sv, 10);
|
||||
cldr.unique_format_lists.generate(generator, cldr.unique_formats.type_that_fits(), "s_number_format_lists"sv);
|
||||
cldr.unique_symbols.generate(generator, cldr.unique_strings.type_that_fits(), "s_numeric_symbol_lists"sv);
|
||||
cldr.unique_systems.generate(generator, "NumberSystemData"sv, "s_number_systems"sv, 10);
|
||||
cldr.unique_units.generate(generator, "Unit"sv, "s_units"sv, 10);
|
||||
|
||||
auto locales = cldr.locales.keys();
|
||||
quick_sort(locales);
|
||||
|
||||
generator.set("size", ByteString::number(locales.size()));
|
||||
generator.append(R"~~~(
|
||||
static constexpr Array<u8, @size@> s_minimum_grouping_digits { { )~~~");
|
||||
|
||||
bool first = true;
|
||||
for (auto const& locale : locales) {
|
||||
generator.append(first ? " "sv : ", "sv);
|
||||
generator.append(ByteString::number(cldr.locales.find(locale)->value.minimum_grouping_digits));
|
||||
first = false;
|
||||
}
|
||||
generator.append(" } };\n");
|
||||
|
||||
auto append_map = [&](ByteString name, auto type, auto const& map) {
|
||||
generator.set("name", name);
|
||||
generator.set("name", move(name));
|
||||
generator.set("type", type);
|
||||
generator.set("size", ByteString::number(map.size()));
|
||||
|
||||
|
@ -883,7 +317,6 @@ static constexpr Array<@type@, @size@> @name@ { {)~~~");
|
|||
|
||||
generate_mapping(generator, cldr.number_system_digits, "u32"sv, "s_number_systems_digits"sv, "s_number_systems_digits_{}"sv, nullptr, [&](auto const& name, auto const& value) { append_map(name, "u32"sv, value); });
|
||||
generate_mapping(generator, cldr.locales, cldr.unique_systems.type_that_fits(), "s_locale_number_systems"sv, "s_number_systems_{}"sv, nullptr, [&](auto const& name, auto const& value) { append_map(name, cldr.unique_systems.type_that_fits(), value.number_systems); });
|
||||
generate_mapping(generator, cldr.locales, cldr.unique_units.type_that_fits(), "s_locale_units"sv, "s_units_{}"sv, nullptr, [&](auto const& name, auto const& value) { append_map(name, cldr.unique_units.type_that_fits(), value.units); });
|
||||
|
||||
generator.append(R"~~~(
|
||||
static Optional<NumberSystem> keyword_to_number_system(KeywordNumbers keyword)
|
||||
|
@ -969,127 +402,6 @@ Optional<StringView> get_number_system_symbol(StringView locale, StringView syst
|
|||
return {};
|
||||
}
|
||||
|
||||
Optional<NumberGroupings> get_number_system_groupings(StringView locale, StringView system)
|
||||
{
|
||||
auto locale_value = locale_from_string(locale);
|
||||
if (!locale_value.has_value())
|
||||
return {};
|
||||
|
||||
u8 minimum_grouping_digits = s_minimum_grouping_digits[to_underlying(*locale_value) - 1];
|
||||
|
||||
if (auto const* number_system = find_number_system(locale, system); number_system != nullptr)
|
||||
return NumberGroupings { minimum_grouping_digits, number_system->primary_grouping_size, number_system->secondary_grouping_size };
|
||||
return {};
|
||||
}
|
||||
|
||||
Optional<NumberFormat> get_standard_number_system_format(StringView locale, StringView system, StandardNumberFormatType type)
|
||||
{
|
||||
if (auto const* number_system = find_number_system(locale, system); number_system != nullptr) {
|
||||
@number_format_index_type@ format_index = 0;
|
||||
|
||||
switch (type) {
|
||||
case StandardNumberFormatType::Decimal:
|
||||
format_index = number_system->decimal_format;
|
||||
break;
|
||||
case StandardNumberFormatType::Currency:
|
||||
format_index = number_system->currency_format;
|
||||
break;
|
||||
case StandardNumberFormatType::Accounting:
|
||||
format_index = number_system->accounting_format;
|
||||
break;
|
||||
case StandardNumberFormatType::Percent:
|
||||
format_index = number_system->percent_format;
|
||||
break;
|
||||
case StandardNumberFormatType::Scientific:
|
||||
format_index = number_system->scientific_format;
|
||||
break;
|
||||
}
|
||||
|
||||
return s_number_formats[format_index].to_unicode_number_format();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Vector<NumberFormat> get_compact_number_system_formats(StringView locale, StringView system, CompactNumberFormatType type)
|
||||
{
|
||||
Vector<NumberFormat> formats;
|
||||
|
||||
if (auto const* number_system = find_number_system(locale, system); number_system != nullptr) {
|
||||
@number_format_list_index_type@ number_format_list_index { 0 };
|
||||
|
||||
switch (type) {
|
||||
case CompactNumberFormatType::DecimalLong:
|
||||
number_format_list_index = number_system->decimal_long_formats;
|
||||
break;
|
||||
case CompactNumberFormatType::DecimalShort:
|
||||
number_format_list_index = number_system->decimal_short_formats;
|
||||
break;
|
||||
case CompactNumberFormatType::CurrencyUnit:
|
||||
number_format_list_index = number_system->currency_unit_formats;
|
||||
break;
|
||||
}
|
||||
|
||||
auto number_formats = s_number_format_lists.at(number_format_list_index);
|
||||
formats.ensure_capacity(number_formats.size());
|
||||
|
||||
for (auto number_format : number_formats)
|
||||
formats.unchecked_append(s_number_formats[number_format].to_unicode_number_format());
|
||||
}
|
||||
|
||||
return formats;
|
||||
}
|
||||
|
||||
static Unit const* find_units(StringView locale, StringView unit)
|
||||
{
|
||||
auto locale_value = locale_from_string(locale);
|
||||
if (!locale_value.has_value())
|
||||
return nullptr;
|
||||
|
||||
auto locale_index = to_underlying(*locale_value) - 1; // Subtract 1 because 0 == Locale::None.
|
||||
auto const& locale_units = s_locale_units.at(locale_index);
|
||||
|
||||
for (auto unit_index : locale_units) {
|
||||
auto const& units = s_units.at(unit_index);
|
||||
|
||||
if (unit == decode_string(units.unit))
|
||||
return &units;
|
||||
};
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Vector<NumberFormat> get_unit_formats(StringView locale, StringView unit, Style style)
|
||||
{
|
||||
Vector<NumberFormat> formats;
|
||||
|
||||
if (auto const* units = find_units(locale, unit); units != nullptr) {
|
||||
@number_format_list_index_type@ number_format_list_index { 0 };
|
||||
|
||||
switch (style) {
|
||||
case Style::Long:
|
||||
number_format_list_index = units->long_formats;
|
||||
break;
|
||||
case Style::Short:
|
||||
number_format_list_index = units->short_formats;
|
||||
break;
|
||||
case Style::Narrow:
|
||||
number_format_list_index = units->narrow_formats;
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
auto number_formats = s_number_format_lists.at(number_format_list_index);
|
||||
formats.ensure_capacity(number_formats.size());
|
||||
|
||||
for (auto number_format : number_formats)
|
||||
formats.unchecked_append(s_number_formats[number_format].to_unicode_number_format());
|
||||
}
|
||||
|
||||
return formats;
|
||||
}
|
||||
|
||||
}
|
||||
)~~~");
|
||||
|
||||
|
@ -1103,21 +415,19 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
StringView generated_implementation_path;
|
||||
StringView core_path;
|
||||
StringView numbers_path;
|
||||
StringView units_path;
|
||||
|
||||
Core::ArgsParser args_parser;
|
||||
args_parser.add_option(generated_header_path, "Path to the Unicode locale header file to generate", "generated-header-path", 'h', "generated-header-path");
|
||||
args_parser.add_option(generated_implementation_path, "Path to the Unicode locale implementation file to generate", "generated-implementation-path", 'c', "generated-implementation-path");
|
||||
args_parser.add_option(core_path, "Path to cldr-core directory", "core-path", 'r', "core-path");
|
||||
args_parser.add_option(numbers_path, "Path to cldr-numbers directory", "numbers-path", 'n', "numbers-path");
|
||||
args_parser.add_option(units_path, "Path to cldr-units directory", "units-path", 'u', "units-path");
|
||||
args_parser.parse(arguments);
|
||||
|
||||
auto generated_header_file = TRY(open_file(generated_header_path, Core::File::OpenMode::Write));
|
||||
auto generated_implementation_file = TRY(open_file(generated_implementation_path, Core::File::OpenMode::Write));
|
||||
|
||||
CLDR cldr;
|
||||
TRY(parse_all_locales(core_path, numbers_path, units_path, cldr));
|
||||
TRY(parse_all_locales(core_path, numbers_path, cldr));
|
||||
|
||||
TRY(generate_unicode_locale_header(*generated_header_file, cldr));
|
||||
TRY(generate_unicode_locale_implementation(*generated_implementation_file, cldr));
|
||||
|
|
|
@ -93,7 +93,7 @@ JS_DEFINE_NATIVE_FUNCTION(BigIntPrototype::to_locale_string)
|
|||
auto* number_format = static_cast<Intl::NumberFormat*>(TRY(construct(vm, realm.intrinsics().intl_number_format_constructor(), locales, options)).ptr());
|
||||
|
||||
// 3. Return ? FormatNumeric(numberFormat, x).
|
||||
auto formatted = Intl::format_numeric(vm, *number_format, Value(bigint));
|
||||
auto formatted = Intl::format_numeric(*number_format, Value(bigint));
|
||||
return PrimitiveString::create(vm, move(formatted));
|
||||
}
|
||||
|
||||
|
|
|
@ -56,7 +56,8 @@ struct PatternPartition {
|
|||
};
|
||||
|
||||
struct PatternPartitionWithSource : public PatternPartition {
|
||||
static Vector<PatternPartitionWithSource> create_from_parent_list(Vector<PatternPartition> partitions)
|
||||
template<typename ParentList>
|
||||
static Vector<PatternPartitionWithSource> create_from_parent_list(ParentList partitions)
|
||||
{
|
||||
Vector<PatternPartitionWithSource> result;
|
||||
result.ensure_capacity(partitions.size());
|
||||
|
|
|
@ -541,7 +541,7 @@ ThrowCompletionOr<Vector<PatternPartition>> format_date_time_pattern(VM& vm, Dat
|
|||
value = floor(value * pow(10, static_cast<int>(*fractional_second_digits) - 3));
|
||||
|
||||
// iii. Let fv be FormatNumeric(nf3, v).
|
||||
auto formatted_value = format_numeric(vm, *number_format3, Value(value));
|
||||
auto formatted_value = format_numeric(*number_format3, Value(value));
|
||||
|
||||
// iv. Append a new Record { [[Type]]: "fractionalSecond", [[Value]]: fv } as the last element of result.
|
||||
result.append({ "fractionalSecond"sv, move(formatted_value) });
|
||||
|
@ -625,13 +625,13 @@ ThrowCompletionOr<Vector<PatternPartition>> format_date_time_pattern(VM& vm, Dat
|
|||
// viii. If f is "numeric", then
|
||||
case ::Locale::CalendarPatternStyle::Numeric:
|
||||
// 1. Let fv be FormatNumeric(nf, v).
|
||||
formatted_value = format_numeric(vm, *number_format, Value(value));
|
||||
formatted_value = format_numeric(*number_format, Value(value));
|
||||
break;
|
||||
|
||||
// ix. Else if f is "2-digit", then
|
||||
case ::Locale::CalendarPatternStyle::TwoDigit:
|
||||
// 1. Let fv be FormatNumeric(nf2, v).
|
||||
formatted_value = format_numeric(vm, *number_format2, Value(value));
|
||||
formatted_value = format_numeric(*number_format2, Value(value));
|
||||
|
||||
// 2. If the "length" property of fv is greater than 2, let fv be the substring of fv containing the last two characters.
|
||||
// NOTE: The first length check here isn't enough, but lets us avoid UTF-16 transcoding when the formatted value is ASCII.
|
||||
|
|
|
@ -449,7 +449,7 @@ Vector<::Locale::ListFormatPart> partition_duration_format_pattern(VM& vm, Durat
|
|||
// 3. Let dataLocaleData be %DurationFormat%.[[LocaleData]].[[<dataLocale>]].
|
||||
|
||||
// 4. Let num be ! FormatNumeric(nf, 𝔽(value)).
|
||||
auto number = format_numeric(vm, *number_format, MathematicalValue(value));
|
||||
auto number = format_numeric(*number_format, MathematicalValue(value));
|
||||
|
||||
// 5. Append the new Record { [[Type]]: unit, [[Value]]: num} to the end of result.
|
||||
result.append({ unit, move(number) });
|
||||
|
@ -508,7 +508,7 @@ Vector<::Locale::ListFormatPart> partition_duration_format_pattern(VM& vm, Durat
|
|||
auto* number_format = static_cast<NumberFormat*>(MUST(construct(vm, realm.intrinsics().intl_number_format_constructor(), PrimitiveString::create(vm, duration_format.locale()), number_format_options)).ptr());
|
||||
|
||||
// 5. Let parts be ! PartitionNumberPattern(nf, 𝔽(value)).
|
||||
auto parts = partition_number_pattern(vm, *number_format, MathematicalValue(value));
|
||||
auto parts = partition_number_pattern(*number_format, MathematicalValue(value));
|
||||
|
||||
// 6. Let concat be an empty String.
|
||||
StringBuilder concat;
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
|
||||
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/Intl/MathematicalValue.h>
|
||||
#include <math.h>
|
||||
|
||||
namespace JS::Intl {
|
||||
|
||||
|
@ -66,265 +63,25 @@ bool MathematicalValue::is_nan() const
|
|||
return m_value.get<Symbol>() == Symbol::NotANumber;
|
||||
}
|
||||
|
||||
void MathematicalValue::negate()
|
||||
{
|
||||
m_value.visit(
|
||||
[](double& value) {
|
||||
VERIFY(value != 0.0);
|
||||
value *= -1.0;
|
||||
},
|
||||
[](Crypto::SignedBigInteger& value) { value.negate(); },
|
||||
[](auto) { VERIFY_NOT_REACHED(); });
|
||||
}
|
||||
|
||||
MathematicalValue MathematicalValue::plus(Checked<i32> addition) const
|
||||
::Locale::NumberFormat::Value MathematicalValue::to_value() const
|
||||
{
|
||||
return m_value.visit(
|
||||
[&](double value) {
|
||||
return MathematicalValue { value + addition.value() };
|
||||
[](double value) -> ::Locale::NumberFormat::Value {
|
||||
return value;
|
||||
},
|
||||
[&](Crypto::SignedBigInteger const& value) {
|
||||
return MathematicalValue { value.plus(Crypto::SignedBigInteger { addition.value() }) };
|
||||
},
|
||||
[](auto) -> MathematicalValue { VERIFY_NOT_REACHED(); });
|
||||
}
|
||||
|
||||
MathematicalValue MathematicalValue::plus(MathematicalValue const& addition) const
|
||||
{
|
||||
return m_value.visit(
|
||||
[&](double value) {
|
||||
return MathematicalValue { value + addition.as_number() };
|
||||
},
|
||||
[&](Crypto::SignedBigInteger const& value) {
|
||||
return MathematicalValue { value.plus(addition.as_bigint()) };
|
||||
},
|
||||
[](auto) -> MathematicalValue { VERIFY_NOT_REACHED(); });
|
||||
}
|
||||
|
||||
MathematicalValue MathematicalValue::minus(Checked<i32> subtraction) const
|
||||
{
|
||||
return m_value.visit(
|
||||
[&](double value) {
|
||||
return MathematicalValue { value - subtraction.value() };
|
||||
},
|
||||
[&](Crypto::SignedBigInteger const& value) {
|
||||
return MathematicalValue { value.minus(Crypto::SignedBigInteger { subtraction.value() }) };
|
||||
},
|
||||
[](auto) -> MathematicalValue { VERIFY_NOT_REACHED(); });
|
||||
}
|
||||
|
||||
MathematicalValue MathematicalValue::minus(MathematicalValue const& subtraction) const
|
||||
{
|
||||
return m_value.visit(
|
||||
[&](double value) {
|
||||
return MathematicalValue { value - subtraction.as_number() };
|
||||
},
|
||||
[&](Crypto::SignedBigInteger const& value) {
|
||||
return MathematicalValue { value.minus(subtraction.as_bigint()) };
|
||||
},
|
||||
[](auto) -> MathematicalValue { VERIFY_NOT_REACHED(); });
|
||||
}
|
||||
|
||||
MathematicalValue MathematicalValue::multiplied_by(Checked<i32> multiplier) const
|
||||
{
|
||||
return m_value.visit(
|
||||
[&](double value) {
|
||||
return MathematicalValue { value * multiplier.value() };
|
||||
},
|
||||
[&](Crypto::SignedBigInteger const& value) {
|
||||
return MathematicalValue { value.multiplied_by(Crypto::SignedBigInteger { multiplier.value() }) };
|
||||
},
|
||||
[](auto) -> MathematicalValue { VERIFY_NOT_REACHED(); });
|
||||
}
|
||||
|
||||
MathematicalValue MathematicalValue::multiplied_by(MathematicalValue const& multiplier) const
|
||||
{
|
||||
return m_value.visit(
|
||||
[&](double value) {
|
||||
return MathematicalValue { value * multiplier.as_number() };
|
||||
},
|
||||
[&](Crypto::SignedBigInteger const& value) {
|
||||
return MathematicalValue { value.multiplied_by(multiplier.as_bigint()) };
|
||||
},
|
||||
[](auto) -> MathematicalValue { VERIFY_NOT_REACHED(); });
|
||||
}
|
||||
|
||||
MathematicalValue MathematicalValue::divided_by(Checked<i32> divisor) const
|
||||
{
|
||||
return m_value.visit(
|
||||
[&](double value) {
|
||||
return MathematicalValue { value / divisor.value() };
|
||||
},
|
||||
[&](Crypto::SignedBigInteger const& value) {
|
||||
return MathematicalValue { value.divided_by(Crypto::SignedBigInteger { divisor.value() }).quotient };
|
||||
},
|
||||
[](auto) -> MathematicalValue { VERIFY_NOT_REACHED(); });
|
||||
}
|
||||
|
||||
MathematicalValue MathematicalValue::divided_by(MathematicalValue const& divisor) const
|
||||
{
|
||||
return m_value.visit(
|
||||
[&](double value) {
|
||||
return MathematicalValue { value / divisor.as_number() };
|
||||
},
|
||||
[&](Crypto::SignedBigInteger const& value) {
|
||||
return MathematicalValue { value.divided_by(divisor.as_bigint()).quotient };
|
||||
},
|
||||
[](auto) -> MathematicalValue { VERIFY_NOT_REACHED(); });
|
||||
}
|
||||
|
||||
static Crypto::SignedBigInteger bigint_power(Checked<i32> exponent)
|
||||
{
|
||||
VERIFY(exponent >= 0);
|
||||
|
||||
static auto base = Crypto::SignedBigInteger { 10 };
|
||||
auto result = Crypto::SignedBigInteger { 1 };
|
||||
|
||||
for (i32 i = 0; i < exponent; ++i)
|
||||
result = result.multiplied_by(base);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
MathematicalValue MathematicalValue::multiplied_by_power(Checked<i32> exponent) const
|
||||
{
|
||||
return m_value.visit(
|
||||
[&](double value) {
|
||||
return MathematicalValue { value * pow(10, exponent.value()) };
|
||||
},
|
||||
[&](Crypto::SignedBigInteger const& value) {
|
||||
if (exponent < 0)
|
||||
return MathematicalValue { value.divided_by(bigint_power(-exponent.value())).quotient };
|
||||
return MathematicalValue { value.multiplied_by(bigint_power(exponent)) };
|
||||
},
|
||||
[](auto) -> MathematicalValue { VERIFY_NOT_REACHED(); });
|
||||
}
|
||||
|
||||
MathematicalValue MathematicalValue::divided_by_power(Checked<i32> exponent) const
|
||||
{
|
||||
return m_value.visit(
|
||||
[&](double value) {
|
||||
if (exponent < 0)
|
||||
return MathematicalValue { value * pow(10, -exponent.value()) };
|
||||
return MathematicalValue { value / pow(10, exponent.value()) };
|
||||
},
|
||||
[&](Crypto::SignedBigInteger const& value) {
|
||||
if (exponent < 0)
|
||||
return MathematicalValue { value.multiplied_by(bigint_power(-exponent.value())) };
|
||||
return MathematicalValue { value.divided_by(bigint_power(exponent)).quotient };
|
||||
},
|
||||
[](auto) -> MathematicalValue { VERIFY_NOT_REACHED(); });
|
||||
}
|
||||
|
||||
bool MathematicalValue::modulo_is_zero(Checked<i32> mod) const
|
||||
{
|
||||
return m_value.visit(
|
||||
[&](double value) {
|
||||
auto result = MathematicalValue { modulo(value, mod.value()) };
|
||||
return result.is_equal_to(MathematicalValue { 0.0 });
|
||||
},
|
||||
[&](Crypto::SignedBigInteger const& value) {
|
||||
return modulo(value, Crypto::SignedBigInteger { mod.value() }).is_zero();
|
||||
},
|
||||
[](auto) -> bool { VERIFY_NOT_REACHED(); });
|
||||
}
|
||||
|
||||
int MathematicalValue::logarithmic_floor() const
|
||||
{
|
||||
return m_value.visit(
|
||||
[](double value) {
|
||||
return static_cast<int>(floor(log10(value)));
|
||||
},
|
||||
[&](Crypto::SignedBigInteger const& value) {
|
||||
// FIXME: Can we do this without string conversion?
|
||||
auto value_as_string = MUST(value.to_base(10));
|
||||
return static_cast<int>(value_as_string.bytes_as_string_view().length() - 1);
|
||||
},
|
||||
[](auto) -> int { VERIFY_NOT_REACHED(); });
|
||||
}
|
||||
|
||||
bool MathematicalValue::is_equal_to(MathematicalValue const& other) const
|
||||
{
|
||||
return m_value.visit(
|
||||
[&](double value) {
|
||||
static constexpr double epsilon = 5e-14;
|
||||
return fabs(value - other.as_number()) < epsilon;
|
||||
},
|
||||
[&](Crypto::SignedBigInteger const& value) {
|
||||
return value == other.as_bigint();
|
||||
},
|
||||
[](auto) -> bool { VERIFY_NOT_REACHED(); });
|
||||
}
|
||||
|
||||
bool MathematicalValue::is_less_than(MathematicalValue const& other) const
|
||||
{
|
||||
return m_value.visit(
|
||||
[&](double value) {
|
||||
if (is_equal_to(other))
|
||||
return false;
|
||||
return value < other.as_number();
|
||||
},
|
||||
[&](Crypto::SignedBigInteger const& value) {
|
||||
return value < other.as_bigint();
|
||||
},
|
||||
[](auto) -> bool { VERIFY_NOT_REACHED(); });
|
||||
}
|
||||
|
||||
bool MathematicalValue::is_negative() const
|
||||
{
|
||||
return m_value.visit(
|
||||
[](double value) { return value < 0.0; },
|
||||
[](Crypto::SignedBigInteger const& value) { return value.is_negative(); },
|
||||
[](Symbol symbol) { return symbol == Symbol::NegativeInfinity; });
|
||||
}
|
||||
|
||||
bool MathematicalValue::is_positive() const
|
||||
{
|
||||
return m_value.visit(
|
||||
[](double value) { return value > 0.0; },
|
||||
[](Crypto::SignedBigInteger const& value) { return !value.is_zero() && !value.is_negative(); },
|
||||
[](Symbol symbol) { return symbol == Symbol::PositiveInfinity; });
|
||||
}
|
||||
|
||||
bool MathematicalValue::is_zero() const
|
||||
{
|
||||
return m_value.visit(
|
||||
[&](double value) { return value == 0.0; },
|
||||
[](Crypto::SignedBigInteger const& value) { return value.is_zero(); },
|
||||
[](auto) { return false; });
|
||||
}
|
||||
|
||||
String MathematicalValue::to_string() const
|
||||
{
|
||||
return m_value.visit(
|
||||
[&](double value) {
|
||||
return number_to_string(value, NumberToStringMode::WithoutExponent);
|
||||
},
|
||||
[&](Crypto::SignedBigInteger const& value) {
|
||||
[](Crypto::SignedBigInteger const& value) -> ::Locale::NumberFormat::Value {
|
||||
return MUST(value.to_base(10));
|
||||
},
|
||||
[&](auto) -> String { VERIFY_NOT_REACHED(); });
|
||||
}
|
||||
|
||||
Value MathematicalValue::to_value(VM& vm) const
|
||||
{
|
||||
return m_value.visit(
|
||||
[](double value) {
|
||||
return Value(value);
|
||||
},
|
||||
[&](Crypto::SignedBigInteger const& value) {
|
||||
return Value(BigInt::create(vm, value));
|
||||
},
|
||||
[](auto symbol) {
|
||||
[](auto symbol) -> ::Locale::NumberFormat::Value {
|
||||
switch (symbol) {
|
||||
case Symbol::PositiveInfinity:
|
||||
return js_infinity();
|
||||
return js_infinity().as_double();
|
||||
case Symbol::NegativeInfinity:
|
||||
return js_negative_infinity();
|
||||
return js_negative_infinity().as_double();
|
||||
case Symbol::NegativeZero:
|
||||
return Value(-0.0);
|
||||
return -0.0;
|
||||
case Symbol::NotANumber:
|
||||
return js_nan();
|
||||
return js_nan().as_double();
|
||||
}
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
|
||||
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Checked.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Variant.h>
|
||||
#include <LibCrypto/BigInt/SignedBigInteger.h>
|
||||
#include <LibJS/Runtime/BigInt.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
#include <LibLocale/NumberFormat.h>
|
||||
|
||||
namespace JS::Intl {
|
||||
|
||||
|
@ -61,36 +60,7 @@ public:
|
|||
bool is_negative_zero() const;
|
||||
bool is_nan() const;
|
||||
|
||||
void negate();
|
||||
|
||||
MathematicalValue plus(Checked<i32> addition) const;
|
||||
MathematicalValue plus(MathematicalValue const& addition) const;
|
||||
|
||||
MathematicalValue minus(Checked<i32> subtraction) const;
|
||||
MathematicalValue minus(MathematicalValue const& subtraction) const;
|
||||
|
||||
MathematicalValue multiplied_by(Checked<i32> multiplier) const;
|
||||
MathematicalValue multiplied_by(MathematicalValue const& multiplier) const;
|
||||
|
||||
MathematicalValue divided_by(Checked<i32> divisor) const;
|
||||
MathematicalValue divided_by(MathematicalValue const& divisor) const;
|
||||
|
||||
MathematicalValue multiplied_by_power(Checked<i32> exponent) const;
|
||||
MathematicalValue divided_by_power(Checked<i32> exponent) const;
|
||||
|
||||
bool modulo_is_zero(Checked<i32> mod) const;
|
||||
|
||||
int logarithmic_floor() const;
|
||||
|
||||
bool is_equal_to(MathematicalValue const&) const;
|
||||
bool is_less_than(MathematicalValue const&) const;
|
||||
|
||||
bool is_negative() const;
|
||||
bool is_positive() const;
|
||||
bool is_zero() const;
|
||||
|
||||
String to_string() const;
|
||||
Value to_value(VM&) const;
|
||||
::Locale::NumberFormat::Value to_value() const;
|
||||
|
||||
private:
|
||||
using ValueType = Variant<double, Crypto::SignedBigInteger, Symbol>;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -22,46 +22,11 @@ class NumberFormatBase : public Object {
|
|||
JS_DECLARE_ALLOCATOR(NumberFormatBase);
|
||||
|
||||
public:
|
||||
enum class RoundingType {
|
||||
Invalid,
|
||||
SignificantDigits,
|
||||
FractionDigits,
|
||||
MorePrecision,
|
||||
LessPrecision,
|
||||
};
|
||||
|
||||
enum class ComputedRoundingPriority {
|
||||
Invalid,
|
||||
Auto,
|
||||
MorePrecision,
|
||||
LessPrecision,
|
||||
};
|
||||
|
||||
enum class RoundingMode {
|
||||
Invalid,
|
||||
Ceil,
|
||||
Expand,
|
||||
Floor,
|
||||
HalfCeil,
|
||||
HalfEven,
|
||||
HalfExpand,
|
||||
HalfFloor,
|
||||
HalfTrunc,
|
||||
Trunc,
|
||||
};
|
||||
|
||||
enum class UnsignedRoundingMode {
|
||||
HalfEven,
|
||||
HalfInfinity,
|
||||
HalfZero,
|
||||
Infinity,
|
||||
Zero,
|
||||
};
|
||||
|
||||
enum class TrailingZeroDisplay {
|
||||
Invalid,
|
||||
Auto,
|
||||
StripIfInteger,
|
||||
};
|
||||
|
||||
virtual ~NumberFormatBase() override = default;
|
||||
|
@ -91,24 +56,29 @@ public:
|
|||
int max_significant_digits() const { return *m_max_significant_digits; }
|
||||
void set_max_significant_digits(int max_significant_digits) { m_max_significant_digits = max_significant_digits; }
|
||||
|
||||
RoundingType rounding_type() const { return m_rounding_type; }
|
||||
StringView rounding_type_string() const;
|
||||
void set_rounding_type(RoundingType rounding_type) { m_rounding_type = rounding_type; }
|
||||
::Locale::RoundingType rounding_type() const { return m_rounding_type; }
|
||||
StringView rounding_type_string() const { return ::Locale::rounding_type_to_string(m_rounding_type); }
|
||||
void set_rounding_type(::Locale::RoundingType rounding_type) { m_rounding_type = rounding_type; }
|
||||
|
||||
ComputedRoundingPriority computed_rounding_priority() const { return m_computed_rounding_priority; }
|
||||
StringView computed_rounding_priority_string() const;
|
||||
void set_computed_rounding_priority(ComputedRoundingPriority computed_rounding_priority) { m_computed_rounding_priority = computed_rounding_priority; }
|
||||
|
||||
RoundingMode rounding_mode() const { return m_rounding_mode; }
|
||||
StringView rounding_mode_string() const;
|
||||
void set_rounding_mode(StringView rounding_mode);
|
||||
::Locale::RoundingMode rounding_mode() const { return m_rounding_mode; }
|
||||
StringView rounding_mode_string() const { return ::Locale::rounding_mode_to_string(m_rounding_mode); }
|
||||
void set_rounding_mode(StringView rounding_mode) { m_rounding_mode = ::Locale::rounding_mode_from_string(rounding_mode); }
|
||||
|
||||
int rounding_increment() const { return m_rounding_increment; }
|
||||
void set_rounding_increment(int rounding_increment) { m_rounding_increment = rounding_increment; }
|
||||
|
||||
TrailingZeroDisplay trailing_zero_display() const { return m_trailing_zero_display; }
|
||||
StringView trailing_zero_display_string() const;
|
||||
void set_trailing_zero_display(StringView trailing_zero_display);
|
||||
::Locale::TrailingZeroDisplay trailing_zero_display() const { return m_trailing_zero_display; }
|
||||
StringView trailing_zero_display_string() const { return ::Locale::trailing_zero_display_to_string(m_trailing_zero_display); }
|
||||
void set_trailing_zero_display(StringView trailing_zero_display) { m_trailing_zero_display = ::Locale::trailing_zero_display_from_string(trailing_zero_display); }
|
||||
|
||||
::Locale::RoundingOptions rounding_options() const;
|
||||
|
||||
::Locale::NumberFormat const& formatter() const { return *m_formatter; }
|
||||
void set_formatter(NonnullOwnPtr<::Locale::NumberFormat> formatter) { m_formatter = move(formatter); }
|
||||
|
||||
protected:
|
||||
explicit NumberFormatBase(Object& prototype);
|
||||
|
@ -121,11 +91,14 @@ private:
|
|||
Optional<int> m_max_fraction_digits {}; // [[MaximumFractionDigits]]
|
||||
Optional<int> m_min_significant_digits {}; // [[MinimumSignificantDigits]]
|
||||
Optional<int> m_max_significant_digits {}; // [[MaximumSignificantDigits]]
|
||||
RoundingType m_rounding_type { RoundingType::Invalid }; // [[RoundingType]]
|
||||
::Locale::RoundingType m_rounding_type; // [[RoundingType]]
|
||||
ComputedRoundingPriority m_computed_rounding_priority { ComputedRoundingPriority::Invalid }; // [[ComputedRoundingPriority]]
|
||||
RoundingMode m_rounding_mode { RoundingMode::Invalid }; // [[RoundingMode]]
|
||||
::Locale::RoundingMode m_rounding_mode; // [[RoundingMode]]
|
||||
int m_rounding_increment { 1 }; // [[RoundingIncrement]]
|
||||
TrailingZeroDisplay m_trailing_zero_display { TrailingZeroDisplay::Invalid }; // [[TrailingZeroDisplay]]
|
||||
::Locale::TrailingZeroDisplay m_trailing_zero_display; // [[TrailingZeroDisplay]]
|
||||
|
||||
// Non-standard. Stores the ICU number formatter for the Intl object's formatting options.
|
||||
OwnPtr<::Locale::NumberFormat> m_formatter;
|
||||
};
|
||||
|
||||
class NumberFormat final : public NumberFormatBase {
|
||||
|
@ -133,56 +106,6 @@ class NumberFormat final : public NumberFormatBase {
|
|||
JS_DECLARE_ALLOCATOR(NumberFormat);
|
||||
|
||||
public:
|
||||
enum class Style {
|
||||
Invalid,
|
||||
Decimal,
|
||||
Percent,
|
||||
Currency,
|
||||
Unit,
|
||||
};
|
||||
|
||||
enum class CurrencyDisplay {
|
||||
Code,
|
||||
Symbol,
|
||||
NarrowSymbol,
|
||||
Name,
|
||||
};
|
||||
|
||||
enum class CurrencySign {
|
||||
Standard,
|
||||
Accounting,
|
||||
};
|
||||
|
||||
enum class Notation {
|
||||
Invalid,
|
||||
Standard,
|
||||
Scientific,
|
||||
Engineering,
|
||||
Compact,
|
||||
};
|
||||
|
||||
enum class CompactDisplay {
|
||||
Short,
|
||||
Long,
|
||||
};
|
||||
|
||||
enum class SignDisplay {
|
||||
Invalid,
|
||||
Auto,
|
||||
Never,
|
||||
Always,
|
||||
ExceptZero,
|
||||
Negative,
|
||||
};
|
||||
|
||||
enum class UseGrouping {
|
||||
Invalid,
|
||||
Always,
|
||||
Auto,
|
||||
Min2,
|
||||
False,
|
||||
};
|
||||
|
||||
static constexpr auto relevant_extension_keys()
|
||||
{
|
||||
// 15.2.3 Internal slots, https://tc39.es/ecma402/#sec-intl.numberformat-internal-slots
|
||||
|
@ -195,24 +118,23 @@ public:
|
|||
String const& numbering_system() const { return m_numbering_system; }
|
||||
void set_numbering_system(String numbering_system) { m_numbering_system = move(numbering_system); }
|
||||
|
||||
Style style() const { return m_style; }
|
||||
StringView style_string() const;
|
||||
void set_style(StringView style);
|
||||
::Locale::NumberFormatStyle style() const { return m_style; }
|
||||
StringView style_string() const { return ::Locale::number_format_style_to_string(m_style); }
|
||||
void set_style(StringView style) { m_style = ::Locale::number_format_style_from_string(style); }
|
||||
|
||||
bool has_currency() const { return m_currency.has_value(); }
|
||||
String const& currency() const { return m_currency.value(); }
|
||||
void set_currency(String currency) { m_currency = move(currency); }
|
||||
|
||||
bool has_currency_display() const { return m_currency_display.has_value(); }
|
||||
CurrencyDisplay currency_display() const { return *m_currency_display; }
|
||||
StringView currency_display_string() const;
|
||||
void set_currency_display(StringView currency_display);
|
||||
StringView resolve_currency_display();
|
||||
::Locale::CurrencyDisplay currency_display() const { return *m_currency_display; }
|
||||
StringView currency_display_string() const { return ::Locale::currency_display_to_string(*m_currency_display); }
|
||||
void set_currency_display(StringView currency_display) { m_currency_display = ::Locale::currency_display_from_string(currency_display); }
|
||||
|
||||
bool has_currency_sign() const { return m_currency_sign.has_value(); }
|
||||
CurrencySign currency_sign() const { return *m_currency_sign; }
|
||||
StringView currency_sign_string() const;
|
||||
void set_currency_sign(StringView set_currency_sign);
|
||||
::Locale::CurrencySign currency_sign() const { return *m_currency_sign; }
|
||||
StringView currency_sign_string() const { return ::Locale::currency_sign_to_string(*m_currency_sign); }
|
||||
void set_currency_sign(StringView currency_sign) { m_currency_sign = ::Locale::currency_sign_from_string(currency_sign); }
|
||||
|
||||
bool has_unit() const { return m_unit.has_value(); }
|
||||
String const& unit() const { return m_unit.value(); }
|
||||
|
@ -223,87 +145,55 @@ public:
|
|||
StringView unit_display_string() const { return ::Locale::style_to_string(*m_unit_display); }
|
||||
void set_unit_display(StringView unit_display) { m_unit_display = ::Locale::style_from_string(unit_display); }
|
||||
|
||||
UseGrouping use_grouping() const { return m_use_grouping; }
|
||||
::Locale::Grouping use_grouping() const { return m_use_grouping; }
|
||||
Value use_grouping_to_value(VM&) const;
|
||||
void set_use_grouping(StringOrBoolean const& use_grouping);
|
||||
|
||||
Notation notation() const { return m_notation; }
|
||||
StringView notation_string() const;
|
||||
void set_notation(StringView notation);
|
||||
::Locale::Notation notation() const { return m_notation; }
|
||||
StringView notation_string() const { return ::Locale::notation_to_string(m_notation); }
|
||||
void set_notation(StringView notation) { m_notation = ::Locale::notation_from_string(notation); }
|
||||
|
||||
bool has_compact_display() const { return m_compact_display.has_value(); }
|
||||
CompactDisplay compact_display() const { return *m_compact_display; }
|
||||
StringView compact_display_string() const;
|
||||
void set_compact_display(StringView compact_display);
|
||||
::Locale::CompactDisplay compact_display() const { return *m_compact_display; }
|
||||
StringView compact_display_string() const { return ::Locale::compact_display_to_string(*m_compact_display); }
|
||||
void set_compact_display(StringView compact_display) { m_compact_display = ::Locale::compact_display_from_string(compact_display); }
|
||||
|
||||
SignDisplay sign_display() const { return m_sign_display; }
|
||||
StringView sign_display_string() const;
|
||||
void set_sign_display(StringView sign_display);
|
||||
::Locale::SignDisplay sign_display() const { return m_sign_display; }
|
||||
StringView sign_display_string() const { return ::Locale::sign_display_to_string(m_sign_display); }
|
||||
void set_sign_display(StringView sign_display) { m_sign_display = ::Locale::sign_display_from_string(sign_display); }
|
||||
|
||||
NativeFunction* bound_format() const { return m_bound_format; }
|
||||
void set_bound_format(NativeFunction* bound_format) { m_bound_format = bound_format; }
|
||||
|
||||
bool has_compact_format() const { return m_compact_format.has_value(); }
|
||||
void set_compact_format(::Locale::NumberFormat compact_format) { m_compact_format = compact_format; }
|
||||
::Locale::NumberFormat compact_format() const { return *m_compact_format; }
|
||||
::Locale::DisplayOptions display_options() const;
|
||||
|
||||
private:
|
||||
explicit NumberFormat(Object& prototype);
|
||||
|
||||
virtual void visit_edges(Visitor&) override;
|
||||
|
||||
String m_locale; // [[Locale]]
|
||||
String m_data_locale; // [[DataLocale]]
|
||||
String m_numbering_system; // [[NumberingSystem]]
|
||||
Style m_style { Style::Invalid }; // [[Style]]
|
||||
Optional<String> m_currency {}; // [[Currency]]
|
||||
Optional<CurrencyDisplay> m_currency_display {}; // [[CurrencyDisplay]]
|
||||
Optional<CurrencySign> m_currency_sign {}; // [[CurrencySign]]
|
||||
Optional<String> m_unit {}; // [[Unit]]
|
||||
Optional<::Locale::Style> m_unit_display {}; // [[UnitDisplay]]
|
||||
UseGrouping m_use_grouping { UseGrouping::False }; // [[UseGrouping]]
|
||||
Notation m_notation { Notation::Invalid }; // [[Notation]]
|
||||
Optional<CompactDisplay> m_compact_display {}; // [[CompactDisplay]]
|
||||
SignDisplay m_sign_display { SignDisplay::Invalid }; // [[SignDisplay]]
|
||||
GCPtr<NativeFunction> m_bound_format; // [[BoundFormat]]
|
||||
|
||||
// Non-standard. Stores the resolved currency display string based on [[Locale]], [[Currency]], and [[CurrencyDisplay]].
|
||||
Optional<String> m_resolved_currency_display;
|
||||
|
||||
// Non-standard. Stores the resolved compact number format based on [[Locale]], [[Notation], [[Style]], and [[CompactDisplay]].
|
||||
Optional<::Locale::NumberFormat> m_compact_format;
|
||||
};
|
||||
|
||||
struct FormatResult {
|
||||
String formatted_string; // [[FormattedString]]
|
||||
MathematicalValue rounded_number { 0.0 }; // [[RoundedNumber]]
|
||||
};
|
||||
|
||||
struct RawFormatResult : public FormatResult {
|
||||
int digits { 0 }; // [[IntegerDigitsCount]]
|
||||
int rounding_magnitude { 0 }; // [[RoundingMagnitude]]
|
||||
};
|
||||
|
||||
enum class RoundingDecision {
|
||||
LowerValue,
|
||||
HigherValue,
|
||||
String m_locale; // [[Locale]]
|
||||
String m_data_locale; // [[DataLocale]]
|
||||
String m_numbering_system; // [[NumberingSystem]]
|
||||
::Locale::NumberFormatStyle m_style; // [[Style]]
|
||||
Optional<String> m_currency; // [[Currency]]
|
||||
Optional<::Locale::CurrencyDisplay> m_currency_display; // [[CurrencyDisplay]]
|
||||
Optional<::Locale::CurrencySign> m_currency_sign; // [[CurrencySign]]
|
||||
Optional<String> m_unit; // [[Unit]]
|
||||
Optional<::Locale::Style> m_unit_display; // [[UnitDisplay]]
|
||||
::Locale::Grouping m_use_grouping { ::Locale::Grouping::False }; // [[UseGrouping]]
|
||||
::Locale::Notation m_notation; // [[Notation]]
|
||||
Optional<::Locale::CompactDisplay> m_compact_display; // [[CompactDisplay]]
|
||||
::Locale::SignDisplay m_sign_display; // [[SignDisplay]]
|
||||
GCPtr<NativeFunction> m_bound_format; // [[BoundFormat]]
|
||||
};
|
||||
|
||||
int currency_digits(StringView currency);
|
||||
FormatResult format_numeric_to_string(NumberFormatBase const& intl_object, MathematicalValue number);
|
||||
Vector<PatternPartition> partition_number_pattern(VM&, NumberFormat&, MathematicalValue number);
|
||||
Vector<PatternPartition> partition_notation_sub_pattern(NumberFormat&, MathematicalValue const& number, String formatted_string, int exponent);
|
||||
String format_numeric(VM&, NumberFormat&, MathematicalValue number);
|
||||
NonnullGCPtr<Array> format_numeric_to_parts(VM&, NumberFormat&, MathematicalValue number);
|
||||
RawFormatResult to_raw_precision(MathematicalValue const& number, int min_precision, int max_precision, NumberFormat::UnsignedRoundingMode unsigned_rounding_mode);
|
||||
RawFormatResult to_raw_fixed(MathematicalValue const& number, int min_fraction, int max_fraction, int rounding_increment, NumberFormat::UnsignedRoundingMode unsigned_rounding_mode);
|
||||
Optional<Variant<StringView, String>> get_number_format_pattern(VM&, NumberFormat&, MathematicalValue const& number, ::Locale::NumberFormat& found_pattern);
|
||||
Optional<StringView> get_notation_sub_pattern(NumberFormat&, int exponent);
|
||||
int compute_exponent(NumberFormat&, MathematicalValue number);
|
||||
int compute_exponent_for_magnitude(NumberFormat&, int magnitude);
|
||||
String format_numeric_to_string(NumberFormatBase const& intl_object, MathematicalValue const& number);
|
||||
Vector<::Locale::NumberFormat::Partition> partition_number_pattern(NumberFormat const&, MathematicalValue const& number);
|
||||
String format_numeric(NumberFormat const&, MathematicalValue const& number);
|
||||
NonnullGCPtr<Array> format_numeric_to_parts(VM&, NumberFormat const&, MathematicalValue const& number);
|
||||
ThrowCompletionOr<MathematicalValue> to_intl_mathematical_value(VM&, Value value);
|
||||
NumberFormat::UnsignedRoundingMode get_unsigned_rounding_mode(NumberFormat::RoundingMode, bool is_negative);
|
||||
RoundingDecision apply_unsigned_rounding_mode(MathematicalValue const& x, MathematicalValue const& r1, MathematicalValue const& r2, NumberFormat::UnsignedRoundingMode unsigned_rounding_mode);
|
||||
ThrowCompletionOr<Vector<PatternPartitionWithSource>> partition_number_range_pattern(VM&, NumberFormat&, MathematicalValue start, MathematicalValue end);
|
||||
Vector<PatternPartitionWithSource> format_approximately(NumberFormat&, Vector<PatternPartitionWithSource> result);
|
||||
Vector<PatternPartitionWithSource> collapse_number_range(Vector<PatternPartitionWithSource> result);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
* Copyright (c) 2021-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -135,7 +135,7 @@ ThrowCompletionOr<NonnullGCPtr<NumberFormat>> initialize_number_format(VM& vm, N
|
|||
int default_max_fraction_digits = 0;
|
||||
|
||||
// 16. If style is "currency", then
|
||||
if (style == NumberFormat::Style::Currency) {
|
||||
if (style == ::Locale::NumberFormatStyle::Currency) {
|
||||
// a. Let currency be numberFormat.[[Currency]].
|
||||
auto const& currency = number_format.currency();
|
||||
|
||||
|
@ -157,7 +157,7 @@ ThrowCompletionOr<NonnullGCPtr<NumberFormat>> initialize_number_format(VM& vm, N
|
|||
// i. Let mxfdDefault be 0.
|
||||
// c. Else,
|
||||
// i. Let mxfdDefault be 3.
|
||||
default_max_fraction_digits = style == NumberFormat::Style::Percent ? 0 : 3;
|
||||
default_max_fraction_digits = style == ::Locale::NumberFormatStyle::Percent ? 0 : 3;
|
||||
}
|
||||
|
||||
// 18. Let notation be ? GetOption(options, "notation", string, « "standard", "scientific", "engineering", "compact" », "standard").
|
||||
|
@ -176,7 +176,7 @@ ThrowCompletionOr<NonnullGCPtr<NumberFormat>> initialize_number_format(VM& vm, N
|
|||
auto default_use_grouping = "auto"sv;
|
||||
|
||||
// 23. If notation is "compact", then
|
||||
if (number_format.notation() == NumberFormat::Notation::Compact) {
|
||||
if (number_format.notation() == ::Locale::Notation::Compact) {
|
||||
// a. Set numberFormat.[[CompactDisplay]] to compactDisplay.
|
||||
number_format.set_compact_display(compact_display.as_string().utf8_string_view());
|
||||
|
||||
|
@ -209,12 +209,20 @@ ThrowCompletionOr<NonnullGCPtr<NumberFormat>> initialize_number_format(VM& vm, N
|
|||
// 30. Set numberFormat.[[SignDisplay]] to signDisplay.
|
||||
number_format.set_sign_display(sign_display.as_string().utf8_string_view());
|
||||
|
||||
// Non-standard, create an ICU number formatter for this Intl object.
|
||||
auto formatter = ::Locale::NumberFormat::create(
|
||||
number_format.locale(),
|
||||
number_format.numbering_system(),
|
||||
number_format.display_options(),
|
||||
number_format.rounding_options());
|
||||
number_format.set_formatter(move(formatter));
|
||||
|
||||
// 31. Return numberFormat.
|
||||
return number_format;
|
||||
}
|
||||
|
||||
// 15.1.3 SetNumberFormatDigitOptions ( intlObj, options, mnfdDefault, mxfdDefault, notation ), https://tc39.es/ecma402/#sec-setnfdigitoptions
|
||||
ThrowCompletionOr<void> set_number_format_digit_options(VM& vm, NumberFormatBase& intl_object, Object const& options, int default_min_fraction_digits, int default_max_fraction_digits, NumberFormat::Notation notation)
|
||||
ThrowCompletionOr<void> set_number_format_digit_options(VM& vm, NumberFormatBase& intl_object, Object const& options, int default_min_fraction_digits, int default_max_fraction_digits, ::Locale::Notation notation)
|
||||
{
|
||||
// 1. Let mnid be ? GetNumberOption(options, "minimumIntegerDigits,", 1, 21, 1).
|
||||
auto min_integer_digits = TRY(get_number_option(vm, options, vm.names.minimumIntegerDigits, 1, 21, 1));
|
||||
|
@ -292,7 +300,7 @@ ThrowCompletionOr<void> set_number_format_digit_options(VM& vm, NumberFormatBase
|
|||
need_significant_digits = has_significant_digits;
|
||||
|
||||
// b. If hasSd is true, or hasFd is false and notation is "compact", then
|
||||
if (has_significant_digits || (!has_fraction_digits && notation == NumberFormat::Notation::Compact)) {
|
||||
if (has_significant_digits || (!has_fraction_digits && notation == ::Locale::Notation::Compact)) {
|
||||
// i. Set needFd to false.
|
||||
need_fraction_digits = false;
|
||||
}
|
||||
|
@ -371,7 +379,7 @@ ThrowCompletionOr<void> set_number_format_digit_options(VM& vm, NumberFormatBase
|
|||
intl_object.set_max_significant_digits(2);
|
||||
|
||||
// e. Set intlObj.[[RoundingType]] to morePrecision.
|
||||
intl_object.set_rounding_type(NumberFormatBase::RoundingType::MorePrecision);
|
||||
intl_object.set_rounding_type(::Locale::RoundingType::MorePrecision);
|
||||
|
||||
// f. Set intlObj.[[ComputedRoundingPriority]] to "morePrecision".
|
||||
intl_object.set_computed_rounding_priority(NumberFormatBase::ComputedRoundingPriority::MorePrecision);
|
||||
|
@ -379,7 +387,7 @@ ThrowCompletionOr<void> set_number_format_digit_options(VM& vm, NumberFormatBase
|
|||
// 27. Else if roundingPriority is "morePrecision", then
|
||||
else if (rounding_priority == "morePrecision"sv) {
|
||||
// a. Set intlObj.[[RoundingType]] to morePrecision.
|
||||
intl_object.set_rounding_type(NumberFormatBase::RoundingType::MorePrecision);
|
||||
intl_object.set_rounding_type(::Locale::RoundingType::MorePrecision);
|
||||
|
||||
// b. Set intlObj.[[ComputedRoundingPriority]] to "morePrecision".
|
||||
intl_object.set_computed_rounding_priority(NumberFormatBase::ComputedRoundingPriority::MorePrecision);
|
||||
|
@ -387,7 +395,7 @@ ThrowCompletionOr<void> set_number_format_digit_options(VM& vm, NumberFormatBase
|
|||
// 28. Else if roundingPriority is "lessPrecision", then
|
||||
else if (rounding_priority == "lessPrecision"sv) {
|
||||
// a. Set intlObj.[[RoundingType]] to lessPrecision.
|
||||
intl_object.set_rounding_type(NumberFormatBase::RoundingType::LessPrecision);
|
||||
intl_object.set_rounding_type(::Locale::RoundingType::LessPrecision);
|
||||
|
||||
// b. Set intlObj.[[ComputedRoundingPriority]] to "lessPrecision".
|
||||
intl_object.set_computed_rounding_priority(NumberFormatBase::ComputedRoundingPriority::LessPrecision);
|
||||
|
@ -395,7 +403,7 @@ ThrowCompletionOr<void> set_number_format_digit_options(VM& vm, NumberFormatBase
|
|||
// 29. Else if hasSd is true, then
|
||||
else if (has_significant_digits) {
|
||||
// a. Set intlObj.[[RoundingType]] to significantDigits.
|
||||
intl_object.set_rounding_type(NumberFormatBase::RoundingType::SignificantDigits);
|
||||
intl_object.set_rounding_type(::Locale::RoundingType::SignificantDigits);
|
||||
|
||||
// b. Set intlObj.[[ComputedRoundingPriority]] to "auto".
|
||||
intl_object.set_computed_rounding_priority(NumberFormatBase::ComputedRoundingPriority::Auto);
|
||||
|
@ -403,7 +411,7 @@ ThrowCompletionOr<void> set_number_format_digit_options(VM& vm, NumberFormatBase
|
|||
// 30. Else,
|
||||
else {
|
||||
// a. Set intlObj.[[RoundingType]] to fractionDigits.
|
||||
intl_object.set_rounding_type(NumberFormatBase::RoundingType::FractionDigits);
|
||||
intl_object.set_rounding_type(::Locale::RoundingType::FractionDigits);
|
||||
|
||||
// b. Set intlObj.[[ComputedRoundingPriority]] to "auto".
|
||||
intl_object.set_computed_rounding_priority(NumberFormatBase::ComputedRoundingPriority::Auto);
|
||||
|
@ -412,7 +420,7 @@ ThrowCompletionOr<void> set_number_format_digit_options(VM& vm, NumberFormatBase
|
|||
// 31. If roundingIncrement is not 1, then
|
||||
if (rounding_increment != 1) {
|
||||
// a. If intlObj.[[RoundingType]] is not fractionDigits, throw a TypeError exception.
|
||||
if (intl_object.rounding_type() != NumberFormatBase::RoundingType::FractionDigits)
|
||||
if (intl_object.rounding_type() != ::Locale::RoundingType::FractionDigits)
|
||||
return vm.throw_completion<TypeError>(ErrorType::IntlInvalidRoundingIncrementForRoundingType, *rounding_increment, intl_object.rounding_type_string());
|
||||
|
||||
// b. If intlObj.[[MaximumFractionDigits]] is not equal to intlObj.[[MinimumFractionDigits]], throw a RangeError exception.
|
||||
|
@ -441,7 +449,7 @@ ThrowCompletionOr<void> set_number_format_unit_options(VM& vm, NumberFormat& int
|
|||
// 6. If currency is undefined, then
|
||||
if (currency.is_undefined()) {
|
||||
// a. If style is "currency", throw a TypeError exception.
|
||||
if (intl_object.style() == NumberFormat::Style::Currency)
|
||||
if (intl_object.style() == ::Locale::NumberFormatStyle::Currency)
|
||||
return vm.throw_completion<TypeError>(ErrorType::IntlOptionUndefined, "currency"sv, "style"sv, style);
|
||||
}
|
||||
// 7. Else,
|
||||
|
@ -461,7 +469,7 @@ ThrowCompletionOr<void> set_number_format_unit_options(VM& vm, NumberFormat& int
|
|||
// 11. If unit is undefined, then
|
||||
if (unit.is_undefined()) {
|
||||
// a. If style is "unit", throw a TypeError exception.
|
||||
if (intl_object.style() == NumberFormat::Style::Unit)
|
||||
if (intl_object.style() == ::Locale::NumberFormatStyle::Unit)
|
||||
return vm.throw_completion<TypeError>(ErrorType::IntlOptionUndefined, "unit"sv, "style"sv, style);
|
||||
}
|
||||
// 12. Else,
|
||||
|
@ -473,7 +481,7 @@ ThrowCompletionOr<void> set_number_format_unit_options(VM& vm, NumberFormat& int
|
|||
auto unit_display = TRY(get_option(vm, options, vm.names.unitDisplay, OptionType::String, { "short"sv, "narrow"sv, "long"sv }, "short"sv));
|
||||
|
||||
// 14. If style is "currency", then
|
||||
if (intl_object.style() == NumberFormat::Style::Currency) {
|
||||
if (intl_object.style() == ::Locale::NumberFormatStyle::Currency) {
|
||||
// a. Set intlObj.[[Currency]] to the ASCII-uppercase of currency.
|
||||
intl_object.set_currency(MUST(currency.as_string().utf8_string().to_uppercase()));
|
||||
|
||||
|
@ -485,7 +493,7 @@ ThrowCompletionOr<void> set_number_format_unit_options(VM& vm, NumberFormat& int
|
|||
}
|
||||
|
||||
// 15. If style is "unit", then
|
||||
if (intl_object.style() == NumberFormat::Style::Unit) {
|
||||
if (intl_object.style() == ::Locale::NumberFormatStyle::Unit) {
|
||||
// a. Set intlObj.[[Unit]] to unit.
|
||||
intl_object.set_unit(unit.as_string().utf8_string());
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2022, Tim Flynn <trflynn89@serenityos.org>
|
||||
* Copyright (c) 2021-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -31,7 +31,7 @@ private:
|
|||
};
|
||||
|
||||
ThrowCompletionOr<NonnullGCPtr<NumberFormat>> initialize_number_format(VM&, NumberFormat&, Value locales_value, Value options_value);
|
||||
ThrowCompletionOr<void> set_number_format_digit_options(VM&, NumberFormatBase& intl_object, Object const& options, int default_min_fraction_digits, int default_max_fraction_digits, NumberFormat::Notation notation);
|
||||
ThrowCompletionOr<void> set_number_format_digit_options(VM&, NumberFormatBase& intl_object, Object const& options, int default_min_fraction_digits, int default_max_fraction_digits, ::Locale::Notation notation);
|
||||
ThrowCompletionOr<void> set_number_format_unit_options(VM&, NumberFormat& intl_object, Object const& options);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
* Copyright (c) 2021-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -46,7 +46,7 @@ ThrowCompletionOr<Value> NumberFormatFunction::call()
|
|||
auto mathematical_value = TRY(to_intl_mathematical_value(vm, value));
|
||||
|
||||
// 5. Return ? FormatNumeric(nf, x).
|
||||
auto formatted = format_numeric(vm, m_number_format, move(mathematical_value));
|
||||
auto formatted = format_numeric(m_number_format, move(mathematical_value));
|
||||
return PrimitiveString::create(vm, move(formatted));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2022-2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -95,12 +95,6 @@ PluralRules::PluralRules(Object& prototype)
|
|||
|
||||
// 16.5.3 ResolvePlural ( pluralRules, n ), https://tc39.es/ecma402/#sec-resolveplural
|
||||
ResolvedPlurality resolve_plural(PluralRules const& plural_rules, Value number)
|
||||
{
|
||||
return resolve_plural(plural_rules, plural_rules.type(), number);
|
||||
}
|
||||
|
||||
// Non-standard overload of ResolvePlural to allow using the AO without an Intl.PluralRules object.
|
||||
ResolvedPlurality resolve_plural(NumberFormatBase const& number_format, ::Locale::PluralForm type, Value number)
|
||||
{
|
||||
// 1. Assert: Type(pluralRules) is Object.
|
||||
// 2. Assert: pluralRules has an [[InitializedPluralRules]] internal slot.
|
||||
|
@ -113,15 +107,16 @@ ResolvedPlurality resolve_plural(NumberFormatBase const& number_format, ::Locale
|
|||
}
|
||||
|
||||
// 5. Let locale be pluralRules.[[Locale]].
|
||||
auto const& locale = number_format.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(number_format, number);
|
||||
auto result = format_numeric_to_string(plural_rules, number);
|
||||
|
||||
// 8. Let s be res.[[FormattedString]].
|
||||
auto string = move(result.formatted_string);
|
||||
auto string = move(result);
|
||||
|
||||
// 9. Let operands be ! GetOperands(s).
|
||||
auto operands = get_operands(string);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2022-2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -40,7 +40,6 @@ struct ResolvedPlurality {
|
|||
::Locale::PluralOperands get_operands(StringView string);
|
||||
::Locale::PluralCategory plural_rule_select(StringView locale, ::Locale::PluralForm type, Value number, ::Locale::PluralOperands operands);
|
||||
ResolvedPlurality resolve_plural(PluralRules const&, Value number);
|
||||
ResolvedPlurality resolve_plural(NumberFormatBase const& number_format, ::Locale::PluralForm type, Value number);
|
||||
::Locale::PluralCategory plural_rule_select_range(StringView locale, ::Locale::PluralForm, ::Locale::PluralCategory start, ::Locale::PluralCategory end);
|
||||
ThrowCompletionOr<::Locale::PluralCategory> resolve_plural_range(VM&, PluralRules const&, Value start, Value end);
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2022-2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -99,7 +99,7 @@ ThrowCompletionOr<NonnullGCPtr<PluralRules>> initialize_plural_rules(VM& vm, Plu
|
|||
plural_rules.set_type(type.as_string().utf8_string_view());
|
||||
|
||||
// 8. Perform ? SetNumberFormatDigitOptions(pluralRules, options, +0𝔽, 3𝔽, "standard").
|
||||
TRY(set_number_format_digit_options(vm, plural_rules, *options, 0, 3, NumberFormat::Notation::Standard));
|
||||
TRY(set_number_format_digit_options(vm, plural_rules, *options, 0, 3, ::Locale::Notation::Standard));
|
||||
|
||||
// 9. Let localeData be %PluralRules%.[[LocaleData]].
|
||||
// 10. Let r be ResolveLocale(%PluralRules%.[[AvailableLocales]], requestedLocales, opt, %PluralRules%.[[RelevantExtensionKeys]], localeData).
|
||||
|
@ -111,6 +111,14 @@ ThrowCompletionOr<NonnullGCPtr<PluralRules>> initialize_plural_rules(VM& vm, Plu
|
|||
// Non-standard, the data locale is used by our NumberFormat implementation.
|
||||
plural_rules.set_data_locale(move(result.data_locale));
|
||||
|
||||
// Non-standard, create an ICU number formatter for this Intl object.
|
||||
auto formatter = ::Locale::NumberFormat::create(
|
||||
plural_rules.locale(),
|
||||
{},
|
||||
{},
|
||||
plural_rules.rounding_options());
|
||||
plural_rules.set_formatter(move(formatter));
|
||||
|
||||
// 12. Return pluralRules.
|
||||
return plural_rules;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2022-2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -176,7 +176,7 @@ ThrowCompletionOr<Vector<PatternPartitionWithUnit>> partition_relative_time_patt
|
|||
auto patterns = find_patterns_for_tense_or_number(tense);
|
||||
|
||||
// 20. Let fv be ! PartitionNumberPattern(relativeTimeFormat.[[NumberFormat]], value).
|
||||
auto value_partitions = partition_number_pattern(vm, relative_time_format.number_format(), Value(value));
|
||||
auto value_partitions = partition_number_pattern(relative_time_format.number_format(), Value(value));
|
||||
|
||||
// 21. Let pr be ! ResolvePlural(relativeTimeFormat.[[PluralRules]], value).[[PluralCategory]].
|
||||
auto plurality = resolve_plural(relative_time_format.plural_rules(), Value(value));
|
||||
|
@ -191,7 +191,7 @@ ThrowCompletionOr<Vector<PatternPartitionWithUnit>> partition_relative_time_patt
|
|||
}
|
||||
|
||||
// 17.5.3 MakePartsList ( pattern, unit, parts ), https://tc39.es/ecma402/#sec-makepartslist
|
||||
Vector<PatternPartitionWithUnit> make_parts_list(StringView pattern, StringView unit, Vector<PatternPartition> parts)
|
||||
Vector<PatternPartitionWithUnit> make_parts_list(StringView pattern, StringView unit, Vector<::Locale::NumberFormat::Partition> parts)
|
||||
{
|
||||
// 1. Let patternParts be PartitionPattern(pattern).
|
||||
auto pattern_parts = partition_pattern(pattern);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
|
||||
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -13,6 +13,7 @@
|
|||
#include <LibJS/Runtime/Intl/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Object.h>
|
||||
#include <LibLocale/Locale.h>
|
||||
#include <LibLocale/NumberFormat.h>
|
||||
#include <LibLocale/RelativeTimeFormat.h>
|
||||
|
||||
namespace JS::Intl {
|
||||
|
@ -85,7 +86,7 @@ struct PatternPartitionWithUnit : public PatternPartition {
|
|||
|
||||
ThrowCompletionOr<::Locale::TimeUnit> singular_relative_time_unit(VM&, StringView unit);
|
||||
ThrowCompletionOr<Vector<PatternPartitionWithUnit>> partition_relative_time_pattern(VM&, RelativeTimeFormat&, double value, StringView unit);
|
||||
Vector<PatternPartitionWithUnit> make_parts_list(StringView pattern, StringView unit, Vector<PatternPartition> parts);
|
||||
Vector<PatternPartitionWithUnit> make_parts_list(StringView pattern, StringView unit, Vector<::Locale::NumberFormat::Partition> parts);
|
||||
ThrowCompletionOr<String> format_relative_time(VM&, RelativeTimeFormat&, double value, StringView unit);
|
||||
ThrowCompletionOr<NonnullGCPtr<Array>> format_relative_time_to_parts(VM&, RelativeTimeFormat&, double value, StringView unit);
|
||||
|
||||
|
|
|
@ -275,7 +275,7 @@ JS_DEFINE_NATIVE_FUNCTION(NumberPrototype::to_locale_string)
|
|||
auto* number_format = static_cast<Intl::NumberFormat*>(TRY(construct(vm, realm.intrinsics().intl_number_format_constructor(), locales, options)).ptr());
|
||||
|
||||
// 3. Return ? FormatNumeric(numberFormat, x).
|
||||
auto formatted = Intl::format_numeric(vm, *number_format, number_value);
|
||||
auto formatted = Intl::format_numeric(*number_format, number_value);
|
||||
return PrimitiveString::create(vm, move(formatted));
|
||||
}
|
||||
|
||||
|
|
|
@ -1065,13 +1065,13 @@ describe("style=percent", () => {
|
|||
expect(enFullwide.format(0.1234)).toBe("12%");
|
||||
|
||||
const ar = new Intl.NumberFormat("ar", { style: "percent", notation: "compact" });
|
||||
expect(ar.format(0.01)).toBe("\u0661\u066a\u061c");
|
||||
expect(ar.format(0.012)).toBe("\u0661\u066b\u0662\u066a\u061c");
|
||||
expect(ar.format(0.0123)).toBe("\u0661\u066b\u0662\u066a\u061c");
|
||||
expect(ar.format(0.0129)).toBe("\u0661\u066b\u0663\u066a\u061c");
|
||||
expect(ar.format(0.12)).toBe("\u0661\u0662\u066a\u061c");
|
||||
expect(ar.format(0.123)).toBe("\u0661\u0662\u066a\u061c");
|
||||
expect(ar.format(0.1234)).toBe("\u0661\u0662\u066a\u061c");
|
||||
expect(ar.format(0.01)).toBe("\u0661\u066a");
|
||||
expect(ar.format(0.012)).toBe("\u0661\u066b\u0662\u066a");
|
||||
expect(ar.format(0.0123)).toBe("\u0661\u066b\u0662\u066a");
|
||||
expect(ar.format(0.0129)).toBe("\u0661\u066b\u0663\u066a");
|
||||
expect(ar.format(0.12)).toBe("\u0661\u0662\u066a");
|
||||
expect(ar.format(0.123)).toBe("\u0661\u0662\u066a");
|
||||
expect(ar.format(0.1234)).toBe("\u0661\u0662\u066a");
|
||||
});
|
||||
|
||||
test("signDisplay=never", () => {
|
||||
|
@ -1322,14 +1322,14 @@ describe("style=currency", () => {
|
|||
compactDisplay: "long",
|
||||
});
|
||||
expect(ar.format(1)).toBe("١ US$");
|
||||
expect(ar.format(1200)).toBe("١٫٢ ألف US$");
|
||||
expect(ar.format(1290)).toBe("١٫٣ ألف US$");
|
||||
expect(ar.format(12000)).toBe("١٢ ألف US$");
|
||||
expect(ar.format(12900)).toBe("١٣ ألف US$");
|
||||
expect(ar.format(1200000)).toBe("١٫٢ مليون US$");
|
||||
expect(ar.format(1290000)).toBe("١٫٣ مليون US$");
|
||||
expect(ar.format(12000000)).toBe("١٢ مليون US$");
|
||||
expect(ar.format(12900000)).toBe("١٣ مليون US$");
|
||||
expect(ar.format(1200)).toBe("١٫٢ ألف US$");
|
||||
expect(ar.format(1290)).toBe("١٫٣ ألف US$");
|
||||
expect(ar.format(12000)).toBe("١٢ ألف US$");
|
||||
expect(ar.format(12900)).toBe("١٣ ألف US$");
|
||||
expect(ar.format(1200000)).toBe("١٫٢ مليون US$");
|
||||
expect(ar.format(1290000)).toBe("١٫٣ مليون US$");
|
||||
expect(ar.format(12000000)).toBe("١٢ مليون US$");
|
||||
expect(ar.format(12900000)).toBe("١٣ مليون US$");
|
||||
|
||||
const ja = new Intl.NumberFormat("ja", {
|
||||
style: "currency",
|
||||
|
@ -1392,14 +1392,14 @@ describe("style=currency", () => {
|
|||
compactDisplay: "short",
|
||||
});
|
||||
expect(ar.format(1)).toBe("١ US$");
|
||||
expect(ar.format(1200)).toBe("١٫٢ ألف US$");
|
||||
expect(ar.format(1290)).toBe("١٫٣ ألف US$");
|
||||
expect(ar.format(12000)).toBe("١٢ ألف US$");
|
||||
expect(ar.format(12900)).toBe("١٣ ألف US$");
|
||||
expect(ar.format(1200000)).toBe("١٫٢ مليون US$");
|
||||
expect(ar.format(1290000)).toBe("١٫٣ مليون US$");
|
||||
expect(ar.format(12000000)).toBe("١٢ مليون US$");
|
||||
expect(ar.format(12900000)).toBe("١٣ مليون US$");
|
||||
expect(ar.format(1200)).toBe("١٫٢ ألف US$");
|
||||
expect(ar.format(1290)).toBe("١٫٣ ألف US$");
|
||||
expect(ar.format(12000)).toBe("١٢ ألف US$");
|
||||
expect(ar.format(12900)).toBe("١٣ ألف US$");
|
||||
expect(ar.format(1200000)).toBe("١٫٢ مليون US$");
|
||||
expect(ar.format(1290000)).toBe("١٫٣ مليون US$");
|
||||
expect(ar.format(12000000)).toBe("١٢ مليون US$");
|
||||
expect(ar.format(12900000)).toBe("١٣ مليون US$");
|
||||
|
||||
const ja = new Intl.NumberFormat("ja", {
|
||||
style: "currency",
|
||||
|
|
|
@ -36,7 +36,8 @@ describe("special values", () => {
|
|||
const ar = new Intl.NumberFormat("ar");
|
||||
expect(ar.formatToParts(Infinity)).toEqual([{ type: "infinity", value: "∞" }]);
|
||||
expect(ar.formatToParts(-Infinity)).toEqual([
|
||||
{ type: "minusSign", value: "\u061c-" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "minusSign", value: "-" },
|
||||
{ type: "infinity", value: "∞" },
|
||||
]);
|
||||
});
|
||||
|
@ -98,11 +99,13 @@ describe("style=decimal", () => {
|
|||
expect(ar.formatToParts(0)).toEqual([{ type: "integer", value: "\u0660" }]);
|
||||
expect(ar.formatToParts(1)).toEqual([{ type: "integer", value: "\u0661" }]);
|
||||
expect(ar.formatToParts(-0)).toEqual([
|
||||
{ type: "minusSign", value: "\u061c-" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "minusSign", value: "-" },
|
||||
{ type: "integer", value: "\u0660" },
|
||||
]);
|
||||
expect(ar.formatToParts(-1)).toEqual([
|
||||
{ type: "minusSign", value: "\u061c-" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "minusSign", value: "-" },
|
||||
{ type: "integer", value: "\u0661" },
|
||||
]);
|
||||
});
|
||||
|
@ -128,19 +131,23 @@ describe("style=decimal", () => {
|
|||
|
||||
const ar = new Intl.NumberFormat("ar", { signDisplay: "always" });
|
||||
expect(ar.formatToParts(0)).toEqual([
|
||||
{ type: "plusSign", value: "\u061c+" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "plusSign", value: "+" },
|
||||
{ type: "integer", value: "\u0660" },
|
||||
]);
|
||||
expect(ar.formatToParts(1)).toEqual([
|
||||
{ type: "plusSign", value: "\u061c+" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "plusSign", value: "+" },
|
||||
{ type: "integer", value: "\u0661" },
|
||||
]);
|
||||
expect(ar.formatToParts(-0)).toEqual([
|
||||
{ type: "minusSign", value: "\u061c-" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "minusSign", value: "-" },
|
||||
{ type: "integer", value: "\u0660" },
|
||||
]);
|
||||
expect(ar.formatToParts(-1)).toEqual([
|
||||
{ type: "minusSign", value: "\u061c-" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "minusSign", value: "-" },
|
||||
{ type: "integer", value: "\u0661" },
|
||||
]);
|
||||
});
|
||||
|
@ -161,12 +168,14 @@ describe("style=decimal", () => {
|
|||
const ar = new Intl.NumberFormat("ar", { signDisplay: "exceptZero" });
|
||||
expect(ar.formatToParts(0)).toEqual([{ type: "integer", value: "\u0660" }]);
|
||||
expect(ar.formatToParts(1)).toEqual([
|
||||
{ type: "plusSign", value: "\u061c+" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "plusSign", value: "+" },
|
||||
{ type: "integer", value: "\u0661" },
|
||||
]);
|
||||
expect(ar.formatToParts(-0)).toEqual([{ type: "integer", value: "\u0660" }]);
|
||||
expect(ar.formatToParts(-1)).toEqual([
|
||||
{ type: "minusSign", value: "\u061c-" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "minusSign", value: "-" },
|
||||
{ type: "integer", value: "\u0661" },
|
||||
]);
|
||||
});
|
||||
|
@ -186,7 +195,8 @@ describe("style=decimal", () => {
|
|||
expect(ar.formatToParts(1)).toEqual([{ type: "integer", value: "\u0661" }]);
|
||||
expect(ar.formatToParts(-0)).toEqual([{ type: "integer", value: "\u0660" }]);
|
||||
expect(ar.formatToParts(-1)).toEqual([
|
||||
{ type: "minusSign", value: "\u061c-" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "minusSign", value: "-" },
|
||||
{ type: "integer", value: "\u0661" },
|
||||
]);
|
||||
});
|
||||
|
@ -330,7 +340,8 @@ describe("style=decimal", () => {
|
|||
{ type: "decimal", value: "\u066b" },
|
||||
{ type: "fraction", value: "\u0662" },
|
||||
{ type: "exponentSeparator", value: "\u0623\u0633" },
|
||||
{ type: "exponentMinusSign", value: "\u061c-" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "exponentMinusSign", value: "-" },
|
||||
{ type: "exponentInteger", value: "\u0661" },
|
||||
]);
|
||||
});
|
||||
|
@ -362,7 +373,8 @@ describe("style=decimal", () => {
|
|||
expect(ar.formatToParts(0.12)).toEqual([
|
||||
{ type: "integer", value: "\u0661\u0662\u0660" },
|
||||
{ type: "exponentSeparator", value: "\u0623\u0633" },
|
||||
{ type: "exponentMinusSign", value: "\u061c-" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "exponentMinusSign", value: "-" },
|
||||
{ type: "exponentInteger", value: "\u0663" },
|
||||
]);
|
||||
});
|
||||
|
@ -447,13 +459,15 @@ describe("style=percent", () => {
|
|||
{ type: "integer", value: "\u0661\u0660\u0660" },
|
||||
{ type: "decimal", value: "\u066b" },
|
||||
{ type: "fraction", value: "\u0660\u0660" },
|
||||
{ type: "percentSign", value: "\u066a\u061c" },
|
||||
{ type: "percentSign", value: "\u066a" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
]);
|
||||
expect(ar.formatToParts(1.2345)).toEqual([
|
||||
{ type: "integer", value: "\u0661\u0662\u0663" },
|
||||
{ type: "decimal", value: "\u066b" },
|
||||
{ type: "fraction", value: "\u0664\u0665" },
|
||||
{ type: "percentSign", value: "\u066a\u061c" },
|
||||
{ type: "percentSign", value: "\u066a" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -471,11 +485,13 @@ describe("style=percent", () => {
|
|||
const ar = new Intl.NumberFormat("ar", { style: "percent", signDisplay: "never" });
|
||||
expect(ar.formatToParts(0.01)).toEqual([
|
||||
{ type: "integer", value: "\u0661" },
|
||||
{ type: "percentSign", value: "\u066a\u061c" },
|
||||
{ type: "percentSign", value: "\u066a" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
]);
|
||||
expect(ar.formatToParts(-0.01)).toEqual([
|
||||
{ type: "integer", value: "\u0661" },
|
||||
{ type: "percentSign", value: "\u066a\u061c" },
|
||||
{ type: "percentSign", value: "\u066a" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -503,21 +519,27 @@ describe("style=percent", () => {
|
|||
const ar = new Intl.NumberFormat("ar", { style: "percent", signDisplay: "auto" });
|
||||
expect(ar.formatToParts(0.0)).toEqual([
|
||||
{ type: "integer", value: "\u0660" },
|
||||
{ type: "percentSign", value: "\u066a\u061c" },
|
||||
{ type: "percentSign", value: "\u066a" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
]);
|
||||
expect(ar.formatToParts(0.01)).toEqual([
|
||||
{ type: "integer", value: "\u0661" },
|
||||
{ type: "percentSign", value: "\u066a\u061c" },
|
||||
{ type: "percentSign", value: "\u066a" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
]);
|
||||
expect(ar.formatToParts(-0.0)).toEqual([
|
||||
{ type: "minusSign", value: "\u061c-" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "minusSign", value: "-" },
|
||||
{ type: "integer", value: "\u0660" },
|
||||
{ type: "percentSign", value: "\u066a\u061c" },
|
||||
{ type: "percentSign", value: "\u066a" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
]);
|
||||
expect(ar.formatToParts(-0.01)).toEqual([
|
||||
{ type: "minusSign", value: "\u061c-" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "minusSign", value: "-" },
|
||||
{ type: "integer", value: "\u0661" },
|
||||
{ type: "percentSign", value: "\u066a\u061c" },
|
||||
{ type: "percentSign", value: "\u066a" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -546,24 +568,32 @@ describe("style=percent", () => {
|
|||
|
||||
const ar = new Intl.NumberFormat("ar", { style: "percent", signDisplay: "always" });
|
||||
expect(ar.formatToParts(0.0)).toEqual([
|
||||
{ type: "plusSign", value: "\u061c+" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "plusSign", value: "+" },
|
||||
{ type: "integer", value: "\u0660" },
|
||||
{ type: "percentSign", value: "\u066a\u061c" },
|
||||
{ type: "percentSign", value: "\u066a" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
]);
|
||||
expect(ar.formatToParts(0.01)).toEqual([
|
||||
{ type: "plusSign", value: "\u061c+" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "plusSign", value: "+" },
|
||||
{ type: "integer", value: "\u0661" },
|
||||
{ type: "percentSign", value: "\u066a\u061c" },
|
||||
{ type: "percentSign", value: "\u066a" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
]);
|
||||
expect(ar.formatToParts(-0.0)).toEqual([
|
||||
{ type: "minusSign", value: "\u061c-" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "minusSign", value: "-" },
|
||||
{ type: "integer", value: "\u0660" },
|
||||
{ type: "percentSign", value: "\u066a\u061c" },
|
||||
{ type: "percentSign", value: "\u066a" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
]);
|
||||
expect(ar.formatToParts(-0.01)).toEqual([
|
||||
{ type: "minusSign", value: "\u061c-" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "minusSign", value: "-" },
|
||||
{ type: "integer", value: "\u0661" },
|
||||
{ type: "percentSign", value: "\u066a\u061c" },
|
||||
{ type: "percentSign", value: "\u066a" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -591,21 +621,27 @@ describe("style=percent", () => {
|
|||
const ar = new Intl.NumberFormat("ar", { style: "percent", signDisplay: "exceptZero" });
|
||||
expect(ar.formatToParts(0.0)).toEqual([
|
||||
{ type: "integer", value: "\u0660" },
|
||||
{ type: "percentSign", value: "\u066a\u061c" },
|
||||
{ type: "percentSign", value: "\u066a" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
]);
|
||||
expect(ar.formatToParts(0.01)).toEqual([
|
||||
{ type: "plusSign", value: "\u061c+" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "plusSign", value: "+" },
|
||||
{ type: "integer", value: "\u0661" },
|
||||
{ type: "percentSign", value: "\u066a\u061c" },
|
||||
{ type: "percentSign", value: "\u066a" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
]);
|
||||
expect(ar.formatToParts(-0.0)).toEqual([
|
||||
{ type: "integer", value: "\u0660" },
|
||||
{ type: "percentSign", value: "\u066a\u061c" },
|
||||
{ type: "percentSign", value: "\u066a" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
]);
|
||||
expect(ar.formatToParts(-0.01)).toEqual([
|
||||
{ type: "minusSign", value: "\u061c-" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "minusSign", value: "-" },
|
||||
{ type: "integer", value: "\u0661" },
|
||||
{ type: "percentSign", value: "\u066a\u061c" },
|
||||
{ type: "percentSign", value: "\u066a" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -632,20 +668,25 @@ describe("style=percent", () => {
|
|||
const ar = new Intl.NumberFormat("ar", { style: "percent", signDisplay: "negative" });
|
||||
expect(ar.formatToParts(0.0)).toEqual([
|
||||
{ type: "integer", value: "\u0660" },
|
||||
{ type: "percentSign", value: "\u066a\u061c" },
|
||||
{ type: "percentSign", value: "\u066a" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
]);
|
||||
expect(ar.formatToParts(0.01)).toEqual([
|
||||
{ type: "integer", value: "\u0661" },
|
||||
{ type: "percentSign", value: "\u066a\u061c" },
|
||||
{ type: "percentSign", value: "\u066a" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
]);
|
||||
expect(ar.formatToParts(-0.0)).toEqual([
|
||||
{ type: "integer", value: "\u0660" },
|
||||
{ type: "percentSign", value: "\u066a\u061c" },
|
||||
{ type: "percentSign", value: "\u066a" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
]);
|
||||
expect(ar.formatToParts(-0.01)).toEqual([
|
||||
{ type: "minusSign", value: "\u061c-" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "minusSign", value: "-" },
|
||||
{ type: "integer", value: "\u0661" },
|
||||
{ type: "percentSign", value: "\u066a\u061c" },
|
||||
{ type: "percentSign", value: "\u066a" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -937,7 +978,8 @@ describe("style=currency", () => {
|
|||
{ type: "currency", value: "US$" },
|
||||
]);
|
||||
expect(ar.formatToParts(-0)).toEqual([
|
||||
{ type: "minusSign", value: "\u061c-" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "minusSign", value: "-" },
|
||||
{ type: "literal", value: "\u200f" },
|
||||
{ type: "integer", value: "\u0660" },
|
||||
{ type: "decimal", value: "\u066b" },
|
||||
|
@ -946,7 +988,8 @@ describe("style=currency", () => {
|
|||
{ type: "currency", value: "US$" },
|
||||
]);
|
||||
expect(ar.formatToParts(-1)).toEqual([
|
||||
{ type: "minusSign", value: "\u061c-" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "minusSign", value: "-" },
|
||||
{ type: "literal", value: "\u200f" },
|
||||
{ type: "integer", value: "\u0661" },
|
||||
{ type: "decimal", value: "\u066b" },
|
||||
|
@ -1032,7 +1075,8 @@ describe("style=currency", () => {
|
|||
signDisplay: "always",
|
||||
});
|
||||
expect(ar.formatToParts(0)).toEqual([
|
||||
{ type: "plusSign", value: "\u061c+" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "plusSign", value: "+" },
|
||||
{ type: "literal", value: "\u200f" },
|
||||
{ type: "integer", value: "\u0660" },
|
||||
{ type: "decimal", value: "\u066b" },
|
||||
|
@ -1041,7 +1085,8 @@ describe("style=currency", () => {
|
|||
{ type: "currency", value: "US$" },
|
||||
]);
|
||||
expect(ar.formatToParts(1)).toEqual([
|
||||
{ type: "plusSign", value: "\u061c+" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "plusSign", value: "+" },
|
||||
{ type: "literal", value: "\u200f" },
|
||||
{ type: "integer", value: "\u0661" },
|
||||
{ type: "decimal", value: "\u066b" },
|
||||
|
@ -1050,7 +1095,8 @@ describe("style=currency", () => {
|
|||
{ type: "currency", value: "US$" },
|
||||
]);
|
||||
expect(ar.formatToParts(-0)).toEqual([
|
||||
{ type: "minusSign", value: "\u061c-" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "minusSign", value: "-" },
|
||||
{ type: "literal", value: "\u200f" },
|
||||
{ type: "integer", value: "\u0660" },
|
||||
{ type: "decimal", value: "\u066b" },
|
||||
|
@ -1059,7 +1105,8 @@ describe("style=currency", () => {
|
|||
{ type: "currency", value: "US$" },
|
||||
]);
|
||||
expect(ar.formatToParts(-1)).toEqual([
|
||||
{ type: "minusSign", value: "\u061c-" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "minusSign", value: "-" },
|
||||
{ type: "literal", value: "\u200f" },
|
||||
{ type: "integer", value: "\u0661" },
|
||||
{ type: "decimal", value: "\u066b" },
|
||||
|
@ -1153,7 +1200,8 @@ describe("style=currency", () => {
|
|||
{ type: "currency", value: "US$" },
|
||||
]);
|
||||
expect(ar.formatToParts(1)).toEqual([
|
||||
{ type: "plusSign", value: "\u061c+" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "plusSign", value: "+" },
|
||||
{ type: "literal", value: "\u200f" },
|
||||
{ type: "integer", value: "\u0661" },
|
||||
{ type: "decimal", value: "\u066b" },
|
||||
|
@ -1170,7 +1218,8 @@ describe("style=currency", () => {
|
|||
{ type: "currency", value: "US$" },
|
||||
]);
|
||||
expect(ar.formatToParts(-1)).toEqual([
|
||||
{ type: "minusSign", value: "\u061c-" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "minusSign", value: "-" },
|
||||
{ type: "literal", value: "\u200f" },
|
||||
{ type: "integer", value: "\u0661" },
|
||||
{ type: "decimal", value: "\u066b" },
|
||||
|
@ -1276,7 +1325,8 @@ describe("style=currency", () => {
|
|||
{ type: "currency", value: "US$" },
|
||||
]);
|
||||
expect(ar.formatToParts(-1)).toEqual([
|
||||
{ type: "minusSign", value: "\u061c-" },
|
||||
{ type: "literal", value: "\u061c" },
|
||||
{ type: "minusSign", value: "-" },
|
||||
{ type: "literal", value: "\u200f" },
|
||||
{ type: "integer", value: "\u0661" },
|
||||
{ type: "decimal", value: "\u066b" },
|
||||
|
|
|
@ -13,7 +13,6 @@ namespace Locale {
|
|||
enum class CalendarFormatType : u8;
|
||||
enum class CalendarPatternStyle : u8;
|
||||
enum class CalendarSymbol : u8;
|
||||
enum class CompactNumberFormatType : u8;
|
||||
enum class DayPeriod : u8;
|
||||
enum class Era : u8;
|
||||
enum class FirstDayRegion : u8;
|
||||
|
@ -31,12 +30,13 @@ enum class MinimumDaysRegion : u8;
|
|||
enum class Month : u8;
|
||||
enum class NumericSymbol : u8;
|
||||
enum class PluralCategory : u8;
|
||||
enum class StandardNumberFormatType : u8;
|
||||
enum class Style : u8;
|
||||
enum class Weekday : u8;
|
||||
enum class WeekendEndRegion : u8;
|
||||
enum class WeekendStartRegion : u8;
|
||||
|
||||
class NumberFormat;
|
||||
|
||||
struct CalendarFormat;
|
||||
struct CalendarPattern;
|
||||
struct CalendarRangePattern;
|
||||
|
@ -45,8 +45,6 @@ struct LanguageID;
|
|||
struct ListFormatPart;
|
||||
struct LocaleExtension;
|
||||
struct LocaleID;
|
||||
struct NumberFormat;
|
||||
struct NumberGroupings;
|
||||
struct OtherExtension;
|
||||
struct PluralOperands;
|
||||
struct TransformedExtension;
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
* Copyright (c) 2021-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#define AK_DONT_REPLACE_STD
|
||||
|
||||
#include <AK/CharacterTypes.h>
|
||||
#include <AK/QuickSort.h>
|
||||
#include <AK/Utf8View.h>
|
||||
#include <LibLocale/ICU.h>
|
||||
#include <LibLocale/Locale.h>
|
||||
#include <LibLocale/NumberFormat.h>
|
||||
#include <LibUnicode/CharacterTypes.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <unicode/numberformatter.h>
|
||||
|
||||
#if ENABLE_UNICODE_DATA
|
||||
# include <LibUnicode/UnicodeData.h>
|
||||
|
@ -16,11 +23,718 @@
|
|||
|
||||
namespace Locale {
|
||||
|
||||
NumberFormatStyle number_format_style_from_string(StringView number_format_style)
|
||||
{
|
||||
if (number_format_style == "decimal"sv)
|
||||
return NumberFormatStyle::Decimal;
|
||||
if (number_format_style == "percent"sv)
|
||||
return NumberFormatStyle::Percent;
|
||||
if (number_format_style == "currency"sv)
|
||||
return NumberFormatStyle::Currency;
|
||||
if (number_format_style == "unit"sv)
|
||||
return NumberFormatStyle::Unit;
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
StringView number_format_style_to_string(NumberFormatStyle number_format_style)
|
||||
{
|
||||
switch (number_format_style) {
|
||||
case NumberFormatStyle::Decimal:
|
||||
return "decimal"sv;
|
||||
case NumberFormatStyle::Percent:
|
||||
return "percent"sv;
|
||||
case NumberFormatStyle::Currency:
|
||||
return "currency"sv;
|
||||
case NumberFormatStyle::Unit:
|
||||
return "unit"sv;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
SignDisplay sign_display_from_string(StringView sign_display)
|
||||
{
|
||||
if (sign_display == "auto"sv)
|
||||
return SignDisplay::Auto;
|
||||
if (sign_display == "never"sv)
|
||||
return SignDisplay::Never;
|
||||
if (sign_display == "always"sv)
|
||||
return SignDisplay::Always;
|
||||
if (sign_display == "exceptZero"sv)
|
||||
return SignDisplay::ExceptZero;
|
||||
if (sign_display == "negative"sv)
|
||||
return SignDisplay::Negative;
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
StringView sign_display_to_string(SignDisplay sign_display)
|
||||
{
|
||||
switch (sign_display) {
|
||||
case SignDisplay::Auto:
|
||||
return "auto"sv;
|
||||
case SignDisplay::Never:
|
||||
return "never"sv;
|
||||
case SignDisplay::Always:
|
||||
return "always"sv;
|
||||
case SignDisplay::ExceptZero:
|
||||
return "exceptZero"sv;
|
||||
case SignDisplay::Negative:
|
||||
return "negative"sv;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static constexpr UNumberSignDisplay icu_sign_display(SignDisplay sign_display, Optional<CurrencySign> const& currency_sign)
|
||||
{
|
||||
switch (sign_display) {
|
||||
case SignDisplay::Auto:
|
||||
return currency_sign == CurrencySign::Standard ? UNUM_SIGN_AUTO : UNUM_SIGN_ACCOUNTING;
|
||||
case SignDisplay::Never:
|
||||
return UNUM_SIGN_NEVER;
|
||||
case SignDisplay::Always:
|
||||
return currency_sign == CurrencySign::Standard ? UNUM_SIGN_ALWAYS : UNUM_SIGN_ACCOUNTING_ALWAYS;
|
||||
case SignDisplay::ExceptZero:
|
||||
return currency_sign == CurrencySign::Standard ? UNUM_SIGN_EXCEPT_ZERO : UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO;
|
||||
case SignDisplay::Negative:
|
||||
return currency_sign == CurrencySign::Standard ? UNUM_SIGN_NEGATIVE : UNUM_SIGN_ACCOUNTING_NEGATIVE;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
Notation notation_from_string(StringView notation)
|
||||
{
|
||||
if (notation == "standard"sv)
|
||||
return Notation::Standard;
|
||||
if (notation == "scientific"sv)
|
||||
return Notation::Scientific;
|
||||
if (notation == "engineering"sv)
|
||||
return Notation::Engineering;
|
||||
if (notation == "compact"sv)
|
||||
return Notation::Compact;
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
StringView notation_to_string(Notation notation)
|
||||
{
|
||||
switch (notation) {
|
||||
case Notation::Standard:
|
||||
return "standard"sv;
|
||||
case Notation::Scientific:
|
||||
return "scientific"sv;
|
||||
case Notation::Engineering:
|
||||
return "engineering"sv;
|
||||
case Notation::Compact:
|
||||
return "compact"sv;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static icu::number::Notation icu_notation(Notation notation, Optional<CompactDisplay> const& compact_display)
|
||||
{
|
||||
switch (notation) {
|
||||
case Notation::Standard:
|
||||
return icu::number::Notation::simple();
|
||||
case Notation::Scientific:
|
||||
return icu::number::Notation::scientific();
|
||||
case Notation::Engineering:
|
||||
return icu::number::Notation::engineering();
|
||||
case Notation::Compact:
|
||||
switch (*compact_display) {
|
||||
case CompactDisplay::Short:
|
||||
return icu::number::Notation::compactShort();
|
||||
case CompactDisplay::Long:
|
||||
return icu::number::Notation::compactLong();
|
||||
}
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
CompactDisplay compact_display_from_string(StringView compact_display)
|
||||
{
|
||||
if (compact_display == "short"sv)
|
||||
return CompactDisplay::Short;
|
||||
if (compact_display == "long"sv)
|
||||
return CompactDisplay::Long;
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
StringView compact_display_to_string(CompactDisplay compact_display)
|
||||
{
|
||||
switch (compact_display) {
|
||||
case CompactDisplay::Short:
|
||||
return "short"sv;
|
||||
case CompactDisplay::Long:
|
||||
return "long"sv;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
Grouping grouping_from_string(StringView grouping)
|
||||
{
|
||||
if (grouping == "always"sv)
|
||||
return Grouping::Always;
|
||||
if (grouping == "auto"sv)
|
||||
return Grouping::Auto;
|
||||
if (grouping == "min2"sv)
|
||||
return Grouping::Min2;
|
||||
if (grouping == "false"sv)
|
||||
return Grouping::False;
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
StringView grouping_to_string(Grouping grouping)
|
||||
{
|
||||
switch (grouping) {
|
||||
case Grouping::Always:
|
||||
return "always"sv;
|
||||
case Grouping::Auto:
|
||||
return "auto"sv;
|
||||
case Grouping::Min2:
|
||||
return "min2"sv;
|
||||
case Grouping::False:
|
||||
return "false"sv;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static constexpr UNumberGroupingStrategy icu_grouping_strategy(Grouping grouping)
|
||||
{
|
||||
switch (grouping) {
|
||||
case Grouping::Always:
|
||||
return UNUM_GROUPING_ON_ALIGNED;
|
||||
case Grouping::Auto:
|
||||
return UNUM_GROUPING_AUTO;
|
||||
case Grouping::Min2:
|
||||
return UNUM_GROUPING_MIN2;
|
||||
case Grouping::False:
|
||||
return UNUM_GROUPING_OFF;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
CurrencyDisplay currency_display_from_string(StringView currency_display)
|
||||
{
|
||||
if (currency_display == "code"sv)
|
||||
return CurrencyDisplay::Code;
|
||||
if (currency_display == "symbol"sv)
|
||||
return CurrencyDisplay::Symbol;
|
||||
if (currency_display == "narrowSymbol"sv)
|
||||
return CurrencyDisplay::NarrowSymbol;
|
||||
if (currency_display == "name"sv)
|
||||
return CurrencyDisplay::Name;
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
StringView currency_display_to_string(CurrencyDisplay currency_display)
|
||||
{
|
||||
switch (currency_display) {
|
||||
case CurrencyDisplay::Code:
|
||||
return "code"sv;
|
||||
case CurrencyDisplay::Symbol:
|
||||
return "symbol"sv;
|
||||
case CurrencyDisplay::NarrowSymbol:
|
||||
return "narrowSymbol"sv;
|
||||
case CurrencyDisplay::Name:
|
||||
return "name"sv;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static constexpr UNumberUnitWidth icu_currency_display(CurrencyDisplay currency_display)
|
||||
{
|
||||
switch (currency_display) {
|
||||
case CurrencyDisplay::Code:
|
||||
return UNUM_UNIT_WIDTH_ISO_CODE;
|
||||
case CurrencyDisplay::Symbol:
|
||||
return UNUM_UNIT_WIDTH_SHORT;
|
||||
case CurrencyDisplay::NarrowSymbol:
|
||||
return UNUM_UNIT_WIDTH_NARROW;
|
||||
case CurrencyDisplay::Name:
|
||||
return UNUM_UNIT_WIDTH_FULL_NAME;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
CurrencySign currency_sign_from_string(StringView currency_sign)
|
||||
{
|
||||
if (currency_sign == "standard"sv)
|
||||
return CurrencySign::Standard;
|
||||
if (currency_sign == "accounting"sv)
|
||||
return CurrencySign::Accounting;
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
StringView currency_sign_to_string(CurrencySign currency_sign)
|
||||
{
|
||||
switch (currency_sign) {
|
||||
case CurrencySign::Standard:
|
||||
return "standard"sv;
|
||||
case CurrencySign::Accounting:
|
||||
return "accounting"sv;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
RoundingType rounding_type_from_string(StringView rounding_type)
|
||||
{
|
||||
if (rounding_type == "significantDigits"sv)
|
||||
return RoundingType::SignificantDigits;
|
||||
if (rounding_type == "fractionDigits"sv)
|
||||
return RoundingType::FractionDigits;
|
||||
if (rounding_type == "morePrecision"sv)
|
||||
return RoundingType::MorePrecision;
|
||||
if (rounding_type == "lessPrecision"sv)
|
||||
return RoundingType::LessPrecision;
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
StringView rounding_type_to_string(RoundingType rounding_type)
|
||||
{
|
||||
switch (rounding_type) {
|
||||
case RoundingType::SignificantDigits:
|
||||
return "significantDigits"sv;
|
||||
case RoundingType::FractionDigits:
|
||||
return "fractionDigits"sv;
|
||||
case RoundingType::MorePrecision:
|
||||
return "morePrecision"sv;
|
||||
case RoundingType::LessPrecision:
|
||||
return "lessPrecision"sv;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
RoundingMode rounding_mode_from_string(StringView rounding_mode)
|
||||
{
|
||||
if (rounding_mode == "ceil"sv)
|
||||
return RoundingMode::Ceil;
|
||||
if (rounding_mode == "expand"sv)
|
||||
return RoundingMode::Expand;
|
||||
if (rounding_mode == "floor"sv)
|
||||
return RoundingMode::Floor;
|
||||
if (rounding_mode == "halfCeil"sv)
|
||||
return RoundingMode::HalfCeil;
|
||||
if (rounding_mode == "halfEven"sv)
|
||||
return RoundingMode::HalfEven;
|
||||
if (rounding_mode == "halfExpand"sv)
|
||||
return RoundingMode::HalfExpand;
|
||||
if (rounding_mode == "halfFloor"sv)
|
||||
return RoundingMode::HalfFloor;
|
||||
if (rounding_mode == "halfTrunc"sv)
|
||||
return RoundingMode::HalfTrunc;
|
||||
if (rounding_mode == "trunc"sv)
|
||||
return RoundingMode::Trunc;
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
StringView rounding_mode_to_string(RoundingMode rounding_mode)
|
||||
{
|
||||
switch (rounding_mode) {
|
||||
case RoundingMode::Ceil:
|
||||
return "ceil"sv;
|
||||
case RoundingMode::Expand:
|
||||
return "expand"sv;
|
||||
case RoundingMode::Floor:
|
||||
return "floor"sv;
|
||||
case RoundingMode::HalfCeil:
|
||||
return "halfCeil"sv;
|
||||
case RoundingMode::HalfEven:
|
||||
return "halfEven"sv;
|
||||
case RoundingMode::HalfExpand:
|
||||
return "halfExpand"sv;
|
||||
case RoundingMode::HalfFloor:
|
||||
return "halfFloor"sv;
|
||||
case RoundingMode::HalfTrunc:
|
||||
return "halfTrunc"sv;
|
||||
case RoundingMode::Trunc:
|
||||
return "trunc"sv;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static constexpr UNumberFormatRoundingMode icu_rounding_mode(RoundingMode rounding_mode)
|
||||
{
|
||||
switch (rounding_mode) {
|
||||
case RoundingMode::Ceil:
|
||||
return UNUM_ROUND_CEILING;
|
||||
case RoundingMode::Expand:
|
||||
return UNUM_ROUND_UP;
|
||||
case RoundingMode::Floor:
|
||||
return UNUM_ROUND_FLOOR;
|
||||
case RoundingMode::HalfCeil:
|
||||
return UNUM_ROUND_HALF_CEILING;
|
||||
case RoundingMode::HalfEven:
|
||||
return UNUM_ROUND_HALFEVEN;
|
||||
case RoundingMode::HalfExpand:
|
||||
return UNUM_ROUND_HALFUP;
|
||||
case RoundingMode::HalfFloor:
|
||||
return UNUM_ROUND_HALF_FLOOR;
|
||||
case RoundingMode::HalfTrunc:
|
||||
return UNUM_ROUND_HALFDOWN;
|
||||
case RoundingMode::Trunc:
|
||||
return UNUM_ROUND_DOWN;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
TrailingZeroDisplay trailing_zero_display_from_string(StringView trailing_zero_display)
|
||||
{
|
||||
if (trailing_zero_display == "auto"sv)
|
||||
return TrailingZeroDisplay::Auto;
|
||||
if (trailing_zero_display == "stripIfInteger"sv)
|
||||
return TrailingZeroDisplay::StripIfInteger;
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
StringView trailing_zero_display_to_string(TrailingZeroDisplay trailing_zero_display)
|
||||
{
|
||||
switch (trailing_zero_display) {
|
||||
case TrailingZeroDisplay::Auto:
|
||||
return "auto"sv;
|
||||
case TrailingZeroDisplay::StripIfInteger:
|
||||
return "stripIfInteger"sv;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static constexpr UNumberTrailingZeroDisplay icu_trailing_zero_display(TrailingZeroDisplay trailing_zero_display)
|
||||
{
|
||||
switch (trailing_zero_display) {
|
||||
case TrailingZeroDisplay::Auto:
|
||||
return UNUM_TRAILING_ZERO_AUTO;
|
||||
case TrailingZeroDisplay::StripIfInteger:
|
||||
return UNUM_TRAILING_ZERO_HIDE_IF_WHOLE;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static constexpr UNumberUnitWidth icu_unit_width(Style unit_display)
|
||||
{
|
||||
switch (unit_display) {
|
||||
case Style::Long:
|
||||
return UNUM_UNIT_WIDTH_FULL_NAME;
|
||||
case Style::Short:
|
||||
return UNUM_UNIT_WIDTH_SHORT;
|
||||
case Style::Narrow:
|
||||
return UNUM_UNIT_WIDTH_NARROW;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static void apply_display_options(icu::number::LocalizedNumberFormatter& formatter, DisplayOptions const& display_options)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
switch (display_options.style) {
|
||||
case NumberFormatStyle::Decimal:
|
||||
break;
|
||||
|
||||
case NumberFormatStyle::Percent:
|
||||
formatter = formatter.unit(icu::MeasureUnit::getPercent()).scale(icu::number::Scale::byDouble(100));
|
||||
break;
|
||||
|
||||
case NumberFormatStyle::Currency:
|
||||
formatter = formatter.unit(icu::CurrencyUnit(icu_string_piece(*display_options.currency), status));
|
||||
formatter = formatter.unitWidth(icu_currency_display(*display_options.currency_display));
|
||||
VERIFY(icu_success(status));
|
||||
break;
|
||||
|
||||
case NumberFormatStyle::Unit:
|
||||
formatter = formatter.unit(icu::MeasureUnit::forIdentifier(icu_string_piece(*display_options.unit), status));
|
||||
formatter = formatter.unitWidth(icu_unit_width(*display_options.unit_display));
|
||||
VERIFY(icu_success(status));
|
||||
break;
|
||||
}
|
||||
|
||||
formatter = formatter.sign(icu_sign_display(display_options.sign_display, display_options.currency_sign));
|
||||
formatter = formatter.notation(icu_notation(display_options.notation, display_options.compact_display));
|
||||
formatter = formatter.grouping(icu_grouping_strategy(display_options.grouping));
|
||||
}
|
||||
|
||||
static void apply_rounding_options(icu::number::LocalizedNumberFormatter& formatter, RoundingOptions const& rounding_options)
|
||||
{
|
||||
auto precision = icu::number::Precision::unlimited();
|
||||
|
||||
if (rounding_options.rounding_increment == 1) {
|
||||
switch (rounding_options.type) {
|
||||
case RoundingType::SignificantDigits:
|
||||
precision = icu::number::Precision::minMaxSignificantDigits(*rounding_options.min_significant_digits, *rounding_options.max_significant_digits);
|
||||
break;
|
||||
case RoundingType::FractionDigits:
|
||||
precision = icu::number::Precision::minMaxFraction(*rounding_options.min_fraction_digits, *rounding_options.max_fraction_digits);
|
||||
break;
|
||||
case RoundingType::MorePrecision:
|
||||
precision = icu::number::Precision::minMaxFraction(*rounding_options.min_fraction_digits, *rounding_options.max_fraction_digits)
|
||||
.withSignificantDigits(*rounding_options.min_significant_digits, *rounding_options.max_significant_digits, UNUM_ROUNDING_PRIORITY_RELAXED);
|
||||
break;
|
||||
case RoundingType::LessPrecision:
|
||||
precision = icu::number::Precision::minMaxFraction(*rounding_options.min_fraction_digits, *rounding_options.max_fraction_digits)
|
||||
.withSignificantDigits(*rounding_options.min_significant_digits, *rounding_options.max_significant_digits, UNUM_ROUNDING_PRIORITY_STRICT);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
auto mantissa = rounding_options.rounding_increment;
|
||||
auto magnitude = *rounding_options.max_fraction_digits * -1;
|
||||
|
||||
precision = icu::number::Precision::incrementExact(mantissa, static_cast<i16>(magnitude))
|
||||
.withMinFraction(*rounding_options.min_fraction_digits);
|
||||
}
|
||||
|
||||
formatter = formatter.precision(precision.trailingZeroDisplay(icu_trailing_zero_display(rounding_options.trailing_zero_display)));
|
||||
formatter = formatter.integerWidth(icu::number::IntegerWidth::zeroFillTo(rounding_options.min_integer_digits));
|
||||
formatter = formatter.roundingMode(icu_rounding_mode(rounding_options.mode));
|
||||
}
|
||||
|
||||
// ICU does not contain a field enumeration for "literal" partitions. Define a custom field so that we may provide a
|
||||
// type for those partitions.
|
||||
static constexpr i32 LITERAL_FIELD = -1;
|
||||
|
||||
static constexpr StringView icu_number_format_field_to_string(i32 field, NumberFormat::Value const& value, bool is_unit)
|
||||
{
|
||||
switch (field) {
|
||||
case LITERAL_FIELD:
|
||||
return "literal"sv;
|
||||
case UNUM_INTEGER_FIELD:
|
||||
if (auto const* number = value.get_pointer<double>()) {
|
||||
if (isnan(*number))
|
||||
return "nan"sv;
|
||||
if (isinf(*number))
|
||||
return "infinity"sv;
|
||||
}
|
||||
return "integer"sv;
|
||||
case UNUM_FRACTION_FIELD:
|
||||
return "fraction"sv;
|
||||
case UNUM_DECIMAL_SEPARATOR_FIELD:
|
||||
return "decimal"sv;
|
||||
case UNUM_EXPONENT_SYMBOL_FIELD:
|
||||
return "exponentSeparator"sv;
|
||||
case UNUM_EXPONENT_SIGN_FIELD:
|
||||
return "exponentMinusSign"sv;
|
||||
case UNUM_EXPONENT_FIELD:
|
||||
return "exponentInteger"sv;
|
||||
case UNUM_GROUPING_SEPARATOR_FIELD:
|
||||
return "group"sv;
|
||||
case UNUM_CURRENCY_FIELD:
|
||||
return "currency"sv;
|
||||
case UNUM_PERCENT_FIELD:
|
||||
return is_unit ? "unit"sv : "percentSign"sv;
|
||||
case UNUM_SIGN_FIELD: {
|
||||
auto is_negative = value.visit(
|
||||
[&](double number) { return signbit(number); },
|
||||
[&](String const& number) { return number.starts_with('-'); });
|
||||
return is_negative ? "minusSign"sv : "plusSign"sv;
|
||||
}
|
||||
case UNUM_MEASURE_UNIT_FIELD:
|
||||
return "unit"sv;
|
||||
case UNUM_COMPACT_FIELD:
|
||||
return "compact"sv;
|
||||
case UNUM_APPROXIMATELY_SIGN_FIELD:
|
||||
return "approximatelySign"sv;
|
||||
}
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
struct Range {
|
||||
constexpr bool operator<(Range const& other) const
|
||||
{
|
||||
if (start < other.start)
|
||||
return true;
|
||||
if (start == other.start)
|
||||
return end > other.end;
|
||||
return false;
|
||||
}
|
||||
|
||||
i32 field { LITERAL_FIELD };
|
||||
i32 start { 0 };
|
||||
i32 end { 0 };
|
||||
};
|
||||
|
||||
// ICU will give us overlapping partitions, e.g. for the formatted result "1,234", we will get the following parts:
|
||||
//
|
||||
// part="," type=group start=1 end=2
|
||||
// part="1,234" type=integer start=0 end=5
|
||||
//
|
||||
// We need to massage these partitions into non-overlapping parts for ECMA-402:
|
||||
//
|
||||
// part="1" type=integer start=0 end=1
|
||||
// part="," type=group start=1 end=2
|
||||
// part="234" type=integer start=2 end=5
|
||||
static void flatten_partitions(Vector<Range>& partitions)
|
||||
{
|
||||
if (partitions.size() <= 1)
|
||||
return;
|
||||
|
||||
quick_sort(partitions);
|
||||
|
||||
auto subtract_range = [&](auto const& first, auto const& second) -> Vector<Range> {
|
||||
if (second.start > first.end || first.start > second.end)
|
||||
return { first };
|
||||
|
||||
Vector<Range> result;
|
||||
|
||||
if (second.start > first.start)
|
||||
result.empend(first.field, first.start, second.start);
|
||||
if (second.end < first.end)
|
||||
result.empend(first.field, second.end, first.end);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < partitions.size(); ++i) {
|
||||
for (size_t j = i + 1; j < partitions.size(); ++j) {
|
||||
auto& first = partitions[i];
|
||||
auto& second = partitions[j];
|
||||
|
||||
auto result = subtract_range(first, second);
|
||||
|
||||
if (result.is_empty()) {
|
||||
partitions.remove(i);
|
||||
--i;
|
||||
break;
|
||||
}
|
||||
|
||||
first = result[0];
|
||||
|
||||
if (result.size() == 2)
|
||||
partitions.insert(i + 1, result[1]);
|
||||
}
|
||||
}
|
||||
|
||||
quick_sort(partitions);
|
||||
}
|
||||
|
||||
class NumberFormatImpl : public NumberFormat {
|
||||
public:
|
||||
NumberFormatImpl(icu::number::LocalizedNumberFormatter formatter, bool is_unit)
|
||||
: m_formatter(move(formatter))
|
||||
, m_is_unit(is_unit)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~NumberFormatImpl() override = default;
|
||||
|
||||
virtual String format(Value const& value) const override
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
auto formatted = format_impl(value);
|
||||
if (!formatted.has_value())
|
||||
return {};
|
||||
|
||||
auto result = formatted->toTempString(status);
|
||||
if (icu_failure(status))
|
||||
return {};
|
||||
|
||||
return icu_string_to_string(result);
|
||||
}
|
||||
|
||||
virtual String format_to_decimal(Value const& value) const override
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
auto formatted = format_impl(value);
|
||||
if (!formatted.has_value())
|
||||
return {};
|
||||
|
||||
auto result = formatted->toDecimalNumber<StringBuilder>(status);
|
||||
if (icu_failure(status))
|
||||
return {};
|
||||
|
||||
return MUST(result.to_string());
|
||||
}
|
||||
|
||||
virtual Vector<Partition> format_to_parts(Value const& value) const override
|
||||
{
|
||||
auto formatted = format_impl(value);
|
||||
if (!formatted.has_value())
|
||||
return {};
|
||||
|
||||
return format_to_parts_impl(formatted, value);
|
||||
}
|
||||
|
||||
private:
|
||||
Optional<icu::number::FormattedNumber> format_impl(Value const& value) const
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
auto formatted = value.visit(
|
||||
[&](double number) {
|
||||
return m_formatter.formatDouble(number, status);
|
||||
},
|
||||
[&](String const& number) {
|
||||
return m_formatter.formatDecimal(icu_string_piece(number), status);
|
||||
});
|
||||
|
||||
if (icu_failure(status))
|
||||
return {};
|
||||
|
||||
return formatted;
|
||||
}
|
||||
|
||||
template<typename Formatted>
|
||||
Vector<Partition> format_to_parts_impl(Formatted const& formatted, Value const& value) const
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
auto formatted_number = formatted->toTempString(status);
|
||||
if (icu_failure(status))
|
||||
return {};
|
||||
|
||||
Vector<Range> ranges;
|
||||
ranges.empend(LITERAL_FIELD, 0, formatted_number.length());
|
||||
|
||||
icu::ConstrainedFieldPosition position;
|
||||
|
||||
while (static_cast<bool>(formatted->nextPosition(position, status)) && icu_success(status)) {
|
||||
ranges.empend(position.getField(), position.getStart(), position.getLimit());
|
||||
}
|
||||
|
||||
flatten_partitions(ranges);
|
||||
|
||||
Vector<Partition> result;
|
||||
result.ensure_capacity(ranges.size());
|
||||
|
||||
for (auto const& range : ranges) {
|
||||
auto string = formatted_number.tempSubStringBetween(range.start, range.end);
|
||||
|
||||
Partition partition;
|
||||
partition.type = icu_number_format_field_to_string(range.field, value, m_is_unit);
|
||||
partition.value = icu_string_to_string(string);
|
||||
|
||||
result.unchecked_append(move(partition));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
icu::number::LocalizedNumberFormatter m_formatter;
|
||||
bool m_is_unit { false };
|
||||
};
|
||||
|
||||
NonnullOwnPtr<NumberFormat> NumberFormat::create(
|
||||
StringView locale,
|
||||
StringView numbering_system,
|
||||
DisplayOptions const& display_options,
|
||||
RoundingOptions const& rounding_options)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
auto locale_data = LocaleData::for_locale(locale);
|
||||
VERIFY(locale_data.has_value());
|
||||
|
||||
auto formatter = icu::number::NumberFormatter::withLocale(locale_data->locale());
|
||||
apply_display_options(formatter, display_options);
|
||||
apply_rounding_options(formatter, rounding_options);
|
||||
|
||||
if (!numbering_system.is_empty()) {
|
||||
if (auto* symbols = icu::NumberingSystem::createInstanceByName(ByteString(numbering_system).characters(), status); symbols && icu_success(status))
|
||||
formatter = formatter.adoptSymbols(symbols);
|
||||
}
|
||||
|
||||
bool is_unit = display_options.style == NumberFormatStyle::Unit;
|
||||
return adopt_own(*new NumberFormatImpl(move(formatter), is_unit));
|
||||
}
|
||||
|
||||
Optional<StringView> __attribute__((weak)) get_number_system_symbol(StringView, StringView, NumericSymbol) { return {}; }
|
||||
Optional<NumberGroupings> __attribute__((weak)) get_number_system_groupings(StringView, StringView) { return {}; }
|
||||
Optional<NumberFormat> __attribute__((weak)) get_standard_number_system_format(StringView, StringView, StandardNumberFormatType) { return {}; }
|
||||
Vector<NumberFormat> __attribute__((weak)) get_compact_number_system_formats(StringView, StringView, CompactNumberFormatType) { return {}; }
|
||||
Vector<NumberFormat> __attribute__((weak)) get_unit_formats(StringView, StringView, Style) { return {}; }
|
||||
|
||||
Optional<ReadonlySpan<u32>> __attribute__((weak)) get_digits_for_number_system(StringView)
|
||||
{
|
||||
|
@ -63,50 +777,6 @@ static u32 last_code_point(StringView string)
|
|||
}
|
||||
#endif
|
||||
|
||||
// https://www.unicode.org/reports/tr35/tr35-numbers.html#Currencies
|
||||
Optional<String> augment_currency_format_pattern([[maybe_unused]] StringView currency_display, [[maybe_unused]] StringView base_pattern)
|
||||
{
|
||||
#if ENABLE_UNICODE_DATA
|
||||
constexpr auto number_key = "{number}"sv;
|
||||
constexpr auto currency_key = "{currency}"sv;
|
||||
constexpr auto spacing = "\u00A0"sv; // No-Break Space (NBSP)
|
||||
|
||||
auto number_index = base_pattern.find(number_key);
|
||||
VERIFY(number_index.has_value());
|
||||
|
||||
auto currency_index = base_pattern.find(currency_key);
|
||||
VERIFY(currency_index.has_value());
|
||||
|
||||
Utf8View utf8_currency_display { currency_display };
|
||||
Optional<String> currency_key_with_spacing;
|
||||
|
||||
if (*number_index < *currency_index) {
|
||||
u32 last_pattern_code_point = last_code_point(base_pattern.substring_view(0, *currency_index));
|
||||
|
||||
if (!Unicode::code_point_has_general_category(last_pattern_code_point, Unicode::GeneralCategory::Separator)) {
|
||||
u32 first_currency_code_point = *utf8_currency_display.begin();
|
||||
|
||||
if (!Unicode::code_point_has_general_category(first_currency_code_point, Unicode::GeneralCategory::Symbol))
|
||||
currency_key_with_spacing = MUST(String::formatted("{}{}", spacing, currency_key));
|
||||
}
|
||||
} else {
|
||||
u32 last_pattern_code_point = last_code_point(base_pattern.substring_view(0, *number_index));
|
||||
|
||||
if (!Unicode::code_point_has_general_category(last_pattern_code_point, Unicode::GeneralCategory::Separator)) {
|
||||
u32 last_currency_code_point = last_code_point(currency_display);
|
||||
|
||||
if (!Unicode::code_point_has_general_category(last_currency_code_point, Unicode::GeneralCategory::Symbol))
|
||||
currency_key_with_spacing = MUST(String::formatted("{}{}", currency_key, spacing));
|
||||
}
|
||||
}
|
||||
|
||||
if (currency_key_with_spacing.has_value())
|
||||
return MUST(MUST(String::from_utf8(base_pattern)).replace(currency_key, *currency_key_with_spacing, ReplaceMode::FirstOnly));
|
||||
#endif
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr35/tr35-numbers.html#83-range-pattern-processing
|
||||
Optional<String> augment_range_pattern([[maybe_unused]] StringView range_separator, [[maybe_unused]] StringView lower, [[maybe_unused]] StringView upper)
|
||||
{
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
* Copyright (c) 2021-2024, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -9,40 +9,158 @@
|
|||
#include <AK/Optional.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <AK/Variant.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibLocale/Forward.h>
|
||||
#include <LibLocale/PluralRules.h>
|
||||
|
||||
namespace Locale {
|
||||
|
||||
struct NumberGroupings {
|
||||
u8 minimum_grouping_digits { 0 };
|
||||
u8 primary_grouping_size { 0 };
|
||||
u8 secondary_grouping_size { 0 };
|
||||
};
|
||||
|
||||
enum class StandardNumberFormatType : u8 {
|
||||
enum class NumberFormatStyle {
|
||||
Decimal,
|
||||
Currency,
|
||||
Accounting,
|
||||
Percent,
|
||||
Currency,
|
||||
Unit,
|
||||
};
|
||||
NumberFormatStyle number_format_style_from_string(StringView);
|
||||
StringView number_format_style_to_string(NumberFormatStyle);
|
||||
|
||||
enum class SignDisplay {
|
||||
Auto,
|
||||
Never,
|
||||
Always,
|
||||
ExceptZero,
|
||||
Negative,
|
||||
};
|
||||
SignDisplay sign_display_from_string(StringView);
|
||||
StringView sign_display_to_string(SignDisplay);
|
||||
|
||||
enum class Notation {
|
||||
Standard,
|
||||
Scientific,
|
||||
Engineering,
|
||||
Compact,
|
||||
};
|
||||
Notation notation_from_string(StringView);
|
||||
StringView notation_to_string(Notation);
|
||||
|
||||
enum class CompactDisplay {
|
||||
Short,
|
||||
Long,
|
||||
};
|
||||
CompactDisplay compact_display_from_string(StringView);
|
||||
StringView compact_display_to_string(CompactDisplay);
|
||||
|
||||
enum class Grouping {
|
||||
Always,
|
||||
Auto,
|
||||
Min2,
|
||||
False,
|
||||
};
|
||||
Grouping grouping_from_string(StringView);
|
||||
StringView grouping_to_string(Grouping);
|
||||
|
||||
enum class CurrencyDisplay {
|
||||
Code,
|
||||
Symbol,
|
||||
NarrowSymbol,
|
||||
Name,
|
||||
};
|
||||
CurrencyDisplay currency_display_from_string(StringView);
|
||||
StringView currency_display_to_string(CurrencyDisplay);
|
||||
|
||||
enum class CurrencySign {
|
||||
Standard,
|
||||
Accounting,
|
||||
};
|
||||
CurrencySign currency_sign_from_string(StringView);
|
||||
StringView currency_sign_to_string(CurrencySign);
|
||||
|
||||
struct DisplayOptions {
|
||||
NumberFormatStyle style { NumberFormatStyle::Decimal };
|
||||
SignDisplay sign_display { SignDisplay::Auto };
|
||||
|
||||
Notation notation { Notation::Standard };
|
||||
Optional<CompactDisplay> compact_display;
|
||||
|
||||
Grouping grouping { Grouping::Always };
|
||||
|
||||
Optional<String> currency;
|
||||
Optional<CurrencyDisplay> currency_display;
|
||||
Optional<CurrencySign> currency_sign;
|
||||
|
||||
Optional<String> unit;
|
||||
Optional<Style> unit_display;
|
||||
};
|
||||
|
||||
enum class CompactNumberFormatType : u8 {
|
||||
DecimalLong,
|
||||
DecimalShort,
|
||||
CurrencyUnit,
|
||||
enum class RoundingType {
|
||||
SignificantDigits,
|
||||
FractionDigits,
|
||||
MorePrecision,
|
||||
LessPrecision,
|
||||
};
|
||||
RoundingType rounding_type_from_string(StringView);
|
||||
StringView rounding_type_to_string(RoundingType);
|
||||
|
||||
enum class RoundingMode {
|
||||
Ceil,
|
||||
Expand,
|
||||
Floor,
|
||||
HalfCeil,
|
||||
HalfEven,
|
||||
HalfExpand,
|
||||
HalfFloor,
|
||||
HalfTrunc,
|
||||
Trunc,
|
||||
};
|
||||
RoundingMode rounding_mode_from_string(StringView);
|
||||
StringView rounding_mode_to_string(RoundingMode);
|
||||
|
||||
enum class TrailingZeroDisplay {
|
||||
Auto,
|
||||
StripIfInteger,
|
||||
};
|
||||
TrailingZeroDisplay trailing_zero_display_from_string(StringView);
|
||||
StringView trailing_zero_display_to_string(TrailingZeroDisplay);
|
||||
|
||||
struct RoundingOptions {
|
||||
RoundingType type { RoundingType::MorePrecision };
|
||||
RoundingMode mode { RoundingMode::HalfExpand };
|
||||
TrailingZeroDisplay trailing_zero_display;
|
||||
|
||||
Optional<int> min_significant_digits;
|
||||
Optional<int> max_significant_digits;
|
||||
|
||||
Optional<int> min_fraction_digits;
|
||||
Optional<int> max_fraction_digits;
|
||||
|
||||
int min_integer_digits { 0 };
|
||||
int rounding_increment { 1 };
|
||||
};
|
||||
|
||||
struct NumberFormat {
|
||||
u8 magnitude { 0 };
|
||||
u8 exponent { 0 };
|
||||
PluralCategory plurality { PluralCategory::Other };
|
||||
StringView zero_format {};
|
||||
StringView positive_format {};
|
||||
StringView negative_format {};
|
||||
Vector<StringView> identifiers {};
|
||||
class NumberFormat {
|
||||
public:
|
||||
static NonnullOwnPtr<NumberFormat> create(
|
||||
StringView locale,
|
||||
StringView numbering_system,
|
||||
DisplayOptions const&,
|
||||
RoundingOptions const&);
|
||||
|
||||
virtual ~NumberFormat() = default;
|
||||
|
||||
struct Partition {
|
||||
StringView type;
|
||||
String value;
|
||||
};
|
||||
|
||||
using Value = Variant<double, String>;
|
||||
|
||||
virtual String format(Value const&) const = 0;
|
||||
virtual String format_to_decimal(Value const&) const = 0;
|
||||
virtual Vector<Partition> format_to_parts(Value const&) const = 0;
|
||||
|
||||
protected:
|
||||
NumberFormat() = default;
|
||||
};
|
||||
|
||||
enum class NumericSymbol : u8 {
|
||||
|
@ -58,18 +176,11 @@ enum class NumericSymbol : u8 {
|
|||
RangeSeparator,
|
||||
TimeSeparator,
|
||||
};
|
||||
|
||||
Optional<StringView> get_number_system_symbol(StringView locale, StringView system, NumericSymbol symbol);
|
||||
Optional<NumberGroupings> get_number_system_groupings(StringView locale, StringView system);
|
||||
|
||||
Optional<ReadonlySpan<u32>> get_digits_for_number_system(StringView system);
|
||||
String replace_digits_for_number_system(StringView system, StringView number);
|
||||
|
||||
Optional<NumberFormat> get_standard_number_system_format(StringView locale, StringView system, StandardNumberFormatType type);
|
||||
Vector<NumberFormat> get_compact_number_system_formats(StringView locale, StringView system, CompactNumberFormatType type);
|
||||
Vector<NumberFormat> get_unit_formats(StringView locale, StringView unit, Style style);
|
||||
|
||||
Optional<String> augment_currency_format_pattern(StringView currency_display, StringView base_pattern);
|
||||
Optional<String> augment_range_pattern(StringView range_separator, StringView lower, StringView upper);
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue