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