LibJS+LibLocale: Change ListFormat to be created once per Intl object

ListFormat was the first formatter I ported to ICU. This patch makes it
match the style of subsequently ported formatters, where we create the
formatter once per Intl object, rather than once per prototype
invocation.
This commit is contained in:
Timothy Flynn 2024-06-17 16:46:59 -04:00 committed by Tim Flynn
commit de99dd2c89
Notes: sideshowbarker 2024-07-16 23:34:44 +09:00
5 changed files with 125 additions and 91 deletions

View file

@ -22,24 +22,24 @@ ListFormat::ListFormat(Object& prototype)
} }
// 13.5.2 CreatePartsFromList ( listFormat, list ), https://tc39.es/ecma402/#sec-createpartsfromlist // 13.5.2 CreatePartsFromList ( listFormat, list ), https://tc39.es/ecma402/#sec-createpartsfromlist
Vector<::Locale::ListFormatPart> create_parts_from_list(ListFormat const& list_format, Vector<String> const& list) Vector<::Locale::ListFormat::Partition> create_parts_from_list(ListFormat const& list_format, ReadonlySpan<String> list)
{ {
return ::Locale::format_list_to_parts(list_format.locale(), list_format.type(), list_format.style(), list); return list_format.formatter().format_to_parts(list);
} }
// 13.5.3 FormatList ( listFormat, list ), https://tc39.es/ecma402/#sec-formatlist // 13.5.3 FormatList ( listFormat, list ), https://tc39.es/ecma402/#sec-formatlist
String format_list(ListFormat const& list_format, Vector<String> const& list) String format_list(ListFormat const& list_format, ReadonlySpan<String> list)
{ {
// 1. Let parts be ! CreatePartsFromList(listFormat, list). // 1. Let parts be ! CreatePartsFromList(listFormat, list).
// 2. Let result be an empty String. // 2. Let result be an empty String.
// 3. For each Record { [[Type]], [[Value]] } part in parts, do // 3. For each Record { [[Type]], [[Value]] } part in parts, do
// a. Set result to the string-concatenation of result and part.[[Value]]. // a. Set result to the string-concatenation of result and part.[[Value]].
// 4. Return result. // 4. Return result.
return ::Locale::format_list(list_format.locale(), list_format.type(), list_format.style(), list); return list_format.formatter().format(list);
} }
// 13.5.4 FormatListToParts ( listFormat, list ), https://tc39.es/ecma402/#sec-formatlisttoparts // 13.5.4 FormatListToParts ( listFormat, list ), https://tc39.es/ecma402/#sec-formatlisttoparts
NonnullGCPtr<Array> format_list_to_parts(VM& vm, ListFormat const& list_format, Vector<String> const& list) NonnullGCPtr<Array> format_list_to_parts(VM& vm, ListFormat const& list_format, ReadonlySpan<String> list)
{ {
auto& realm = *vm.current_realm(); auto& realm = *vm.current_realm();

View file

@ -41,17 +41,23 @@ public:
void set_style(StringView style) { m_style = ::Locale::style_from_string(style); } void set_style(StringView style) { m_style = ::Locale::style_from_string(style); }
StringView style_string() const { return ::Locale::style_to_string(m_style); } StringView style_string() const { return ::Locale::style_to_string(m_style); }
::Locale::ListFormat const& formatter() const { return *m_formatter; }
void set_formatter(NonnullOwnPtr<::Locale::ListFormat> formatter) { m_formatter = move(formatter); }
private: private:
explicit ListFormat(Object& prototype); explicit ListFormat(Object& prototype);
String m_locale; // [[Locale]] String m_locale; // [[Locale]]
::Locale::ListFormatType m_type { ::Locale::ListFormatType::Conjunction }; // [[Type]] ::Locale::ListFormatType m_type { ::Locale::ListFormatType::Conjunction }; // [[Type]]
::Locale::Style m_style { ::Locale::Style::Long }; // [[Style]] ::Locale::Style m_style { ::Locale::Style::Long }; // [[Style]]
// Non-standard. Stores the ICU list formatter for the Intl object's formatting options.
OwnPtr<::Locale::ListFormat> m_formatter;
}; };
Vector<::Locale::ListFormatPart> create_parts_from_list(ListFormat const&, Vector<String> const& list); Vector<::Locale::ListFormat::Partition> create_parts_from_list(ListFormat const&, ReadonlySpan<String> list);
String format_list(ListFormat const&, Vector<String> const& list); String format_list(ListFormat const&, ReadonlySpan<String> list);
NonnullGCPtr<Array> format_list_to_parts(VM&, ListFormat const&, Vector<String> const& list); NonnullGCPtr<Array> format_list_to_parts(VM&, ListFormat const&, ReadonlySpan<String> list);
ThrowCompletionOr<Vector<String>> string_list_from_iterable(VM&, Value iterable); ThrowCompletionOr<Vector<String>> string_list_from_iterable(VM&, Value iterable);
} }

