Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2
* vim: set ts=8 sts=2 et sw=2 tw=80:
3
* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this
5
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "builtin/intl/ListFormat.h"
8
9
#include "mozilla/ArrayUtils.h"
10
#include "mozilla/Assertions.h"
11
#include "mozilla/Casting.h"
12
#include "mozilla/PodOperations.h"
13
#include "mozilla/Unused.h"
14
15
#include <stddef.h>
16
#include <stdint.h>
17
18
#include "builtin/Array.h"
19
#include "builtin/intl/CommonFunctions.h"
20
#include "builtin/intl/ScopedICUObject.h"
21
#include "gc/FreeOp.h"
22
#include "js/Utility.h"
23
#include "js/Vector.h"
24
#include "unicode/uformattedvalue.h"
25
#include "unicode/ulistformatter.h"
26
#include "unicode/utypes.h"
27
#include "vm/JSContext.h"
28
#include "vm/SelfHosting.h"
29
#include "vm/StringType.h"
30
31
#include "vm/JSObject-inl.h"
32
#include "vm/NativeObject-inl.h"
33
#include "vm/ObjectOperations-inl.h"
34
35
using namespace js;
36
37
using mozilla::AssertedCast;
38
39
using js::intl::CallICU;
40
using js::intl::IcuLocale;
41
42
const JSClassOps ListFormatObject::classOps_ = {
43
nullptr, // addProperty
44
nullptr, // delProperty
45
nullptr, // enumerate
46
nullptr, // newEnumerate
47
nullptr, // resolve
48
nullptr, // mayResolve
49
ListFormatObject::finalize, // finalize
50
nullptr, // call
51
nullptr, // hasInstance
52
nullptr, // construct
53
nullptr, // trace
54
};
55
const JSClass ListFormatObject::class_ = {
56
js_Object_str,
57
JSCLASS_HAS_RESERVED_SLOTS(ListFormatObject::SLOT_COUNT) |
58
JSCLASS_HAS_CACHED_PROTO(JSProto_ListFormat) |
59
JSCLASS_FOREGROUND_FINALIZE,
60
&ListFormatObject::classOps_, &ListFormatObject::classSpec_};
61
62
const JSClass& ListFormatObject::protoClass_ = PlainObject::class_;
63
64
static bool listFormat_toSource(JSContext* cx, unsigned argc, Value* vp) {
65
CallArgs args = CallArgsFromVp(argc, vp);
66
args.rval().setString(cx->names().ListFormat);
67
return true;
68
}
69
70
static const JSFunctionSpec listFormat_static_methods[] = {
71
JS_SELF_HOSTED_FN("supportedLocalesOf",
72
"Intl_ListFormat_supportedLocalesOf", 1, 0),
73
JS_FS_END};
74
75
static const JSFunctionSpec listFormat_methods[] = {
76
JS_SELF_HOSTED_FN("resolvedOptions", "Intl_ListFormat_resolvedOptions", 0,
77
0),
78
JS_SELF_HOSTED_FN("format", "Intl_ListFormat_format", 1, 0),
79
#ifndef U_HIDE_DRAFT_API
80
JS_SELF_HOSTED_FN("formatToParts", "Intl_ListFormat_formatToParts", 1, 0),
81
#endif
82
JS_FN(js_toSource_str, listFormat_toSource, 0, 0), JS_FS_END};
83
84
static const JSPropertySpec listFormat_properties[] = {
85
JS_STRING_SYM_PS(toStringTag, "Intl.ListFormat", JSPROP_READONLY),
86
JS_PS_END};
87
88
static bool ListFormat(JSContext* cx, unsigned argc, Value* vp);
89
90
const ClassSpec ListFormatObject::classSpec_ = {
91
GenericCreateConstructor<ListFormat, 0, gc::AllocKind::FUNCTION>,
92
GenericCreatePrototype<ListFormatObject>,
93
listFormat_static_methods,
94
nullptr,
95
listFormat_methods,
96
listFormat_properties,
97
nullptr,
98
ClassSpec::DontDefineConstructor};
99
100
/**
101
* Intl.ListFormat([ locales [, options]])
102
*/
103
static bool ListFormat(JSContext* cx, unsigned argc, Value* vp) {
104
CallArgs args = CallArgsFromVp(argc, vp);
105
106
// Step 1.
107
if (!ThrowIfNotConstructing(cx, args, "Intl.ListFormat")) {
108
return false;
109
}
110
111
// Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
112
RootedObject proto(cx);
113
if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_ListFormat,
114
&proto)) {
115
return false;
116
}
117
118
Rooted<ListFormatObject*> listFormat(
119
cx, NewObjectWithClassProto<ListFormatObject>(cx, proto));
120
if (!listFormat) {
121
return false;
122
}
123
124
HandleValue locales = args.get(0);
125
HandleValue options = args.get(1);
126
127
// Step 3.
128
if (!intl::InitializeObject(cx, listFormat, cx->names().InitializeListFormat,
129
locales, options)) {
130
return false;
131
}
132
133
args.rval().setObject(*listFormat);
134
return true;
135
}
136
137
void js::ListFormatObject::finalize(JSFreeOp* fop, JSObject* obj) {
138
MOZ_ASSERT(fop->onMainThread());
139
140
if (UListFormatter* lf = obj->as<ListFormatObject>().getListFormatter()) {
141
intl::RemoveICUCellMemory(fop, obj, ListFormatObject::EstimatedMemoryUse);
142
143
ulistfmt_close(lf);
144
}
145
}
146
147
bool js::AddListFormatConstructor(JSContext* cx, JS::Handle<JSObject*> intl) {
148
JSObject* ctor = GlobalObject::getOrCreateConstructor(cx, JSProto_ListFormat);
149
if (!ctor) {
150
return false;
151
}
152
153
RootedValue ctorValue(cx, ObjectValue(*ctor));
154
return DefineDataProperty(cx, intl, cx->names().ListFormat, ctorValue, 0);
155
}
156
157
/**
158
* Returns a new UListFormatter with the locale and list formatting options
159
* of the given ListFormat.
160
*/
161
static UListFormatter* NewUListFormatter(JSContext* cx,
162
Handle<ListFormatObject*> listFormat) {
163
RootedObject internals(cx, intl::GetInternalsObject(cx, listFormat));
164
if (!internals) {
165
return nullptr;
166
}
167
168
RootedValue value(cx);
169
170
if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) {
171
return nullptr;
172
}
173
UniqueChars locale = intl::EncodeLocale(cx, value.toString());
174
if (!locale) {
175
return nullptr;
176
}
177
178
enum class ListFormatType { Conjunction, Disjunction, Unit };
179
180
ListFormatType type;
181
if (!GetProperty(cx, internals, internals, cx->names().type, &value)) {
182
return nullptr;
183
}
184
{
185
JSLinearString* strType = value.toString()->ensureLinear(cx);
186
if (!strType) {
187
return nullptr;
188
}
189
190
if (StringEqualsLiteral(strType, "conjunction")) {
191
type = ListFormatType::Conjunction;
192
} else if (StringEqualsLiteral(strType, "disjunction")) {
193
type = ListFormatType::Disjunction;
194
} else {
195
MOZ_ASSERT(StringEqualsLiteral(strType, "unit"));
196
type = ListFormatType::Unit;
197
}
198
}
199
200
enum class ListFormatStyle { Long, Short, Narrow };
201
202
ListFormatStyle style;
203
if (!GetProperty(cx, internals, internals, cx->names().style, &value)) {
204
return nullptr;
205
}
206
{
207
JSLinearString* strStyle = value.toString()->ensureLinear(cx);
208
if (!strStyle) {
209
return nullptr;
210
}
211
212
if (StringEqualsLiteral(strStyle, "long")) {
213
style = ListFormatStyle::Long;
214
} else if (StringEqualsLiteral(strStyle, "short")) {
215
style = ListFormatStyle::Short;
216
} else {
217
MOZ_ASSERT(StringEqualsLiteral(strStyle, "narrow"));
218
style = ListFormatStyle::Narrow;
219
}
220
}
221
222
// We're currently only supporting "conjunctive-long" list formatters due to
224
MOZ_ASSERT(type == ListFormatType::Conjunction);
225
MOZ_ASSERT(style == ListFormatStyle::Long);
226
227
mozilla::Unused << type;
228
mozilla::Unused << style;
229
230
UErrorCode status = U_ZERO_ERROR;
231
UListFormatter* lf = ulistfmt_open(IcuLocale(locale.get()), &status);
232
if (U_FAILURE(status)) {
233
intl::ReportInternalError(cx);
234
return nullptr;
235
}
236
return lf;
237
}
238
239
static constexpr size_t DEFAULT_LIST_LENGTH = 8;
240
241
using ListFormatStringVector = Vector<UniqueTwoByteChars, DEFAULT_LIST_LENGTH>;
242
using ListFormatStringLengthVector = Vector<int32_t, DEFAULT_LIST_LENGTH>;
243
244
static_assert(sizeof(UniqueTwoByteChars) == sizeof(char16_t*),
245
"UniqueTwoByteChars are stored efficiently and are held in "
246
"continuous memory");
247
248
/**
249
* FormatList ( listFormat, list )
250
*/
251
static bool FormatList(JSContext* cx, UListFormatter* lf,
252
const ListFormatStringVector& strings,
253
const ListFormatStringLengthVector& stringLengths,
254
MutableHandleValue result) {
255
MOZ_ASSERT(strings.length() == stringLengths.length());
256
MOZ_ASSERT(strings.length() <= INT32_MAX);
257
258
JSString* str = intl::CallICU(cx, [lf, &strings, &stringLengths](
259
UChar* chars, int32_t size,
260
UErrorCode* status) {
261
return ulistfmt_format(
262
lf, reinterpret_cast<char16_t* const*>(strings.begin()),
263
stringLengths.begin(), int32_t(strings.length()), chars, size, status);
264
});
265
if (!str) {
266
return false;
267
}
268
269
result.setString(str);
270
return true;
271
}
272
273
#ifndef U_HIDE_DRAFT_API
274
static JSString* FormattedValueToString(JSContext* cx,
275
const UFormattedValue* formattedValue) {
276
UErrorCode status = U_ZERO_ERROR;
277
int32_t strLength;
278
const char16_t* str = ufmtval_getString(formattedValue, &strLength, &status);
279
if (U_FAILURE(status)) {
280
intl::ReportInternalError(cx);
281
return nullptr;
282
}
283
284
return NewStringCopyN<CanGC>(cx, str, AssertedCast<uint32_t>(strLength));
285
}
286
287
/**
288
* FormatListToParts ( listFormat, list )
289
*/
290
static bool FormatListToParts(JSContext* cx, UListFormatter* lf,
291
const ListFormatStringVector& strings,
292
const ListFormatStringLengthVector& stringLengths,
293
MutableHandleValue result) {
294
MOZ_ASSERT(strings.length() == stringLengths.length());
295
MOZ_ASSERT(strings.length() <= INT32_MAX);
296
297
UErrorCode status = U_ZERO_ERROR;
298
UFormattedList* formatted = ulistfmt_openResult(&status);
299
if (U_FAILURE(status)) {
300
intl::ReportInternalError(cx);
301
return false;
302
}
303
ScopedICUObject<UFormattedList, ulistfmt_closeResult> toClose(formatted);
304
305
ulistfmt_formatStringsToResult(
306
lf, reinterpret_cast<char16_t* const*>(strings.begin()),
307
stringLengths.begin(), int32_t(strings.length()), formatted, &status);
308
if (U_FAILURE(status)) {
309
intl::ReportInternalError(cx);
310
return false;
311
}
312
313
const UFormattedValue* formattedValue =
314
ulistfmt_resultAsValue(formatted, &status);
315
if (U_FAILURE(status)) {
316
intl::ReportInternalError(cx);
317
return false;
318
}
319
320
RootedString overallResult(cx, FormattedValueToString(cx, formattedValue));
321
if (!overallResult) {
322
return false;
323
}
324
325
RootedArrayObject partsArray(cx, NewDenseEmptyArray(cx));
326
if (!partsArray) {
327
return false;
328
}
329
330
using FieldType = js::ImmutablePropertyNamePtr JSAtomState::*;
331
332
size_t lastEndIndex = 0;
333
RootedObject singlePart(cx);
334
RootedValue val(cx);
335
336
auto AppendPart = [&](FieldType type, size_t beginIndex, size_t endIndex) {
337
singlePart = NewBuiltinClassInstance<PlainObject>(cx);
338
if (!singlePart) {
339
return false;
340
}
341
342
val = StringValue(cx->names().*type);
343
if (!DefineDataProperty(cx, singlePart, cx->names().type, val)) {
344
return false;
345
}
346
347
JSLinearString* partSubstr = NewDependentString(
348
cx, overallResult, beginIndex, endIndex - beginIndex);
349
if (!partSubstr) {
350
return false;
351
}
352
353
val = StringValue(partSubstr);
354
if (!DefineDataProperty(cx, singlePart, cx->names().value, val)) {
355
return false;
356
}
357
358
if (!NewbornArrayPush(cx, partsArray, ObjectValue(*singlePart))) {
359
return false;
360
}
361
362
lastEndIndex = endIndex;
363
return true;
364
};
365
366
UConstrainedFieldPosition* fpos = ucfpos_open(&status);
367
if (U_FAILURE(status)) {
368
intl::ReportInternalError(cx);
369
return false;
370
}
371
ScopedICUObject<UConstrainedFieldPosition, ucfpos_close> toCloseFpos(fpos);
372
373
// We're only interested in ULISTFMT_ELEMENT_FIELD fields.
374
ucfpos_constrainField(fpos, UFIELD_CATEGORY_LIST, ULISTFMT_ELEMENT_FIELD,
375
&status);
376
if (U_FAILURE(status)) {
377
intl::ReportInternalError(cx);
378
return false;
379
}
380
381
while (true) {
382
bool hasMore = ufmtval_nextPosition(formattedValue, fpos, &status);
383
if (U_FAILURE(status)) {
384
intl::ReportInternalError(cx);
385
return false;
386
}
387
if (!hasMore) {
388
break;
389
}
390
391
int32_t beginIndexInt, endIndexInt;
392
ucfpos_getIndexes(fpos, &beginIndexInt, &endIndexInt, &status);
393
if (U_FAILURE(status)) {
394
intl::ReportInternalError(cx);
395
return false;
396
}
397
398
MOZ_ASSERT(beginIndexInt >= 0);
399
MOZ_ASSERT(endIndexInt >= 0);
400
MOZ_ASSERT(beginIndexInt <= endIndexInt,
401
"field iterator returning invalid range");
402
403
size_t beginIndex = size_t(beginIndexInt);
404
size_t endIndex = size_t(endIndexInt);
405
406
// Indices are guaranteed to be returned in order (from left to right).
407
MOZ_ASSERT(lastEndIndex <= beginIndex,
408
"field iteration didn't return fields in order start to "
409
"finish as expected");
410
411
if (lastEndIndex < beginIndex) {
412
if (!AppendPart(&JSAtomState::literal, lastEndIndex, beginIndex)) {
413
return false;
414
}
415
}
416
417
if (!AppendPart(&JSAtomState::element, beginIndex, endIndex)) {
418
return false;
419
}
420
}
421
422
// Append any final literal.
423
if (lastEndIndex < overallResult->length()) {
424
if (!AppendPart(&JSAtomState::literal, lastEndIndex,
425
overallResult->length())) {
426
return false;
427
}
428
}
429
430
result.setObject(*partsArray);
431
return true;
432
}
433
#endif // U_HIDE_DRAFT_API
434
435
bool js::intl_FormatList(JSContext* cx, unsigned argc, Value* vp) {
436
CallArgs args = CallArgsFromVp(argc, vp);
437
MOZ_ASSERT(args.length() == 3);
438
439
Rooted<ListFormatObject*> listFormat(
440
cx, &args[0].toObject().as<ListFormatObject>());
441
442
bool formatToParts = args[2].toBoolean();
443
444
// Obtain a cached UListFormatter object.
445
UListFormatter* lf = listFormat->getListFormatter();
446
if (!lf) {
447
lf = NewUListFormatter(cx, listFormat);
448
if (!lf) {
449
return false;
450
}
451
listFormat->setListFormatter(lf);
452
453
intl::AddICUCellMemory(listFormat, ListFormatObject::EstimatedMemoryUse);
454
}
455
456
// Collect all strings and their lengths.
457
ListFormatStringVector strings(cx);
458
ListFormatStringLengthVector stringLengths(cx);
459
460
RootedArrayObject list(cx, &args[1].toObject().as<ArrayObject>());
461
RootedValue value(cx);
462
for (uint32_t i = 0; i < list->length(); i++) {
463
if (!GetElement(cx, list, list, i, &value)) {
464
return false;
465
}
466
467
JSLinearString* linear = value.toString()->ensureLinear(cx);
468
if (!linear) {
469
return false;
470
}
471
472
size_t linearLength = linear->length();
473
if (!stringLengths.append(linearLength)) {
474
return false;
475
}
476
477
UniqueTwoByteChars chars = cx->make_pod_array<char16_t>(linearLength);
478
if (!chars) {
479
return false;
480
}
481
CopyChars(chars.get(), *linear);
482
483
if (!strings.append(std::move(chars))) {
484
return false;
485
}
486
}
487
488
// Use the UListFormatter to actually format the strings.
489
#ifndef U_HIDE_DRAFT_API
490
if (formatToParts) {
491
return FormatListToParts(cx, lf, strings, stringLengths, args.rval());
492
}
493
#else
494
MOZ_ASSERT(!formatToParts);
495
#endif
496
497
return FormatList(cx, lf, strings, stringLengths, args.rval());
498
}