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