View file

@ -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 * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -52,7 +52,7 @@ ThrowCompletionOr<NonnullGCPtr<Object>> ListFormatConstructor::construct(Functio
auto locale_value = vm.argument(0); auto locale_value = vm.argument(0);
auto options_value = vm.argument(1); auto options_value = vm.argument(1);
// 2. Let listFormat be ? OrdinaryCreateFromConstructor(NewTarget, "%ListFormat.prototype%", « [[InitializedListFormat]], [[Locale]], [[Type]], [[Style]], [[Templates]] »). // 2. Let listFormat be ? OrdinaryCreateFromConstructor(NewTarget, "%Intl.ListFormat.prototype%", « [[InitializedListFormat]], [[Locale]], [[Type]], [[Style]], [[Templates]] »).
auto list_format = TRY(ordinary_create_from_constructor<ListFormat>(vm, new_target, &Intrinsics::intl_list_format_prototype)); auto list_format = TRY(ordinary_create_from_constructor<ListFormat>(vm, new_target, &Intrinsics::intl_list_format_prototype));
// 3. Let requestedLocales be ? CanonicalizeLocaleList(locales). // 3. Let requestedLocales be ? CanonicalizeLocaleList(locales).
@ -70,29 +70,34 @@ ThrowCompletionOr<NonnullGCPtr<Object>> ListFormatConstructor::construct(Functio
// 7. Set opt.[[localeMatcher]] to matcher. // 7. Set opt.[[localeMatcher]] to matcher.
opt.locale_matcher = matcher; opt.locale_matcher = matcher;
// 8. Let localeData be %ListFormat%.[[LocaleData]]. // 8. Let r be ResolveLocale(%Intl.ListFormat%.[[AvailableLocales]], requestedLocales, opt, %Intl.ListFormat%.[[RelevantExtensionKeys]], %Intl.ListFormat%.[[LocaleData]]).
// 9. Let r be ResolveLocale(%ListFormat%.[[AvailableLocales]], requestedLocales, opt, %ListFormat%.[[RelevantExtensionKeys]], localeData).
auto result = resolve_locale(requested_locales, opt, {}); auto result = resolve_locale(requested_locales, opt, {});
// 10. Set listFormat.[[Locale]] to r.[[locale]]. // 9. Set listFormat.[[Locale]] to r.[[Locale]].
list_format->set_locale(move(result.locale)); list_format->set_locale(move(result.locale));
// 11. Let type be ? GetOption(options, "type", string, « "conjunction", "disjunction", "unit" », "conjunction"). // 10. Let type be ? GetOption(options, "type", string, « "conjunction", "disjunction", "unit" », "conjunction").
auto type = TRY(get_option(vm, *options, vm.names.type, OptionType::String, { "conjunction"sv, "disjunction"sv, "unit"sv }, "conjunction"sv)); auto type = TRY(get_option(vm, *options, vm.names.type, OptionType::String, { "conjunction"sv, "disjunction"sv, "unit"sv }, "conjunction"sv));
// 12. Set listFormat.[[Type]] to type. // 11. Set listFormat.[[Type]] to type.
list_format->set_type(type.as_string().utf8_string_view()); list_format->set_type(type.as_string().utf8_string_view());
// 13. Let style be ? GetOption(options, "style", string, « "long", "short", "narrow" », "long"). // 12. Let style be ? GetOption(options, "style", string, « "long", "short", "narrow" », "long").
auto style = TRY(get_option(vm, *options, vm.names.style, OptionType::String, { "long"sv, "short"sv, "narrow"sv }, "long"sv)); auto style = TRY(get_option(vm, *options, vm.names.style, OptionType::String, { "long"sv, "short"sv, "narrow"sv }, "long"sv));
// 14. Set listFormat.[[Style]] to style. // 13. Set listFormat.[[Style]] to style.
list_format->set_style(style.as_string().utf8_string_view()); list_format->set_style(style.as_string().utf8_string_view());
// Note: The remaining steps are skipped in favor of deferring to LibUnicode. // 14. Let resolvedLocaleData be r.[[LocaleData]].
// 15. Let dataLocaleTypes be resolvedLocaleData.[[<type>]].
// 16. Set listFormat.[[Templates]] to dataLocaleTypes.[[<style>]].
auto formatter = ::Locale::ListFormat::create(
list_format->locale(),
list_format->type(),
list_format->style());
list_format->set_formatter(move(formatter));
// 19. Return listFormat. // 17. Return listFormat.
return list_format; return list_format;
} }

