#ifndef V8_INTL_SUPPORT
#error Internationalization is expected to be enabled.
#endif
#include "src/objects/js-list-format.h"
#include <memory>
#include <vector>
#include "src/execution/isolate.h"
#include "src/heap/factory.h"
#include "src/objects/elements-inl.h"
#include "src/objects/elements.h"
#include "src/objects/intl-objects.h"
#include "src/objects/js-array-inl.h"
#include "src/objects/js-list-format-inl.h"
#include "src/objects/managed-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/option-utils.h"
#include "unicode/fieldpos.h"
#include "unicode/fpositer.h"
#include "unicode/listformatter.h"
#include "unicode/ulistformatter.h"
namespace v8 {
namespace internal {
namespace {
UListFormatterWidth GetIcuWidth(JSListFormat::Style style) {
switch (style) {
case JSListFormat::Style::LONG:
return ULISTFMT_WIDTH_WIDE;
case JSListFormat::Style::SHORT:
return ULISTFMT_WIDTH_SHORT;
case JSListFormat::Style::NARROW:
return ULISTFMT_WIDTH_NARROW;
}
UNREACHABLE();
}
UListFormatterType GetIcuType(JSListFormat::Type type) {
switch (type) {
case JSListFormat::Type::CONJUNCTION:
return ULISTFMT_TYPE_AND;
case JSListFormat::Type::DISJUNCTION:
return ULISTFMT_TYPE_OR;
case JSListFormat::Type::UNIT:
return ULISTFMT_TYPE_UNITS;
}
UNREACHABLE();
}
}
MaybeDirectHandle<JSListFormat> JSListFormat::New(
Isolate* isolate, DirectHandle<Map> map, DirectHandle<Object> locales,
DirectHandle<Object> input_options, const char* service) {
Maybe<std::vector<std::string>> maybe_requested_locales =
Intl::CanonicalizeLocaleList(isolate, locales);
MAYBE_RETURN(maybe_requested_locales, DirectHandle<JSListFormat>());
std::vector<std::string> requested_locales =
maybe_requested_locales.FromJust();
DirectHandle<JSReceiver> options;
ASSIGN_RETURN_ON_EXCEPTION(isolate, options,
GetOptionsObject(isolate, input_options, service));
Maybe<Intl::MatcherOption> maybe_locale_matcher =
Intl::GetLocaleMatcher(isolate, options, service);
MAYBE_RETURN(maybe_locale_matcher, MaybeDirectHandle<JSListFormat>());
Intl::MatcherOption matcher = maybe_locale_matcher.FromJust();
Intl::ResolvedLocale r;
if (!Intl::ResolveLocale(isolate, JSListFormat::GetAvailableLocales(),
requested_locales, matcher, {})
.To(&r)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError));
}
DirectHandle<String> locale_str =
isolate->factory()->NewStringFromAsciiChecked(r.locale.c_str());
Maybe<Type> maybe_type = GetStringOption<Type>(
isolate, options, isolate->factory()->type_string(), service,
std::to_array<const std::string_view>(
{"conjunction", "disjunction", "unit"}),
std::array{Type::CONJUNCTION, Type::DISJUNCTION, Type::UNIT},
Type::CONJUNCTION);
MAYBE_RETURN(maybe_type, MaybeDirectHandle<JSListFormat>());
Type type_enum = maybe_type.FromJust();
Maybe<Style> maybe_style = GetStringOption<Style>(
isolate, options, isolate->factory()->style_string(), service,
std::to_array<const std::string_view>({"long", "short", "narrow"}),
std::array{Style::LONG, Style::SHORT, Style::NARROW}, Style::LONG);
MAYBE_RETURN(maybe_style, MaybeDirectHandle<JSListFormat>());
Style style_enum = maybe_style.FromJust();
icu::Locale icu_locale = r.icu_locale;
UErrorCode status = U_ZERO_ERROR;
std::shared_ptr<icu::ListFormatter> formatter{
icu::ListFormatter::createInstance(icu_locale, GetIcuType(type_enum),
GetIcuWidth(style_enum), status)};
if (U_FAILURE(status) || formatter == nullptr) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError));
}
DirectHandle<Managed<icu::ListFormatter>> managed_formatter =
Managed<icu::ListFormatter>::From(isolate, 0, std::move(formatter));
DirectHandle<JSListFormat> list_format =
Cast<JSListFormat>(isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
DisallowGarbageCollection no_gc;
list_format->set_flags(0);
list_format->set_icu_formatter(*managed_formatter);
list_format->set_locale(*locale_str);
list_format->set_type(type_enum);
list_format->set_style(style_enum);
return list_format;
}
DirectHandle<JSObject> JSListFormat::ResolvedOptions(
Isolate* isolate, DirectHandle<JSListFormat> format) {
Factory* factory = isolate->factory();
DirectHandle<JSObject> result =
factory->NewJSObject(isolate->object_function());
DirectHandle<String> locale(format->locale(), isolate);
JSObject::AddProperty(isolate, result, factory->locale_string(), locale,
NONE);
JSObject::AddProperty(isolate, result, factory->type_string(),
format->TypeAsString(isolate), NONE);
JSObject::AddProperty(isolate, result, factory->style_string(),
format->StyleAsString(isolate), NONE);
return result;
}
Handle<String> JSListFormat::StyleAsString(Isolate* isolate) const {
switch (style()) {
case Style::LONG:
return isolate->factory()->long_string();
case Style::SHORT:
return isolate->factory()->short_string();
case Style::NARROW:
return isolate->factory()->narrow_string();
}
UNREACHABLE();
}
Handle<String> JSListFormat::TypeAsString(Isolate* isolate) const {
switch (type()) {
case Type::CONJUNCTION:
return isolate->factory()->conjunction_string();
case Type::DISJUNCTION:
return isolate->factory()->disjunction_string();
case Type::UNIT:
return isolate->factory()->unit_string();
}
UNREACHABLE();
}
namespace {
Maybe<std::vector<icu::UnicodeString>> ToUnicodeStringArray(
Isolate* isolate, DirectHandle<FixedArray> array) {
int length = array->length();
std::vector<icu::UnicodeString> result;
for (int i = 0; i < length; i++) {
Handle<Object> item(array->get(i), isolate);
DCHECK(IsString(*item));
Handle<String> item_str = Cast<String>(item);
if (!item_str->IsFlat()) item_str = String::Flatten(isolate, item_str);
result.push_back(Intl::ToICUUnicodeString(isolate, item_str));
}
return Just(result);
}
template <typename T>
MaybeDirectHandle<T> FormatListCommon(
Isolate* isolate, DirectHandle<JSListFormat> format,
DirectHandle<FixedArray> list,
const std::function<MaybeDirectHandle<T>(
Isolate*, const icu::FormattedValue&)>& formatToResult) {
DCHECK(!IsUndefined(*list));
Maybe<std::vector<icu::UnicodeString>> maybe_array =
ToUnicodeStringArray(isolate, list);
MAYBE_RETURN(maybe_array, DirectHandle<T>());
std::vector<icu::UnicodeString> array = maybe_array.FromJust();
icu::ListFormatter* formatter = format->icu_formatter()->raw();
DCHECK_NOT_NULL(formatter);
UErrorCode status = U_ZERO_ERROR;
icu::FormattedList formatted = formatter->formatStringsToValue(
array.data(), static_cast<int32_t>(array.size()), status);
if (U_FAILURE(status)) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError));
}
return formatToResult(isolate, formatted);
}
DirectHandle<String> IcuFieldIdToType(Isolate* isolate, int32_t field_id) {
switch (field_id) {
case ULISTFMT_LITERAL_FIELD:
return isolate->factory()->literal_string();
case ULISTFMT_ELEMENT_FIELD:
return isolate->factory()->element_string();
default:
UNREACHABLE();
}
}
MaybeDirectHandle<JSArray> FormattedListToJSArray(
Isolate* isolate, const icu::FormattedValue& formatted) {
DirectHandle<JSArray> array = isolate->factory()->NewJSArray(0);
icu::ConstrainedFieldPosition cfpos;
cfpos.constrainCategory(UFIELD_CATEGORY_LIST);
int index = 0;
UErrorCode status = U_ZERO_ERROR;
icu::UnicodeString string = formatted.toString(status);
DirectHandle<String> substring;
while (formatted.nextPosition(cfpos, status) && U_SUCCESS(status)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, substring,
Intl::ToString(isolate, string, cfpos.getStart(), cfpos.getLimit()));
Intl::AddElement(isolate, array, index++,
IcuFieldIdToType(isolate, cfpos.getField()), substring);
}
if (U_FAILURE(status)) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError));
}
JSObject::ValidateElements(isolate, *array);
return array;
}
}
MaybeDirectHandle<String> JSListFormat::FormatList(
Isolate* isolate, DirectHandle<JSListFormat> format,
DirectHandle<FixedArray> list) {
return FormatListCommon<String>(isolate, format, list,
Intl::FormattedToString);
}
MaybeDirectHandle<JSArray> JSListFormat::FormatListToParts(
Isolate* isolate, DirectHandle<JSListFormat> format,
DirectHandle<FixedArray> list) {
return FormatListCommon<JSArray>(isolate, format, list,
FormattedListToJSArray);
}
namespace {
struct CheckListPattern {
static const char* key() { return "listPattern"; }
static const char* path() { return nullptr; }
};
}
const std::set<std::string>& JSListFormat::GetAvailableLocales() {
static base::LazyInstance<Intl::AvailableLocales<CheckListPattern>>::type
available_locales = LAZY_INSTANCE_INITIALIZER;
return available_locales.Pointer()->Get();
}
}
}