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
/* Implementation of the Intl.PluralRules proposal. */
8
9
#include "builtin/intl/PluralRules.h"
10
11
#include "mozilla/Assertions.h"
12
#include "mozilla/Casting.h"
13
14
#include "builtin/Array.h"
15
#include "builtin/intl/CommonFunctions.h"
16
#include "builtin/intl/NumberFormat.h"
17
#include "builtin/intl/ScopedICUObject.h"
18
#include "gc/FreeOp.h"
19
#include "js/CharacterEncoding.h"
20
#include "js/PropertySpec.h"
21
#include "unicode/uenum.h"
22
#include "unicode/uloc.h"
23
#include "unicode/unumberformatter.h"
24
#include "unicode/upluralrules.h"
25
#include "unicode/utypes.h"
26
#include "vm/GlobalObject.h"
27
#include "vm/JSContext.h"
28
#include "vm/StringType.h"
29
30
#include "vm/JSObject-inl.h"
31
#include "vm/NativeObject-inl.h"
32
33
using namespace js;
34
35
using mozilla::AssertedCast;
36
37
using js::intl::CallICU;
38
using js::intl::IcuLocale;
39
40
const JSClassOps PluralRulesObject::classOps_ = {nullptr, /* addProperty */
41
nullptr, /* delProperty */
42
nullptr, /* enumerate */
43
nullptr, /* newEnumerate */
44
nullptr, /* resolve */
45
nullptr, /* mayResolve */
46
PluralRulesObject::finalize};
47
48
const JSClass PluralRulesObject::class_ = {
49
js_Object_str,
50
JSCLASS_HAS_RESERVED_SLOTS(PluralRulesObject::SLOT_COUNT) |
51
JSCLASS_HAS_CACHED_PROTO(JSProto_PluralRules) |
52
JSCLASS_FOREGROUND_FINALIZE,
53
&PluralRulesObject::classOps_, &PluralRulesObject::classSpec_};
54
55
const JSClass& PluralRulesObject::protoClass_ = PlainObject::class_;
56
57
static bool pluralRules_toSource(JSContext* cx, unsigned argc, Value* vp) {
58
CallArgs args = CallArgsFromVp(argc, vp);
59
args.rval().setString(cx->names().PluralRules);
60
return true;
61
}
62
63
static const JSFunctionSpec pluralRules_static_methods[] = {
64
JS_SELF_HOSTED_FN("supportedLocalesOf",
65
"Intl_PluralRules_supportedLocalesOf", 1, 0),
66
JS_FS_END};
67
68
static const JSFunctionSpec pluralRules_methods[] = {
69
JS_SELF_HOSTED_FN("resolvedOptions", "Intl_PluralRules_resolvedOptions", 0,
70
0),
71
JS_SELF_HOSTED_FN("select", "Intl_PluralRules_select", 1, 0),
72
JS_FN(js_toSource_str, pluralRules_toSource, 0, 0), JS_FS_END};
73
74
static bool PluralRules(JSContext* cx, unsigned argc, Value* vp);
75
76
const ClassSpec PluralRulesObject::classSpec_ = {
77
GenericCreateConstructor<PluralRules, 0, gc::AllocKind::FUNCTION>,
78
GenericCreatePrototype<PluralRulesObject>,
79
pluralRules_static_methods,
80
nullptr,
81
pluralRules_methods,
82
nullptr,
83
nullptr,
84
ClassSpec::DontDefineConstructor};
85
86
/**
87
* PluralRules constructor.
88
* Spec: ECMAScript 402 API, PluralRules, 13.2.1
89
*/
90
static bool PluralRules(JSContext* cx, unsigned argc, Value* vp) {
91
CallArgs args = CallArgsFromVp(argc, vp);
92
93
// Step 1.
94
if (!ThrowIfNotConstructing(cx, args, "Intl.PluralRules")) {
95
return false;
96
}
97
98
// Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
99
RootedObject proto(cx);
100
if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_PluralRules,
101
&proto)) {
102
return false;
103
}
104
105
Rooted<PluralRulesObject*> pluralRules(cx);
106
pluralRules = NewObjectWithClassProto<PluralRulesObject>(cx, proto);
107
if (!pluralRules) {
108
return false;
109
}
110
111
HandleValue locales = args.get(0);
112
HandleValue options = args.get(1);
113
114
// Step 3.
115
if (!intl::InitializeObject(cx, pluralRules,
116
cx->names().InitializePluralRules, locales,
117
options)) {
118
return false;
119
}
120
121
args.rval().setObject(*pluralRules);
122
return true;
123
}
124
125
void js::PluralRulesObject::finalize(JSFreeOp* fop, JSObject* obj) {
126
MOZ_ASSERT(fop->onMainThread());
127
128
auto* pluralRules = &obj->as<PluralRulesObject>();
129
UPluralRules* pr = pluralRules->getPluralRules();
130
UNumberFormatter* nf = pluralRules->getNumberFormatter();
131
UFormattedNumber* formatted = pluralRules->getFormattedNumber();
132
133
if (pr) {
134
intl::RemoveICUCellMemory(
135
fop, obj, PluralRulesObject::UPluralRulesEstimatedMemoryUse);
136
}
137
if (nf) {
138
intl::RemoveICUCellMemory(
139
fop, obj, PluralRulesObject::UNumberFormatterEstimatedMemoryUse);
140
141
// UFormattedNumber memory tracked as part of UNumberFormatter.
142
}
143
144
if (pr) {
145
uplrules_close(pr);
146
}
147
if (nf) {
148
unumf_close(nf);
149
}
150
if (formatted) {
151
unumf_closeResult(formatted);
152
}
153
}
154
155
/**
156
* This creates a new UNumberFormatter with calculated digit formatting
157
* properties for PluralRules.
158
*
159
* This is similar to NewUNumberFormatter but doesn't allow for currency or
160
* percent types.
161
*/
162
static UNumberFormatter* NewUNumberFormatterForPluralRules(
163
JSContext* cx, Handle<PluralRulesObject*> pluralRules) {
164
RootedObject internals(cx, intl::GetInternalsObject(cx, pluralRules));
165
if (!internals) {
166
return nullptr;
167
}
168
169
RootedValue value(cx);
170
171
if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) {
172
return nullptr;
173
}
174
UniqueChars locale = intl::EncodeLocale(cx, value.toString());
175
if (!locale) {
176
return nullptr;
177
}
178
179
intl::NumberFormatterSkeleton skeleton(cx);
180
181
bool hasMinimumSignificantDigits;
182
if (!HasProperty(cx, internals, cx->names().minimumSignificantDigits,
183
&hasMinimumSignificantDigits)) {
184
return nullptr;
185
}
186
187
if (hasMinimumSignificantDigits) {
188
if (!GetProperty(cx, internals, internals,
189
cx->names().minimumSignificantDigits, &value)) {
190
return nullptr;
191
}
192
uint32_t minimumSignificantDigits = AssertedCast<uint32_t>(value.toInt32());
193
194
if (!GetProperty(cx, internals, internals,
195
cx->names().maximumSignificantDigits, &value)) {
196
return nullptr;
197
}
198
uint32_t maximumSignificantDigits = AssertedCast<uint32_t>(value.toInt32());
199
200
if (!skeleton.significantDigits(minimumSignificantDigits,
201
maximumSignificantDigits)) {
202
return nullptr;
203
}
204
} else {
205
if (!GetProperty(cx, internals, internals,
206
cx->names().minimumFractionDigits, &value)) {
207
return nullptr;
208
}
209
uint32_t minimumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
210
211
if (!GetProperty(cx, internals, internals,
212
cx->names().maximumFractionDigits, &value)) {
213
return nullptr;
214
}
215
uint32_t maximumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
216
217
if (!skeleton.fractionDigits(minimumFractionDigits,
218
maximumFractionDigits)) {
219
return nullptr;
220
}
221
}
222
223
if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
224
&value)) {
225
return nullptr;
226
}
227
uint32_t minimumIntegerDigits = AssertedCast<uint32_t>(value.toInt32());
228
229
if (!skeleton.integerWidth(minimumIntegerDigits)) {
230
return nullptr;
231
}
232
233
if (!skeleton.roundingModeHalfUp()) {
234
return nullptr;
235
}
236
237
return skeleton.toFormatter(cx, locale.get());
238
}
239
240
static UFormattedNumber* NewUFormattedNumberForPluralRules(JSContext* cx) {
241
UErrorCode status = U_ZERO_ERROR;
242
UFormattedNumber* formatted = unumf_openResult(&status);
243
if (U_FAILURE(status)) {
244
intl::ReportInternalError(cx);
245
return nullptr;
246
}
247
return formatted;
248
}
249
250
/**
251
* Returns a new UPluralRules with the locale and type options of the given
252
* PluralRules.
253
*/
254
static UPluralRules* NewUPluralRules(JSContext* cx,
255
Handle<PluralRulesObject*> pluralRules) {
256
RootedObject internals(cx, intl::GetInternalsObject(cx, pluralRules));
257
if (!internals) {
258
return nullptr;
259
}
260
261
RootedValue value(cx);
262
263
if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) {
264
return nullptr;
265
}
266
UniqueChars locale = intl::EncodeLocale(cx, value.toString());
267
if (!locale) {
268
return nullptr;
269
}
270
271
if (!GetProperty(cx, internals, internals, cx->names().type, &value)) {
272
return nullptr;
273
}
274
275
UPluralType category;
276
{
277
JSLinearString* type = value.toString()->ensureLinear(cx);
278
if (!type) {
279
return nullptr;
280
}
281
282
if (StringEqualsLiteral(type, "cardinal")) {
283
category = UPLURAL_TYPE_CARDINAL;
284
} else {
285
MOZ_ASSERT(StringEqualsLiteral(type, "ordinal"));
286
category = UPLURAL_TYPE_ORDINAL;
287
}
288
}
289
290
UErrorCode status = U_ZERO_ERROR;
291
UPluralRules* pr =
292
uplrules_openForType(IcuLocale(locale.get()), category, &status);
293
if (U_FAILURE(status)) {
294
intl::ReportInternalError(cx);
295
return nullptr;
296
}
297
return pr;
298
}
299
300
bool js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp) {
301
CallArgs args = CallArgsFromVp(argc, vp);
302
MOZ_ASSERT(args.length() == 2);
303
304
Rooted<PluralRulesObject*> pluralRules(
305
cx, &args[0].toObject().as<PluralRulesObject>());
306
307
double x = args[1].toNumber();
308
309
// Obtain a cached UPluralRules object.
310
UPluralRules* pr = pluralRules->getPluralRules();
311
if (!pr) {
312
pr = NewUPluralRules(cx, pluralRules);
313
if (!pr) {
314
return false;
315
}
316
pluralRules->setPluralRules(pr);
317
318
intl::AddICUCellMemory(pluralRules,
319
PluralRulesObject::UPluralRulesEstimatedMemoryUse);
320
}
321
322
// Obtain a cached UNumberFormat object.
323
UNumberFormatter* nf = pluralRules->getNumberFormatter();
324
if (!nf) {
325
nf = NewUNumberFormatterForPluralRules(cx, pluralRules);
326
if (!nf) {
327
return false;
328
}
329
pluralRules->setNumberFormatter(nf);
330
331
intl::AddICUCellMemory(
332
pluralRules, PluralRulesObject::UNumberFormatterEstimatedMemoryUse);
333
}
334
335
// Obtain a cached UFormattedNumber object.
336
UFormattedNumber* formatted = pluralRules->getFormattedNumber();
337
if (!formatted) {
338
formatted = NewUFormattedNumberForPluralRules(cx);
339
if (!formatted) {
340
return false;
341
}
342
pluralRules->setFormattedNumber(formatted);
343
344
// UFormattedNumber memory tracked as part of UNumberFormatter.
345
}
346
347
UErrorCode status = U_ZERO_ERROR;
348
unumf_formatDouble(nf, x, formatted, &status);
349
if (U_FAILURE(status)) {
350
intl::ReportInternalError(cx);
351
return false;
352
}
353
354
JSString* str = CallICU(
355
cx, [pr, formatted](UChar* chars, int32_t size, UErrorCode* status) {
356
return uplrules_selectFormatted(pr, formatted, chars, size, status);
357
});
358
if (!str) {
359
return false;
360
}
361
362
args.rval().setString(str);
363
return true;
364
}
365
366
bool js::intl_GetPluralCategories(JSContext* cx, unsigned argc, Value* vp) {
367
CallArgs args = CallArgsFromVp(argc, vp);
368
MOZ_ASSERT(args.length() == 1);
369
370
Rooted<PluralRulesObject*> pluralRules(
371
cx, &args[0].toObject().as<PluralRulesObject>());
372
373
// Obtain a cached UPluralRules object.
374
UPluralRules* pr = pluralRules->getPluralRules();
375
if (!pr) {
376
pr = NewUPluralRules(cx, pluralRules);
377
if (!pr) {
378
return false;
379
}
380
pluralRules->setPluralRules(pr);
381
382
intl::AddICUCellMemory(pluralRules,
383
PluralRulesObject::UPluralRulesEstimatedMemoryUse);
384
}
385
386
UErrorCode status = U_ZERO_ERROR;
387
UEnumeration* ue = uplrules_getKeywords(pr, &status);
388
if (U_FAILURE(status)) {
389
intl::ReportInternalError(cx);
390
return false;
391
}
392
ScopedICUObject<UEnumeration, uenum_close> closeEnum(ue);
393
394
RootedObject res(cx, NewDenseEmptyArray(cx));
395
if (!res) {
396
return false;
397
}
398
399
do {
400
int32_t catSize;
401
const char* cat = uenum_next(ue, &catSize, &status);
402
if (U_FAILURE(status)) {
403
intl::ReportInternalError(cx);
404
return false;
405
}
406
407
if (!cat) {
408
break;
409
}
410
411
MOZ_ASSERT(catSize >= 0);
412
JSString* str = NewStringCopyN<CanGC>(cx, cat, catSize);
413
if (!str) {
414
return false;
415
}
416
417
if (!NewbornArrayPush(cx, res, StringValue(str))) {
418
return false;
419
}
420
} while (true);
421
422
args.rval().setObject(*res);
423
return true;
424
}