View file

@ -34,9 +34,8 @@ StringView list_format_type_to_string(ListFormatType list_format_type)
return "disjunction"sv; return "disjunction"sv;
case ListFormatType::Unit: case ListFormatType::Unit:
return "unit"sv; return "unit"sv;
default:
VERIFY_NOT_REACHED();
} }
VERIFY_NOT_REACHED();
} }
static constexpr UListFormatterType icu_list_format_type(ListFormatType type) static constexpr UListFormatterType icu_list_format_type(ListFormatType type)
@ -49,7 +48,6 @@ static constexpr UListFormatterType icu_list_format_type(ListFormatType type)
case ListFormatType::Unit: case ListFormatType::Unit:
return ULISTFMT_TYPE_UNITS; return ULISTFMT_TYPE_UNITS;
} }
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }
@ -63,7 +61,6 @@ static constexpr UListFormatterWidth icu_list_format_width(Style style)
case Style::Narrow: case Style::Narrow:
return ULISTFMT_WIDTH_NARROW; return ULISTFMT_WIDTH_NARROW;
} }
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }
@ -75,65 +72,53 @@ static constexpr StringView icu_list_format_field_to_string(i32 field)
case ULISTFMT_ELEMENT_FIELD: case ULISTFMT_ELEMENT_FIELD:
return "element"sv; return "element"sv;
} }
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }
struct FormatResult { class ListFormatImpl : public ListFormat {
icu::FormattedList list; public:
icu::UnicodeString string; ListFormatImpl(NonnullOwnPtr<icu::ListFormatter> formatter)
}; : m_formatter(move(formatter))
static Optional<FormatResult> format_list_impl(StringView locale, ListFormatType type, Style style, ReadonlySpan<String> list)
{ {
auto locale_data = LocaleData::for_locale(locale);
if (!locale_data.has_value())
return {};
UErrorCode status = U_ZERO_ERROR;
auto list_formatter = adopt_own(*icu::ListFormatter::createInstance(locale_data->locale(), icu_list_format_type(type), icu_list_format_width(style), status));
if (icu_failure(status))
return {};
auto icu_list = icu_string_list(list);
auto formatted_list = list_formatter->formatStringsToValue(icu_list.data(), static_cast<i32>(icu_list.size()), status);
if (icu_failure(status))
return {};
auto formatted_string = formatted_list.toString(status);
if (icu_failure(status))
return {};
return FormatResult { move(formatted_list), move(formatted_string) };
} }
String format_list(StringView locale, ListFormatType type, Style style, ReadonlySpan<String> list) virtual ~ListFormatImpl() override = default;
{
auto formatted = format_list_impl(locale, type, style, list);
if (!formatted.has_value())
return {};
return icu_string_to_string(formatted->string); virtual String format(ReadonlySpan<String> list) const override
}
Vector<ListFormatPart> format_list_to_parts(StringView locale, ListFormatType type, Style style, ReadonlySpan<String> list)
{ {
UErrorCode status = U_ZERO_ERROR; UErrorCode status = U_ZERO_ERROR;
auto formatted = format_list_impl(locale, type, style, list); auto formatted = format_list_impl(list);
if (!formatted.has_value()) if (!formatted.has_value())
return {}; return {};
Vector<ListFormatPart> result; auto formatted_string = formatted->toTempString(status);
if (icu_failure(status))
return {};
return icu_string_to_string(formatted_string);
}
virtual Vector<Partition> format_to_parts(ReadonlySpan<String> list) const override
{
UErrorCode status = U_ZERO_ERROR;
auto formatted = format_list_impl(list);
if (!formatted.has_value())
return {};
auto formatted_string = formatted->toTempString(status);
if (icu_failure(status))
return {};
Vector<Partition> result;
icu::ConstrainedFieldPosition position; icu::ConstrainedFieldPosition position;
position.constrainCategory(UFIELD_CATEGORY_LIST); position.constrainCategory(UFIELD_CATEGORY_LIST);
while (static_cast<bool>(formatted->list.nextPosition(position, status)) && icu_success(status)) { while (static_cast<bool>(formatted->nextPosition(position, status)) && icu_success(status)) {
auto type = icu_list_format_field_to_string(position.getField()); auto type = icu_list_format_field_to_string(position.getField());
auto part = formatted->string.tempSubStringBetween(position.getStart(), position.getLimit()); auto part = formatted_string.tempSubStringBetween(position.getStart(), position.getLimit());
result.empend(type, icu_string_to_string(part)); result.empend(type, icu_string_to_string(part));
} }
@ -141,4 +126,34 @@ Vector<ListFormatPart> format_list_to_parts(StringView locale, ListFormatType ty
return result; return result;
} }
private:
Optional<icu::FormattedList> format_list_impl(ReadonlySpan<String> list) const
{
UErrorCode status = U_ZERO_ERROR;
auto icu_list = icu_string_list(list);
auto formatted_list = m_formatter->formatStringsToValue(icu_list.data(), static_cast<i32>(icu_list.size()), status);
if (icu_failure(status))
return {};
return formatted_list;
}
NonnullOwnPtr<icu::ListFormatter> m_formatter;
};
NonnullOwnPtr<ListFormat> ListFormat::create(StringView locale, ListFormatType type, Style style)
{
UErrorCode status = U_ZERO_ERROR;
auto locale_data = LocaleData::for_locale(locale);
VERIFY(locale_data.has_value());
auto formatter = adopt_own(*icu::ListFormatter::createInstance(locale_data->locale(), icu_list_format_type(type), icu_list_format_width(style), status));
VERIFY(icu_success(status));
return adopt_own(*new ListFormatImpl(move(formatter)));
}
} }

View file

@ -17,16 +17,24 @@ enum class ListFormatType {
Disjunction, Disjunction,
Unit, Unit,
}; };
ListFormatType list_format_type_from_string(StringView);
StringView list_format_type_to_string(ListFormatType);
ListFormatType list_format_type_from_string(StringView list_format_type); class ListFormat {
StringView list_format_type_to_string(ListFormatType list_format_type); public:
static NonnullOwnPtr<ListFormat> create(StringView locale, ListFormatType, Style);
virtual ~ListFormat() = default;
struct ListFormatPart { struct Partition {
StringView type; StringView type;
String value; String value;
}; };
String format_list(StringView locale, ListFormatType, Style, ReadonlySpan<String> list); virtual String format(ReadonlySpan<String> list) const = 0;
Vector<ListFormatPart> format_list_to_parts(StringView locale, ListFormatType, Style, ReadonlySpan<String> list); virtual Vector<Partition> format_to_parts(ReadonlySpan<String> list) const = 0;
protected:
ListFormat() = default;
};
} }