• 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.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