• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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