1 // Copyright 2018 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #ifndef V8_INTL_SUPPORT
6 #error Internationalization is expected to be enabled.
7 #endif // V8_INTL_SUPPORT
8
9 #include "src/objects/js-list-format.h"
10
11 #include <memory>
12 #include <vector>
13
14 #include "src/execution/isolate.h"
15 #include "src/heap/factory.h"
16 #include "src/objects/elements-inl.h"
17 #include "src/objects/elements.h"
18 #include "src/objects/intl-objects.h"
19 #include "src/objects/js-array-inl.h"
20 #include "src/objects/js-list-format-inl.h"
21 #include "src/objects/managed.h"
22 #include "src/objects/objects-inl.h"
23 #include "unicode/fieldpos.h"
24 #include "unicode/fpositer.h"
25 #include "unicode/listformatter.h"
26 #include "unicode/ulistformatter.h"
27
28 namespace v8 {
29 namespace internal {
30
31 namespace {
32
GetIcuWidth(JSListFormat::Style style)33 UListFormatterWidth GetIcuWidth(JSListFormat::Style style) {
34 switch (style) {
35 case JSListFormat::Style::LONG:
36 return ULISTFMT_WIDTH_WIDE;
37 case JSListFormat::Style::SHORT:
38 return ULISTFMT_WIDTH_SHORT;
39 case JSListFormat::Style::NARROW:
40 return ULISTFMT_WIDTH_NARROW;
41 }
42 UNREACHABLE();
43 }
44
GetIcuType(JSListFormat::Type type)45 UListFormatterType GetIcuType(JSListFormat::Type type) {
46 switch (type) {
47 case JSListFormat::Type::CONJUNCTION:
48 return ULISTFMT_TYPE_AND;
49 case JSListFormat::Type::DISJUNCTION:
50 return ULISTFMT_TYPE_OR;
51 case JSListFormat::Type::UNIT:
52 return ULISTFMT_TYPE_UNITS;
53 }
54 UNREACHABLE();
55 }
56
57 } // namespace
58
New(Isolate * isolate,Handle<Map> map,Handle<Object> locales,Handle<Object> input_options)59 MaybeHandle<JSListFormat> JSListFormat::New(Isolate* isolate, Handle<Map> map,
60 Handle<Object> locales,
61 Handle<Object> input_options) {
62 Handle<JSReceiver> options;
63 // 3. Let requestedLocales be ? CanonicalizeLocaleList(locales).
64 Maybe<std::vector<std::string>> maybe_requested_locales =
65 Intl::CanonicalizeLocaleList(isolate, locales);
66 MAYBE_RETURN(maybe_requested_locales, Handle<JSListFormat>());
67 std::vector<std::string> requested_locales =
68 maybe_requested_locales.FromJust();
69
70 // 4. If options is undefined, then
71 if (input_options->IsUndefined(isolate)) {
72 // 4. a. Let options be ObjectCreate(null).
73 options = isolate->factory()->NewJSObjectWithNullProto();
74 // 5. Else
75 } else {
76 // 5. a. Let options be ? ToObject(options).
77 ASSIGN_RETURN_ON_EXCEPTION(isolate, options,
78 Object::ToObject(isolate, input_options),
79 JSListFormat);
80 }
81
82 // Note: No need to create a record. It's not observable.
83 // 6. Let opt be a new Record.
84
85 // 7. Let matcher be ? GetOption(options, "localeMatcher", "string", «
86 // "lookup", "best fit" », "best fit").
87 Maybe<Intl::MatcherOption> maybe_locale_matcher =
88 Intl::GetLocaleMatcher(isolate, options, "Intl.ListFormat");
89 MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSListFormat>());
90
91 // 8. Set opt.[[localeMatcher]] to matcher.
92 Intl::MatcherOption matcher = maybe_locale_matcher.FromJust();
93
94 // 10. Let r be ResolveLocale(%ListFormat%.[[AvailableLocales]],
95 // requestedLocales, opt, undefined, localeData).
96 Maybe<Intl::ResolvedLocale> maybe_resolve_locale =
97 Intl::ResolveLocale(isolate, JSListFormat::GetAvailableLocales(),
98 requested_locales, matcher, {});
99 if (maybe_resolve_locale.IsNothing()) {
100 THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
101 JSListFormat);
102 }
103 Intl::ResolvedLocale r = maybe_resolve_locale.FromJust();
104 Handle<String> locale_str =
105 isolate->factory()->NewStringFromAsciiChecked(r.locale.c_str());
106
107 // 12. Let t be GetOption(options, "type", "string", «"conjunction",
108 // "disjunction", "unit"», "conjunction").
109 Maybe<Type> maybe_type = Intl::GetStringOption<Type>(
110 isolate, options, "type", "Intl.ListFormat",
111 {"conjunction", "disjunction", "unit"},
112 {Type::CONJUNCTION, Type::DISJUNCTION, Type::UNIT}, Type::CONJUNCTION);
113 MAYBE_RETURN(maybe_type, MaybeHandle<JSListFormat>());
114 Type type_enum = maybe_type.FromJust();
115
116 // 14. Let s be ? GetOption(options, "style", "string",
117 // «"long", "short", "narrow"», "long").
118 Maybe<Style> maybe_style = Intl::GetStringOption<Style>(
119 isolate, options, "style", "Intl.ListFormat", {"long", "short", "narrow"},
120 {Style::LONG, Style::SHORT, Style::NARROW}, Style::LONG);
121 MAYBE_RETURN(maybe_style, MaybeHandle<JSListFormat>());
122 Style style_enum = maybe_style.FromJust();
123
124 icu::Locale icu_locale = r.icu_locale;
125 UErrorCode status = U_ZERO_ERROR;
126 icu::ListFormatter* formatter = icu::ListFormatter::createInstance(
127 icu_locale, GetIcuType(type_enum), GetIcuWidth(style_enum), status);
128 if (U_FAILURE(status) || formatter == nullptr) {
129 delete formatter;
130 THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
131 JSListFormat);
132 }
133
134 Handle<Managed<icu::ListFormatter>> managed_formatter =
135 Managed<icu::ListFormatter>::FromRawPtr(isolate, 0, formatter);
136
137 // Now all properties are ready, so we can allocate the result object.
138 Handle<JSListFormat> list_format = Handle<JSListFormat>::cast(
139 isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
140 DisallowHeapAllocation no_gc;
141 list_format->set_flags(0);
142 list_format->set_icu_formatter(*managed_formatter);
143
144 // 11. Set listFormat.[[Locale]] to r.[[Locale]].
145 list_format->set_locale(*locale_str);
146
147 // 13. Set listFormat.[[Type]] to t.
148 list_format->set_type(type_enum);
149
150 // 15. Set listFormat.[[Style]] to s.
151 list_format->set_style(style_enum);
152
153 return list_format;
154 }
155
156 // ecma402 #sec-intl.pluralrules.prototype.resolvedoptions
ResolvedOptions(Isolate * isolate,Handle<JSListFormat> format)157 Handle<JSObject> JSListFormat::ResolvedOptions(Isolate* isolate,
158 Handle<JSListFormat> format) {
159 Factory* factory = isolate->factory();
160 // 4. Let options be ! ObjectCreate(%ObjectPrototype%).
161 Handle<JSObject> result = factory->NewJSObject(isolate->object_function());
162
163 // 5. For each row of Table 1, except the header row, do
164 // Table 1: Resolved Options of ListFormat Instances
165 // Internal Slot Property
166 // [[Locale]] "locale"
167 // [[Type]] "type"
168 // [[Style]] "style"
169 Handle<String> locale(format->locale(), isolate);
170 JSObject::AddProperty(isolate, result, factory->locale_string(), locale,
171 NONE);
172 JSObject::AddProperty(isolate, result, factory->type_string(),
173 format->TypeAsString(), NONE);
174 JSObject::AddProperty(isolate, result, factory->style_string(),
175 format->StyleAsString(), NONE);
176 // 6. Return options.
177 return result;
178 }
179
StyleAsString() const180 Handle<String> JSListFormat::StyleAsString() const {
181 switch (style()) {
182 case Style::LONG:
183 return GetReadOnlyRoots().long_string_handle();
184 case Style::SHORT:
185 return GetReadOnlyRoots().short_string_handle();
186 case Style::NARROW:
187 return GetReadOnlyRoots().narrow_string_handle();
188 }
189 UNREACHABLE();
190 }
191
TypeAsString() const192 Handle<String> JSListFormat::TypeAsString() const {
193 switch (type()) {
194 case Type::CONJUNCTION:
195 return GetReadOnlyRoots().conjunction_string_handle();
196 case Type::DISJUNCTION:
197 return GetReadOnlyRoots().disjunction_string_handle();
198 case Type::UNIT:
199 return GetReadOnlyRoots().unit_string_handle();
200 }
201 UNREACHABLE();
202 }
203
204 namespace {
205
206 // Extract String from JSArray into array of UnicodeString
ToUnicodeStringArray(Isolate * isolate,Handle<JSArray> array)207 Maybe<std::vector<icu::UnicodeString>> ToUnicodeStringArray(
208 Isolate* isolate, Handle<JSArray> array) {
209 // Thanks to iterable-to-list preprocessing, we never see dictionary-mode
210 // arrays here, so the loop below can construct an entry from the index.
211 DCHECK(array->HasFastElements(isolate));
212 auto* accessor = array->GetElementsAccessor();
213 size_t length = accessor->NumberOfElements(*array);
214
215 std::vector<icu::UnicodeString> result;
216 for (InternalIndex entry : InternalIndex::Range(length)) {
217 DCHECK(accessor->HasEntry(*array, entry));
218 Handle<Object> item = accessor->Get(array, entry);
219 DCHECK(item->IsString());
220 Handle<String> item_str = Handle<String>::cast(item);
221 if (!item_str->IsFlat()) item_str = String::Flatten(isolate, item_str);
222 result.push_back(Intl::ToICUUnicodeString(isolate, item_str));
223 }
224 return Just(result);
225 }
226
227 template <typename T>
FormatListCommon(Isolate * isolate,Handle<JSListFormat> format,Handle<JSArray> list,MaybeHandle<T> (* formatToResult)(Isolate *,const icu::FormattedValue &))228 MaybeHandle<T> FormatListCommon(
229 Isolate* isolate, Handle<JSListFormat> format, Handle<JSArray> list,
230 MaybeHandle<T> (*formatToResult)(Isolate*, const icu::FormattedValue&)) {
231 DCHECK(!list->IsUndefined());
232 Maybe<std::vector<icu::UnicodeString>> maybe_array =
233 ToUnicodeStringArray(isolate, list);
234 MAYBE_RETURN(maybe_array, Handle<T>());
235 std::vector<icu::UnicodeString> array = maybe_array.FromJust();
236
237 icu::ListFormatter* formatter = format->icu_formatter().raw();
238 DCHECK_NOT_NULL(formatter);
239
240 UErrorCode status = U_ZERO_ERROR;
241 icu::FormattedList formatted = formatter->formatStringsToValue(
242 array.data(), static_cast<int32_t>(array.size()), status);
243 if (U_FAILURE(status)) {
244 THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), T);
245 }
246 return formatToResult(isolate, formatted);
247 }
248
IcuFieldIdToType(Isolate * isolate,int32_t field_id)249 Handle<String> IcuFieldIdToType(Isolate* isolate, int32_t field_id) {
250 switch (field_id) {
251 case ULISTFMT_LITERAL_FIELD:
252 return isolate->factory()->literal_string();
253 case ULISTFMT_ELEMENT_FIELD:
254 return isolate->factory()->element_string();
255 default:
256 UNREACHABLE();
257 // To prevent MSVC from issuing C4715 warning.
258 return Handle<String>();
259 }
260 }
261
262 // A helper function to convert the FormattedList to a
263 // MaybeHandle<JSArray> for the implementation of formatToParts.
FormattedListToJSArray(Isolate * isolate,const icu::FormattedValue & formatted)264 MaybeHandle<JSArray> FormattedListToJSArray(
265 Isolate* isolate, const icu::FormattedValue& formatted) {
266 Handle<JSArray> array = isolate->factory()->NewJSArray(0);
267 icu::ConstrainedFieldPosition cfpos;
268 cfpos.constrainCategory(UFIELD_CATEGORY_LIST);
269 int index = 0;
270 UErrorCode status = U_ZERO_ERROR;
271 icu::UnicodeString string = formatted.toString(status);
272 Handle<String> substring;
273 while (formatted.nextPosition(cfpos, status) && U_SUCCESS(status)) {
274 ASSIGN_RETURN_ON_EXCEPTION(
275 isolate, substring,
276 Intl::ToString(isolate, string, cfpos.getStart(), cfpos.getLimit()),
277 JSArray);
278 Intl::AddElement(isolate, array, index++,
279 IcuFieldIdToType(isolate, cfpos.getField()), substring);
280 }
281 if (U_FAILURE(status)) {
282 THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), JSArray);
283 }
284 JSObject::ValidateElements(*array);
285 return array;
286 }
287
288 } // namespace
289
290 // ecma402 #sec-formatlist
FormatList(Isolate * isolate,Handle<JSListFormat> format,Handle<JSArray> list)291 MaybeHandle<String> JSListFormat::FormatList(Isolate* isolate,
292 Handle<JSListFormat> format,
293 Handle<JSArray> list) {
294 return FormatListCommon<String>(isolate, format, list,
295 Intl::FormattedToString);
296 }
297
298 // ecma42 #sec-formatlisttoparts
FormatListToParts(Isolate * isolate,Handle<JSListFormat> format,Handle<JSArray> list)299 MaybeHandle<JSArray> JSListFormat::FormatListToParts(
300 Isolate* isolate, Handle<JSListFormat> format, Handle<JSArray> list) {
301 return FormatListCommon<JSArray>(isolate, format, list,
302 FormattedListToJSArray);
303 }
304
305 namespace {
306
307 struct CheckListPattern {
keyv8::internal::__anond007d1d90311::CheckListPattern308 static const char* key() { return "listPattern"; }
pathv8::internal::__anond007d1d90311::CheckListPattern309 static const char* path() { return nullptr; }
310 };
311
312 } // namespace
313
GetAvailableLocales()314 const std::set<std::string>& JSListFormat::GetAvailableLocales() {
315 static base::LazyInstance<Intl::AvailableLocales<CheckListPattern>>::type
316 available_locales = LAZY_INSTANCE_INITIALIZER;
317 return available_locales.Pointer()->Get();
318 }
319
320 } // namespace internal
321 } // namespace v8
322