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
parent 1c51ac4763
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
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
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).
// 2. Let result be an empty String.
// 3. For each Record { [[Type]], [[Value]] } part in parts, do
// a. Set result to the string-concatenation of result and part.[[Value]].
// 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
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();

View file

@ -41,17 +41,23 @@ public:
void set_style(StringView style) { m_style = ::Locale::style_from_string(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:
explicit ListFormat(Object& prototype);
String m_locale; // [[Locale]]
::Locale::ListFormatType m_type { ::Locale::ListFormatType::Conjunction }; // [[Type]]
::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);
String format_list(ListFormat const&, Vector<String> const& list);
NonnullGCPtr<Array> format_list_to_parts(VM&, ListFormat const&, Vector<String> const& list);
Vector<::Locale::ListFormat::Partition> create_parts_from_list(ListFormat const&, ReadonlySpan<String> list);
String format_list(ListFormat const&, ReadonlySpan<String> list);
NonnullGCPtr<Array> format_list_to_parts(VM&, ListFormat const&, ReadonlySpan<String> list);
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
*/
@ -52,7 +52,7 @@ ThrowCompletionOr<NonnullGCPtr<Object>> ListFormatConstructor::construct(Functio
auto locale_value = vm.argument(0);
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));
// 3. Let requestedLocales be ? CanonicalizeLocaleList(locales).
@ -70,29 +70,34 @@ ThrowCompletionOr<NonnullGCPtr<Object>> ListFormatConstructor::construct(Functio
// 7. Set opt.[[localeMatcher]] to matcher.
opt.locale_matcher = matcher;
// 8. Let localeData be %ListFormat%.[[LocaleData]].
// 9. Let r be ResolveLocale(%ListFormat%.[[AvailableLocales]], requestedLocales, opt, %ListFormat%.[[RelevantExtensionKeys]], localeData).
// 8. Let r be ResolveLocale(%Intl.ListFormat%.[[AvailableLocales]], requestedLocales, opt, %Intl.ListFormat%.[[RelevantExtensionKeys]], %Intl.ListFormat%.[[LocaleData]]).
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));
// 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));
// 12. Set listFormat.[[Type]] to type.
// 11. Set listFormat.[[Type]] to type.
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));
// 14. Set listFormat.[[Style]] to style.
// 13. Set listFormat.[[Style]] to style.
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;
}

View file

@ -34,9 +34,8 @@ StringView list_format_type_to_string(ListFormatType list_format_type)
return "disjunction"sv;
case ListFormatType::Unit:
return "unit"sv;
default:
VERIFY_NOT_REACHED();
}
VERIFY_NOT_REACHED();
}
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:
return ULISTFMT_TYPE_UNITS;
}
VERIFY_NOT_REACHED();
}
@ -63,7 +61,6 @@ static constexpr UListFormatterWidth icu_list_format_width(Style style)
case Style::Narrow:
return ULISTFMT_WIDTH_NARROW;
}
VERIFY_NOT_REACHED();
}
@ -75,70 +72,88 @@ static constexpr StringView icu_list_format_field_to_string(i32 field)
case ULISTFMT_ELEMENT_FIELD:
return "element"sv;
}
VERIFY_NOT_REACHED();
}
struct FormatResult {
icu::FormattedList list;
icu::UnicodeString string;
};
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)
{
auto formatted = format_list_impl(locale, type, style, list);
if (!formatted.has_value())
return {};
return icu_string_to_string(formatted->string);
}
Vector<ListFormatPart> format_list_to_parts(StringView locale, ListFormatType type, Style style, ReadonlySpan<String> list)
{
UErrorCode status = U_ZERO_ERROR;
auto formatted = format_list_impl(locale, type, style, list);
if (!formatted.has_value())
return {};
Vector<ListFormatPart> result;
icu::ConstrainedFieldPosition position;
position.constrainCategory(UFIELD_CATEGORY_LIST);
while (static_cast<bool>(formatted->list.nextPosition(position, status)) && icu_success(status)) {
auto type = icu_list_format_field_to_string(position.getField());
auto part = formatted->string.tempSubStringBetween(position.getStart(), position.getLimit());
result.empend(type, icu_string_to_string(part));
class ListFormatImpl : public ListFormat {
public:
ListFormatImpl(NonnullOwnPtr<icu::ListFormatter> formatter)
: m_formatter(move(formatter))
{
}
return result;
virtual ~ListFormatImpl() override = default;
virtual String format(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 {};
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;
position.constrainCategory(UFIELD_CATEGORY_LIST);
while (static_cast<bool>(formatted->nextPosition(position, status)) && icu_success(status)) {
auto type = icu_list_format_field_to_string(position.getField());
auto part = formatted_string.tempSubStringBetween(position.getStart(), position.getLimit());
result.empend(type, icu_string_to_string(part));
}
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,
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);
StringView list_format_type_to_string(ListFormatType list_format_type);
class ListFormat {
public:
static NonnullOwnPtr<ListFormat> create(StringView locale, ListFormatType, Style);
virtual ~ListFormat() = default;
struct ListFormatPart {
StringView type;
String value;
struct Partition {
StringView type;
String value;
};
virtual String format(ReadonlySpan<String> list) const = 0;
virtual Vector<Partition> format_to_parts(ReadonlySpan<String> list) const = 0;
protected:
ListFormat() = default;
};
String format_list(StringView locale, ListFormatType, Style, ReadonlySpan<String> list);
Vector<ListFormatPart> format_list_to_parts(StringView locale, ListFormatType, Style, ReadonlySpan<String> list);
}