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
/* Intl.NumberFormat implementation. */
8
9
#include "builtin/intl/NumberFormat.h"
10
11
#include "mozilla/ArrayUtils.h"
12
#include "mozilla/Assertions.h"
13
#include "mozilla/Casting.h"
14
#include "mozilla/FloatingPoint.h"
15
#include "mozilla/UniquePtr.h"
16
17
#include <algorithm>
18
#include <cstring>
19
#include <iterator>
20
#include <stddef.h>
21
#include <stdint.h>
22
#include <string>
23
#include <type_traits>
24
25
#include "builtin/Array.h"
26
#include "builtin/intl/CommonFunctions.h"
27
#include "builtin/intl/ScopedICUObject.h"
28
#include "ds/Sort.h"
29
#include "gc/FreeOp.h"
30
#include "js/CharacterEncoding.h"
31
#include "js/PropertySpec.h"
32
#include "js/RootingAPI.h"
33
#include "js/StableStringChars.h"
34
#include "js/TypeDecls.h"
35
#include "js/Vector.h"
36
#include "unicode/udata.h"
37
#include "unicode/ufieldpositer.h"
38
#include "unicode/uformattedvalue.h"
39
#include "unicode/unum.h"
40
#include "unicode/unumberformatter.h"
41
#include "unicode/unumsys.h"
42
#include "unicode/ures.h"
43
#include "unicode/utypes.h"
44
#include "vm/BigIntType.h"
45
#include "vm/JSContext.h"
46
#include "vm/SelfHosting.h"
47
#include "vm/Stack.h"
48
#include "vm/StringType.h"
49
50
#include "vm/JSObject-inl.h"
51
52
using namespace js;
53
54
using mozilla::AssertedCast;
55
using mozilla::IsFinite;
56
using mozilla::IsNaN;
57
using mozilla::IsNegative;
58
using mozilla::SpecificNaN;
59
60
using js::intl::CallICU;
61
using js::intl::DateTimeFormatOptions;
62
using js::intl::FieldType;
63
using js::intl::GetAvailableLocales;
64
using js::intl::IcuLocale;
65
66
using JS::AutoStableStringChars;
67
68
const JSClassOps NumberFormatObject::classOps_ = {nullptr, /* addProperty */
69
nullptr, /* delProperty */
70
nullptr, /* enumerate */
71
nullptr, /* newEnumerate */
72
nullptr, /* resolve */
73
nullptr, /* mayResolve */
74
NumberFormatObject::finalize};
75
76
const JSClass NumberFormatObject::class_ = {
77
js_Object_str,
78
JSCLASS_HAS_RESERVED_SLOTS(NumberFormatObject::SLOT_COUNT) |
79
JSCLASS_FOREGROUND_FINALIZE,
80
&NumberFormatObject::classOps_};
81
82
static bool numberFormat_toSource(JSContext* cx, unsigned argc, Value* vp) {
83
CallArgs args = CallArgsFromVp(argc, vp);
84
args.rval().setString(cx->names().NumberFormat);
85
return true;
86
}
87
88
static const JSFunctionSpec numberFormat_static_methods[] = {
89
JS_SELF_HOSTED_FN("supportedLocalesOf",
90
"Intl_NumberFormat_supportedLocalesOf", 1, 0),
91
JS_FS_END};
92
93
static const JSFunctionSpec numberFormat_methods[] = {
94
JS_SELF_HOSTED_FN("resolvedOptions", "Intl_NumberFormat_resolvedOptions", 0,
95
0),
96
JS_SELF_HOSTED_FN("formatToParts", "Intl_NumberFormat_formatToParts", 1, 0),
97
JS_FN(js_toSource_str, numberFormat_toSource, 0, 0), JS_FS_END};
98
99
static const JSPropertySpec numberFormat_properties[] = {
100
JS_SELF_HOSTED_GET("format", "$Intl_NumberFormat_format_get", 0),
101
JS_STRING_SYM_PS(toStringTag, "Object", JSPROP_READONLY), JS_PS_END};
102
103
/**
104
* 11.2.1 Intl.NumberFormat([ locales [, options]])
105
*
106
* ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
107
*/
108
static bool NumberFormat(JSContext* cx, const CallArgs& args, bool construct) {
109
// Step 1 (Handled by OrdinaryCreateFromConstructor fallback code).
110
111
// Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
112
RootedObject proto(cx);
113
if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Null, &proto)) {
114
return false;
115
}
116
117
if (!proto) {
118
proto = GlobalObject::getOrCreateNumberFormatPrototype(cx, cx->global());
119
if (!proto) {
120
return false;
121
}
122
}
123
124
Rooted<NumberFormatObject*> numberFormat(cx);
125
numberFormat = NewObjectWithGivenProto<NumberFormatObject>(cx, proto);
126
if (!numberFormat) {
127
return false;
128
}
129
130
RootedValue thisValue(cx,
131
construct ? ObjectValue(*numberFormat) : args.thisv());
132
HandleValue locales = args.get(0);
133
HandleValue options = args.get(1);
134
135
// Step 3.
136
return intl::LegacyInitializeObject(
137
cx, numberFormat, cx->names().InitializeNumberFormat, thisValue, locales,
138
options, DateTimeFormatOptions::Standard, args.rval());
139
}
140
141
static bool NumberFormat(JSContext* cx, unsigned argc, Value* vp) {
142
CallArgs args = CallArgsFromVp(argc, vp);
143
return NumberFormat(cx, args, args.isConstructing());
144
}
145
146
bool js::intl_NumberFormat(JSContext* cx, unsigned argc, Value* vp) {
147
CallArgs args = CallArgsFromVp(argc, vp);
148
MOZ_ASSERT(args.length() == 2);
149
MOZ_ASSERT(!args.isConstructing());
150
// intl_NumberFormat is an intrinsic for self-hosted JavaScript, so it
151
// cannot be used with "new", but it still has to be treated as a
152
// constructor.
153
return NumberFormat(cx, args, true);
154
}
155
156
void js::NumberFormatObject::finalize(JSFreeOp* fop, JSObject* obj) {
157
MOZ_ASSERT(fop->onMainThread());
158
159
auto* numberFormat = &obj->as<NumberFormatObject>();
160
UNumberFormatter* nf = numberFormat->getNumberFormatter();
161
UFormattedNumber* formatted = numberFormat->getFormattedNumber();
162
163
if (nf) {
164
unumf_close(nf);
165
}
166
if (formatted) {
167
unumf_closeResult(formatted);
168
}
169
}
170
171
JSObject* js::CreateNumberFormatPrototype(JSContext* cx, HandleObject Intl,
172
Handle<GlobalObject*> global,
173
MutableHandleObject constructor) {
174
RootedFunction ctor(cx);
175
ctor = GlobalObject::createConstructor(cx, &NumberFormat,
176
cx->names().NumberFormat, 0);
177
if (!ctor) {
178
return nullptr;
179
}
180
181
RootedObject proto(
182
cx, GlobalObject::createBlankPrototype<PlainObject>(cx, global));
183
if (!proto) {
184
return nullptr;
185
}
186
187
if (!LinkConstructorAndPrototype(cx, ctor, proto)) {
188
return nullptr;
189
}
190
191
// 11.3.2
192
if (!JS_DefineFunctions(cx, ctor, numberFormat_static_methods)) {
193
return nullptr;
194
}
195
196
// 11.4.4
197
if (!JS_DefineFunctions(cx, proto, numberFormat_methods)) {
198
return nullptr;
199
}
200
201
// 11.4.2 and 11.4.3
202
if (!JS_DefineProperties(cx, proto, numberFormat_properties)) {
203
return nullptr;
204
}
205
206
// 8.1
207
RootedValue ctorValue(cx, ObjectValue(*ctor));
208
if (!DefineDataProperty(cx, Intl, cx->names().NumberFormat, ctorValue, 0)) {
209
return nullptr;
210
}
211
212
constructor.set(ctor);
213
return proto;
214
}
215
216
bool js::intl_NumberFormat_availableLocales(JSContext* cx, unsigned argc,
217
Value* vp) {
218
CallArgs args = CallArgsFromVp(argc, vp);
219
MOZ_ASSERT(args.length() == 0);
220
221
return GetAvailableLocales(cx, unum_countAvailable, unum_getAvailable,
222
args.rval());
223
}
224
225
bool js::intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp) {
226
CallArgs args = CallArgsFromVp(argc, vp);
227
MOZ_ASSERT(args.length() == 1);
228
MOZ_ASSERT(args[0].isString());
229
230
UniqueChars locale = intl::EncodeLocale(cx, args[0].toString());
231
if (!locale) {
232
return false;
233
}
234
235
UErrorCode status = U_ZERO_ERROR;
236
UNumberingSystem* numbers = unumsys_open(IcuLocale(locale.get()), &status);
237
if (U_FAILURE(status)) {
238
intl::ReportInternalError(cx);
239
return false;
240
}
241
242
ScopedICUObject<UNumberingSystem, unumsys_close> toClose(numbers);
243
244
const char* name = unumsys_getName(numbers);
245
if (!name) {
246
intl::ReportInternalError(cx);
247
return false;
248
}
249
250
JSString* jsname = NewStringCopyZ<CanGC>(cx, name);
251
if (!jsname) {
252
return false;
253
}
254
255
args.rval().setString(jsname);
256
return true;
257
}
258
259
#if DEBUG || MOZ_SYSTEM_ICU
260
class UResourceBundleDeleter {
261
public:
262
void operator()(UResourceBundle* aPtr) { ures_close(aPtr); }
263
};
264
265
using UniqueUResourceBundle =
266
mozilla::UniquePtr<UResourceBundle, UResourceBundleDeleter>;
267
268
bool js::intl_availableMeasurementUnits(JSContext* cx, unsigned argc,
269
Value* vp) {
270
CallArgs args = CallArgsFromVp(argc, vp);
271
MOZ_ASSERT(args.length() == 0);
272
273
RootedObject measurementUnits(
274
cx, NewObjectWithGivenProto<PlainObject>(cx, nullptr));
275
if (!measurementUnits) {
276
return false;
277
}
278
279
// Lookup the available measurement units in the resource boundle of the root
280
// locale.
281
282
static const char packageName[] =
283
U_ICUDATA_NAME U_TREE_SEPARATOR_STRING "unit";
284
static const char rootLocale[] = "";
285
286
UErrorCode status = U_ZERO_ERROR;
287
UResourceBundle* rawRes = ures_open(packageName, rootLocale, &status);
288
if (U_FAILURE(status)) {
289
intl::ReportInternalError(cx);
290
return false;
291
}
292
UniqueUResourceBundle res(rawRes);
293
294
UResourceBundle* rawUnits =
295
ures_getByKey(res.get(), "units", nullptr, &status);
296
if (U_FAILURE(status)) {
297
intl::ReportInternalError(cx);
298
return false;
299
}
300
UniqueUResourceBundle units(rawUnits);
301
302
RootedAtom unitAtom(cx);
303
304
int32_t unitsSize = ures_getSize(units.get());
305
for (int32_t i = 0; i < unitsSize; i++) {
306
UResourceBundle* rawType =
307
ures_getByIndex(units.get(), i, nullptr, &status);
308
if (U_FAILURE(status)) {
309
intl::ReportInternalError(cx);
310
return false;
311
}
312
UniqueUResourceBundle type(rawType);
313
314
int32_t typeSize = ures_getSize(type.get());
315
for (int32_t j = 0; j < typeSize; j++) {
316
UResourceBundle* rawSubtype =
317
ures_getByIndex(type.get(), j, nullptr, &status);
318
if (U_FAILURE(status)) {
319
intl::ReportInternalError(cx);
320
return false;
321
}
322
UniqueUResourceBundle subtype(rawSubtype);
323
324
const char* unitIdentifier = ures_getKey(subtype.get());
325
326
unitAtom = Atomize(cx, unitIdentifier, strlen(unitIdentifier));
327
if (!unitAtom) {
328
return false;
329
}
330
if (!DefineDataProperty(cx, measurementUnits, unitAtom->asPropertyName(),
331
TrueHandleValue)) {
332
return false;
333
}
334
}
335
}
336
337
args.rval().setObject(*measurementUnits);
338
return true;
339
}
340
#endif
341
342
bool js::intl::NumberFormatterSkeleton::currency(JSLinearString* currency) {
343
MOZ_ASSERT(currency->length() == 3,
344
"IsWellFormedCurrencyCode permits only length-3 strings");
345
346
char16_t currencyChars[] = {currency->latin1OrTwoByteChar(0),
347
currency->latin1OrTwoByteChar(1),
348
currency->latin1OrTwoByteChar(2), '\0'};
349
return append(u"currency/") && append(currencyChars) && append(' ');
350
}
351
352
bool js::intl::NumberFormatterSkeleton::currencyDisplay(
353
CurrencyDisplay display) {
354
switch (display) {
355
case CurrencyDisplay::Code:
356
return appendToken(u"unit-width-iso-code");
357
case CurrencyDisplay::Name:
358
return appendToken(u"unit-width-full-name");
359
case CurrencyDisplay::Symbol:
360
// Default, no additional tokens needed.
361
return true;
362
case CurrencyDisplay::NarrowSymbol:
363
return appendToken(u"unit-width-narrow");
364
}
365
MOZ_CRASH("unexpected currency display type");
366
}
367
368
struct MeasureUnit {
369
const char* const type;
370
const char* const subtype;
371
};
372
373
/**
374
* The list of currently supported simple unit identifiers.
375
*
376
* Note: Keep in sync with the measure unit lists in
377
* - js/src/builtin/intl/NumberFormat.js
378
* - intl/icu/data_filter.json
379
*
380
* The list must be kept in alphabetical order of the |subtype|.
381
*/
382
const MeasureUnit simpleMeasureUnits[] = {
383
// clang-format off
384
{"area", "acre"},
385
{"digital", "bit"},
386
{"digital", "byte"},
387
{"temperature", "celsius"},
388
{"length", "centimeter"},
389
{"duration", "day"},
390
{"angle", "degree"},
391
{"temperature", "fahrenheit"},
392
{"volume", "fluid-ounce"},
393
{"length", "foot"},
394
{"volume", "gallon"},
395
{"digital", "gigabit"},
396
{"digital", "gigabyte"},
397
{"mass", "gram"},
398
{"area", "hectare"},
399
{"duration", "hour"},
400
{"length", "inch"},
401
{"digital", "kilobit"},
402
{"digital", "kilobyte"},
403
{"mass", "kilogram"},
404
{"length", "kilometer"},
405
{"volume", "liter"},
406
{"digital", "megabit"},
407
{"digital", "megabyte"},
408
{"length", "meter"},
409
{"length", "mile"},
410
{"length", "mile-scandinavian"},
411
{"volume", "milliliter"},
412
{"length", "millimeter"},
413
{"duration", "millisecond"},
414
{"duration", "minute"},
415
{"duration", "month"},
416
{"mass", "ounce"},
417
{"concentr", "percent"},
418
{"digital", "petabyte"},
419
{"mass", "pound"},
420
{"duration", "second"},
421
{"mass", "stone"},
422
{"digital", "terabit"},
423
{"digital", "terabyte"},
424
{"duration", "week"},
425
{"length", "yard"},
426
{"duration", "year"},
427
// clang-format on
428
};
429
430
static const MeasureUnit& FindSimpleMeasureUnit(const char* subtype) {
431
auto measureUnit = std::lower_bound(
432
std::begin(simpleMeasureUnits), std::end(simpleMeasureUnits), subtype,
433
[](const auto& measureUnit, const char* subtype) {
434
return strcmp(measureUnit.subtype, subtype) < 0;
435
});
436
MOZ_ASSERT(measureUnit != std::end(simpleMeasureUnits),
437
"unexpected unit identifier: unit not found");
438
MOZ_ASSERT(strcmp(measureUnit->subtype, subtype) == 0,
439
"unexpected unit identifier: wrong unit found");
440
return *measureUnit;
441
}
442
443
static constexpr size_t MaxUnitLength() {
444
// Enable by default when bug 1560664 is fixed.
445
#if __cplusplus >= 201703L
446
size_t length = 0;
447
for (const auto& unit : simpleMeasureUnits) {
448
length = std::max(length, std::char_traits<char>::length(unit.subtype));
449
}
450
return length * 2 + std::char_traits<char>::length("-per-");
451
#else
452
return mozilla::ArrayLength("mile-scandinavian-per-mile-scandinavian") - 1;
453
#endif
454
}
455
456
bool js::intl::NumberFormatterSkeleton::unit(JSLinearString* unit) {
457
MOZ_RELEASE_ASSERT(unit->length() <= MaxUnitLength());
458
459
char unitChars[MaxUnitLength() + 1] = {};
460
CopyChars(reinterpret_cast<Latin1Char*>(unitChars), *unit);
461
462
auto appendUnit = [this](const MeasureUnit& unit) {
463
return append(unit.type, strlen(unit.type)) && append('-') &&
464
append(unit.subtype, strlen(unit.subtype));
465
};
466
467
// |unit| can be a compound unit identifier, separated by "-per-".
468
469
static constexpr char separator[] = "-per-";
470
if (char* p = strstr(unitChars, separator)) {
471
// Split into two strings.
472
p[0] = '\0';
473
474
auto& numerator = FindSimpleMeasureUnit(unitChars);
475
if (!append(u"measure-unit/") || !appendUnit(numerator) || !append(' ')) {
476
return false;
477
}
478
479
auto& denominator = FindSimpleMeasureUnit(p + strlen(separator));
480
if (!append(u"per-measure-unit/") || !appendUnit(denominator) ||
481
!append(' ')) {
482
return false;
483
}
484
} else {
485
auto& simple = FindSimpleMeasureUnit(unitChars);
486
if (!append(u"measure-unit/") || !appendUnit(simple) || !append(' ')) {
487
return false;
488
}
489
}
490
return true;
491
}
492
493
bool js::intl::NumberFormatterSkeleton::unitDisplay(UnitDisplay display) {
494
switch (display) {
495
case UnitDisplay::Short:
496
return appendToken(u"unit-width-short");
497
case UnitDisplay::Narrow:
498
return appendToken(u"unit-width-narrow");
499
case UnitDisplay::Long:
500
return appendToken(u"unit-width-full-name");
501
}
502
MOZ_CRASH("unexpected unit display type");
503
}
504
505
bool js::intl::NumberFormatterSkeleton::percent() {
506
return appendToken(u"percent scale/100");
507
}
508
509
bool js::intl::NumberFormatterSkeleton::fractionDigits(uint32_t min,
510
uint32_t max) {
511
// Note: |min| can be zero here.
512
MOZ_ASSERT(min <= max);
513
return append('.') && appendN('0', min) && appendN('#', max - min) &&
514
append(' ');
515
}
516
517
bool js::intl::NumberFormatterSkeleton::integerWidth(uint32_t min) {
518
MOZ_ASSERT(min > 0);
519
return append(u"integer-width/+") && appendN('0', min) && append(' ');
520
}
521
522
bool js::intl::NumberFormatterSkeleton::significantDigits(uint32_t min,
523
uint32_t max) {
524
MOZ_ASSERT(min > 0);
525
MOZ_ASSERT(min <= max);
526
return appendN('@', min) && appendN('#', max - min) && append(' ');
527
}
528
529
bool js::intl::NumberFormatterSkeleton::useGrouping(bool on) {
530
return on || appendToken(u"group-off");
531
}
532
533
bool js::intl::NumberFormatterSkeleton::notation(Notation style) {
534
switch (style) {
535
case Notation::Standard:
536
// Default, no additional tokens needed.
537
return true;
538
case Notation::Scientific:
539
return appendToken(u"scientific");
540
case Notation::Engineering:
541
return appendToken(u"engineering");
542
case Notation::CompactShort:
543
return appendToken(u"compact-short");
544
case Notation::CompactLong:
545
return appendToken(u"compact-long");
546
}
547
MOZ_CRASH("unexpected notation style");
548
}
549
550
bool js::intl::NumberFormatterSkeleton::signDisplay(SignDisplay display) {
551
switch (display) {
552
case SignDisplay::Auto:
553
// Default, no additional tokens needed.
554
return true;
555
case SignDisplay::Always:
556
return appendToken(u"sign-always");
557
case SignDisplay::Never:
558
return appendToken(u"sign-never");
559
case SignDisplay::ExceptZero:
560
return appendToken(u"sign-except-zero");
561
case SignDisplay::Accounting:
562
return appendToken(u"sign-accounting");
563
case SignDisplay::AccountingAlways:
564
return appendToken(u"sign-accounting-always");
565
case SignDisplay::AccountingExceptZero:
566
return appendToken(u"sign-accounting-except-zero");
567
}
568
MOZ_CRASH("unexpected sign display type");
569
}
570
571
bool js::intl::NumberFormatterSkeleton::roundingModeHalfUp() {
572
return appendToken(u"rounding-mode-half-up");
573
}
574
575
UNumberFormatter* js::intl::NumberFormatterSkeleton::toFormatter(
576
JSContext* cx, const char* locale) {
577
UErrorCode status = U_ZERO_ERROR;
578
UNumberFormatter* nf = unumf_openForSkeletonAndLocale(
579
vector_.begin(), vector_.length(), locale, &status);
580
if (U_FAILURE(status)) {
581
intl::ReportInternalError(cx);
582
return nullptr;
583
}
584
return nf;
585
}
586
587
/**
588
* Returns a new UNumberFormatter with the locale and number formatting options
589
* of the given NumberFormat.
590
*/
591
static UNumberFormatter* NewUNumberFormatter(
592
JSContext* cx, Handle<NumberFormatObject*> numberFormat) {
593
RootedValue value(cx);
594
595
RootedObject internals(cx, intl::GetInternalsObject(cx, numberFormat));
596
if (!internals) {
597
return nullptr;
598
}
599
600
if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) {
601
return nullptr;
602
}
603
UniqueChars locale = intl::EncodeLocale(cx, value.toString());
604
if (!locale) {
605
return nullptr;
606
}
607
608
intl::NumberFormatterSkeleton skeleton(cx);
609
610
// We don't need to look at numberingSystem - it can only be set via
611
// the Unicode locale extension and is therefore already set on locale.
612
613
if (!GetProperty(cx, internals, internals, cx->names().style, &value)) {
614
return nullptr;
615
}
616
617
bool accountingSign = false;
618
{
619
JSLinearString* style = value.toString()->ensureLinear(cx);
620
if (!style) {
621
return nullptr;
622
}
623
624
if (StringEqualsAscii(style, "currency")) {
625
if (!GetProperty(cx, internals, internals, cx->names().currency,
626
&value)) {
627
return nullptr;
628
}
629
JSLinearString* currency = value.toString()->ensureLinear(cx);
630
if (!currency) {
631
return nullptr;
632
}
633
634
if (!skeleton.currency(currency)) {
635
return nullptr;
636
}
637
638
if (!GetProperty(cx, internals, internals, cx->names().currencyDisplay,
639
&value)) {
640
return nullptr;
641
}
642
JSLinearString* currencyDisplay = value.toString()->ensureLinear(cx);
643
if (!currencyDisplay) {
644
return nullptr;
645
}
646
647
using CurrencyDisplay = intl::NumberFormatterSkeleton::CurrencyDisplay;
648
649
CurrencyDisplay display;
650
if (StringEqualsAscii(currencyDisplay, "code")) {
651
display = CurrencyDisplay::Code;
652
} else if (StringEqualsAscii(currencyDisplay, "symbol")) {
653
display = CurrencyDisplay::Symbol;
654
} else if (StringEqualsAscii(currencyDisplay, "narrowSymbol")) {
655
display = CurrencyDisplay::NarrowSymbol;
656
} else {
657
MOZ_ASSERT(StringEqualsAscii(currencyDisplay, "name"));
658
display = CurrencyDisplay::Name;
659
}
660
661
if (!skeleton.currencyDisplay(display)) {
662
return nullptr;
663
}
664
665
if (!GetProperty(cx, internals, internals, cx->names().currencySign,
666
&value)) {
667
return nullptr;
668
}
669
JSLinearString* currencySign = value.toString()->ensureLinear(cx);
670
if (!currencySign) {
671
return nullptr;
672
}
673
674
if (StringEqualsAscii(currencySign, "accounting")) {
675
accountingSign = true;
676
} else {
677
MOZ_ASSERT(StringEqualsAscii(currencySign, "standard"));
678
}
679
} else if (StringEqualsAscii(style, "percent")) {
680
if (!skeleton.percent()) {
681
return nullptr;
682
}
683
} else if (StringEqualsAscii(style, "unit")) {
684
if (!GetProperty(cx, internals, internals, cx->names().unit, &value)) {
685
return nullptr;
686
}
687
JSLinearString* unit = value.toString()->ensureLinear(cx);
688
if (!unit) {
689
return nullptr;
690
}
691
692
if (!skeleton.unit(unit)) {
693
return nullptr;
694
}
695
696
if (!GetProperty(cx, internals, internals, cx->names().unitDisplay,
697
&value)) {
698
return nullptr;
699
}
700
JSLinearString* unitDisplay = value.toString()->ensureLinear(cx);
701
if (!unitDisplay) {
702
return nullptr;
703
}
704
705
using UnitDisplay = intl::NumberFormatterSkeleton::UnitDisplay;
706
707
UnitDisplay display;
708
if (StringEqualsAscii(unitDisplay, "short")) {
709
display = UnitDisplay::Short;
710
} else if (StringEqualsAscii(unitDisplay, "narrow")) {
711
display = UnitDisplay::Narrow;
712
} else {
713
MOZ_ASSERT(StringEqualsAscii(unitDisplay, "long"));
714
display = UnitDisplay::Long;
715
}
716
717
if (!skeleton.unitDisplay(display)) {
718
return nullptr;
719
}
720
} else {
721
MOZ_ASSERT(StringEqualsAscii(style, "decimal"));
722
}
723
}
724
725
bool hasMinimumSignificantDigits;
726
if (!HasProperty(cx, internals, cx->names().minimumSignificantDigits,
727
&hasMinimumSignificantDigits)) {
728
return nullptr;
729
}
730
731
if (hasMinimumSignificantDigits) {
732
if (!GetProperty(cx, internals, internals,
733
cx->names().minimumSignificantDigits, &value)) {
734
return nullptr;
735
}
736
uint32_t minimumSignificantDigits = AssertedCast<uint32_t>(value.toInt32());
737
738
if (!GetProperty(cx, internals, internals,
739
cx->names().maximumSignificantDigits, &value)) {
740
return nullptr;
741
}
742
uint32_t maximumSignificantDigits = AssertedCast<uint32_t>(value.toInt32());
743
744
if (!skeleton.significantDigits(minimumSignificantDigits,
745
maximumSignificantDigits)) {
746
return nullptr;
747
}
748
}
749
750
bool hasMinimumFractionDigits;
751
if (!HasProperty(cx, internals, cx->names().minimumFractionDigits,
752
&hasMinimumFractionDigits)) {
753
return nullptr;
754
}
755
756
if (hasMinimumFractionDigits) {
757
if (!GetProperty(cx, internals, internals,
758
cx->names().minimumFractionDigits, &value)) {
759
return nullptr;
760
}
761
uint32_t minimumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
762
763
if (!GetProperty(cx, internals, internals,
764
cx->names().maximumFractionDigits, &value)) {
765
return nullptr;
766
}
767
uint32_t maximumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
768
769
if (!skeleton.fractionDigits(minimumFractionDigits,
770
maximumFractionDigits)) {
771
return nullptr;
772
}
773
}
774
775
if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
776
&value)) {
777
return nullptr;
778
}
779
uint32_t minimumIntegerDigits = AssertedCast<uint32_t>(value.toInt32());
780
781
if (!skeleton.integerWidth(minimumIntegerDigits)) {
782
return nullptr;
783
}
784
785
if (!GetProperty(cx, internals, internals, cx->names().useGrouping, &value)) {
786
return nullptr;
787
}
788
if (!skeleton.useGrouping(value.toBoolean())) {
789
return nullptr;
790
}
791
792
if (!GetProperty(cx, internals, internals, cx->names().notation, &value)) {
793
return nullptr;
794
}
795
796
{
797
JSLinearString* notation = value.toString()->ensureLinear(cx);
798
if (!notation) {
799
return nullptr;
800
}
801
802
using Notation = intl::NumberFormatterSkeleton::Notation;
803
804
Notation style;
805
if (StringEqualsAscii(notation, "standard")) {
806
style = Notation::Standard;
807
} else if (StringEqualsAscii(notation, "scientific")) {
808
style = Notation::Scientific;
809
} else if (StringEqualsAscii(notation, "engineering")) {
810
style = Notation::Engineering;
811
} else {
812
MOZ_ASSERT(StringEqualsAscii(notation, "compact"));
813
814
if (!GetProperty(cx, internals, internals, cx->names().compactDisplay,
815
&value)) {
816
return nullptr;
817
}
818
819
JSLinearString* compactDisplay = value.toString()->ensureLinear(cx);
820
if (!compactDisplay) {
821
return nullptr;
822
}
823
824
if (StringEqualsAscii(compactDisplay, "short")) {
825
style = Notation::CompactShort;
826
} else {
827
MOZ_ASSERT(StringEqualsAscii(compactDisplay, "long"));
828
style = Notation::CompactLong;
829
}
830
}
831
832
if (!skeleton.notation(style)) {
833
return nullptr;
834
}
835
}
836
837
if (!GetProperty(cx, internals, internals, cx->names().signDisplay, &value)) {
838
return nullptr;
839
}
840
841
{
842
JSLinearString* signDisplay = value.toString()->ensureLinear(cx);
843
if (!signDisplay) {
844
return nullptr;
845
}
846
847
using SignDisplay = intl::NumberFormatterSkeleton::SignDisplay;
848
849
SignDisplay display;
850
if (StringEqualsAscii(signDisplay, "auto")) {
851
if (accountingSign) {
852
display = SignDisplay::Accounting;
853
} else {
854
display = SignDisplay::Auto;
855
}
856
} else if (StringEqualsAscii(signDisplay, "never")) {
857
display = SignDisplay::Never;
858
} else if (StringEqualsAscii(signDisplay, "always")) {
859
if (accountingSign) {
860
display = SignDisplay::AccountingAlways;
861
} else {
862
display = SignDisplay::Always;
863
}
864
} else {
865
MOZ_ASSERT(StringEqualsAscii(signDisplay, "exceptZero"));
866
if (accountingSign) {
867
display = SignDisplay::AccountingExceptZero;
868
} else {
869
display = SignDisplay::ExceptZero;
870
}
871
}
872
873
if (!skeleton.signDisplay(display)) {
874
return nullptr;
875
}
876
}
877
878
if (!skeleton.roundingModeHalfUp()) {
879
return nullptr;
880
}
881
882
return skeleton.toFormatter(cx, locale.get());
883
}
884
885
static UFormattedNumber* NewUFormattedNumber(JSContext* cx) {
886
UErrorCode status = U_ZERO_ERROR;
887
UFormattedNumber* formatted = unumf_openResult(&status);
888
if (U_FAILURE(status)) {
889
intl::ReportInternalError(cx);
890
return nullptr;
891
}
892
return formatted;
893
}
894
895
// We also support UFormattedNumber in addition to UFormattedValue, in case
896
// we're compiling against a system ICU which doesn't expose draft APIs.
897
898
#ifndef U_HIDE_DRAFT_API
899
using PartitionNumberPatternResult = const UFormattedValue*;
900
#else
901
using PartitionNumberPatternResult = const UFormattedNumber*;
902
#endif
903
904
static PartitionNumberPatternResult PartitionNumberPattern(
905
JSContext* cx, const UNumberFormatter* nf, UFormattedNumber* formatted,
906
HandleValue x) {
907
UErrorCode status = U_ZERO_ERROR;
908
if (x.isNumber()) {
909
double num = x.toNumber();
910
911
// ICU incorrectly formats NaN values with the sign bit set, as if they
912
// were negative. Replace all NaNs with a single pattern with sign bit
913
// unset ("positive", that is) until ICU is fixed.
914
if (MOZ_UNLIKELY(IsNaN(num))) {
915
num = SpecificNaN<double>(0, 1);
916
}
917
918
unumf_formatDouble(nf, num, formatted, &status);
919
} else {
920
RootedBigInt bi(cx, x.toBigInt());
921
922
int64_t num;
923
if (BigInt::isInt64(bi, &num)) {
924
unumf_formatInt(nf, num, formatted, &status);
925
} else {
926
JSLinearString* str = BigInt::toString<CanGC>(cx, bi, 10);
927
if (!str) {
928
return nullptr;
929
}
930
MOZ_ASSERT(str->hasLatin1Chars());
931
932
// Tell the analysis the |unumf_formatDecimal| function can't GC.
933
JS::AutoSuppressGCAnalysis nogc;
934
935
const char* chars = reinterpret_cast<const char*>(str->latin1Chars(nogc));
936
unumf_formatDecimal(nf, chars, str->length(), formatted, &status);
937
}
938
}
939
if (U_FAILURE(status)) {
940
intl::ReportInternalError(cx);
941
return nullptr;
942
}
943
944
#ifndef U_HIDE_DRAFT_API
945
const UFormattedValue* formattedValue =
946
unumf_resultAsValue(formatted, &status);
947
if (U_FAILURE(status)) {
948
intl::ReportInternalError(cx);
949
return nullptr;
950
}
951
return formattedValue;
952
#else
953
return formatted;
954
#endif
955
}
956
957
static JSString* FormattedNumberToString(
958
JSContext* cx, PartitionNumberPatternResult formattedValue) {
959
#ifndef U_HIDE_DRAFT_API
960
static_assert(
961
std::is_same<PartitionNumberPatternResult, const UFormattedValue*>::value,
962
"UFormattedValue arm");
963
964
UErrorCode status = U_ZERO_ERROR;
965
int32_t strLength;
966
const char16_t* str = ufmtval_getString(formattedValue, &strLength, &status);
967
if (U_FAILURE(status)) {
968
intl::ReportInternalError(cx);
969
return nullptr;
970
}
971
972
return NewStringCopyN<CanGC>(cx, str, AssertedCast<uint32_t>(strLength));
973
#else
974
static_assert(std::is_same<PartitionNumberPatternResult,
975
const UFormattedNumber*>::value,
976
"UFormattedNumber arm");
977
978
return CallICU(cx,
979
[formatted](UChar* chars, int32_t size, UErrorCode* status) {
980
return unumf_resultToString(formatted, chars, size, status);
981
});
982
#endif
983
}
984
985
static bool FormatNumeric(JSContext* cx, const UNumberFormatter* nf,
986
UFormattedNumber* formatted, HandleValue x,
987
MutableHandleValue result) {
988
PartitionNumberPatternResult formattedValue =
989
PartitionNumberPattern(cx, nf, formatted, x);
990
if (!formattedValue) {
991
return false;
992
}
993
994
JSString* str = FormattedNumberToString(cx, formattedValue);
995
if (!str) {
996
return false;
997
}
998
999
result.setString(str);
1000
return true;
1001
}
1002
1003
static FieldType GetFieldTypeForNumberField(UNumberFormatFields fieldName,
1004
HandleValue x) {
1005
// See intl/icu/source/i18n/unicode/unum.h for a detailed field list. This
1006
// list is deliberately exhaustive: cases might have to be added/removed if
1007
// this code is compiled with a different ICU with more UNumberFormatFields
1008
// enum initializers. Please guard such cases with appropriate ICU
1009
// version-testing #ifdefs, should cross-version divergence occur.
1010
switch (fieldName) {
1011
case UNUM_INTEGER_FIELD:
1012
if (x.isNumber()) {
1013
double d = x.toNumber();
1014
if (IsNaN(d)) {
1015
return &JSAtomState::nan;
1016
}
1017
if (!IsFinite(d)) {
1018
return &JSAtomState::infinity;
1019
}
1020
}
1021
return &JSAtomState::integer;
1022
1023
case UNUM_GROUPING_SEPARATOR_FIELD:
1024
return &JSAtomState::group;
1025
1026
case UNUM_DECIMAL_SEPARATOR_FIELD:
1027
return &JSAtomState::decimal;
1028
1029
case UNUM_FRACTION_FIELD:
1030
return &JSAtomState::fraction;
1031
1032
case UNUM_SIGN_FIELD: {
1033
// We coerce all NaNs to one with the sign bit unset, so all NaNs are
1034
// positive in our implementation.
1035
bool isNegative = x.isNumber()
1036
? !IsNaN(x.toNumber()) && IsNegative(x.toNumber())
1037
: x.toBigInt()->isNegative();
1038
return isNegative ? &JSAtomState::minusSign : &JSAtomState::plusSign;
1039
}
1040
1041
case UNUM_PERCENT_FIELD:
1042
return &JSAtomState::percentSign;
1043
1044
case UNUM_CURRENCY_FIELD:
1045
return &JSAtomState::currency;
1046
1047
case UNUM_PERMILL_FIELD:
1048
MOZ_ASSERT_UNREACHABLE(
1049
"unexpected permill field found, even though "
1050
"we don't use any user-defined patterns that "
1051
"would require a permill field");
1052
break;
1053
1054
case UNUM_EXPONENT_SYMBOL_FIELD:
1055
return &JSAtomState::exponentSeparator;
1056
1057
case UNUM_EXPONENT_SIGN_FIELD:
1058
return &JSAtomState::exponentMinusSign;
1059
1060
case UNUM_EXPONENT_FIELD:
1061
return &JSAtomState::exponentInteger;
1062
1063
#ifndef U_HIDE_DRAFT_API
1064
case UNUM_MEASURE_UNIT_FIELD:
1065
return &JSAtomState::unit;
1066
1067
case UNUM_COMPACT_FIELD:
1068
return &JSAtomState::compact;
1069
#endif
1070
1071
#ifndef U_HIDE_DEPRECATED_API
1072
case UNUM_FIELD_COUNT:
1073
MOZ_ASSERT_UNREACHABLE(
1074
"format field sentinel value returned by iterator!");
1075
break;
1076
#endif
1077
}
1078
1079
MOZ_ASSERT_UNREACHABLE(
1080
"unenumerated, undocumented format field returned by iterator");
1081
return nullptr;
1082
}
1083
1084
struct Field {
1085
uint32_t begin;
1086
uint32_t end;
1087
FieldType type;
1088
1089
// Needed for vector-resizing scratch space.
1090
Field() = default;
1091
1092
Field(uint32_t begin, uint32_t end, FieldType type)
1093
: begin(begin), end(end), type(type) {}
1094
};
1095
1096
class NumberFormatFields {
1097
using FieldsVector = Vector<Field, 16>;
1098
1099
FieldsVector fields_;
1100
HandleValue number_;
1101
1102
public:
1103
NumberFormatFields(JSContext* cx, HandleValue number)
1104
: fields_(cx), number_(number) {}
1105
1106
MOZ_MUST_USE bool append(int32_t field, int32_t begin, int32_t end);
1107
1108
MOZ_MUST_USE ArrayObject* toArray(JSContext* cx,
1109
JS::HandleString overallResult,
1110
FieldType unitType);
1111
};
1112
1113
bool NumberFormatFields::append(int32_t field, int32_t begin, int32_t end) {
1114
MOZ_ASSERT(begin >= 0);
1115
MOZ_ASSERT(end >= 0);
1116
MOZ_ASSERT(begin < end, "erm, aren't fields always non-empty?");
1117
1118
FieldType type =
1119
GetFieldTypeForNumberField(UNumberFormatFields(field), number_);
1120
return fields_.emplaceBack(uint32_t(begin), uint32_t(end), type);
1121
}
1122
1123
ArrayObject* NumberFormatFields::toArray(JSContext* cx,
1124
HandleString overallResult,
1125
FieldType unitType) {
1126
// Merge sort the fields vector. Expand the vector to have scratch space for
1127
// performing the sort.
1128
size_t fieldsLen = fields_.length();
1129
if (!fields_.growByUninitialized(fieldsLen)) {
1130
return nullptr;
1131
}
1132
1133
MOZ_ALWAYS_TRUE(MergeSort(
1134
fields_.begin(), fieldsLen, fields_.begin() + fieldsLen,
1135
[](const Field& left, const Field& right, bool* lessOrEqual) {
1136
// Sort first by begin index, then to place
1137
// enclosing fields before nested fields.
1138
*lessOrEqual = left.begin < right.begin ||
1139
(left.begin == right.begin && left.end > right.end);
1140
return true;
1141
}));
1142
1143
// Delete the elements in the scratch space.
1144
fields_.shrinkBy(fieldsLen);
1145
1146
// Then iterate over the sorted field list to generate a sequence of parts
1147
// (what ECMA-402 actually exposes). A part is a maximal character sequence
1148
// entirely within no field or a single most-nested field.
1149
//
1150
// Diagrams may be helpful to illustrate how fields map to parts. Consider
1151
// formatting -19,766,580,028,249.41, the US national surplus (negative
1152
// because it's actually a debt) on October 18, 2016.
1153
//
1154
// var options =
1155
// { style: "currency", currency: "USD", currencyDisplay: "name" };
1156
// var usdFormatter = new Intl.NumberFormat("en-US", options);
1157
// usdFormatter.format(-19766580028249.41);
1158
//
1159
// The formatted result is "-19,766,580,028,249.41 US dollars". ICU
1160
// identifies these fields in the string:
1161
//
1162
// UNUM_GROUPING_SEPARATOR_FIELD
1163
// |
1164
// UNUM_SIGN_FIELD | UNUM_DECIMAL_SEPARATOR_FIELD
1165
// | __________/| |
1166
// | / | | | |
1167
// "-19,766,580,028,249.41 US dollars"
1168
// \________________/ |/ \_______/
1169
// | | |
1170
// UNUM_INTEGER_FIELD | UNUM_CURRENCY_FIELD
1171
// |
1172
// UNUM_FRACTION_FIELD
1173
//
1174
// These fields map to parts as follows:
1175
//
1176
// integer decimal
1177
// _____|________ |
1178
// / /| |\ |\ |\ | literal
1179
// /| / | | \ | \ | \| |
1180
// "-19,766,580,028,249.41 US dollars"
1181
// | \___|___|___/ |/ \________/
1182
// | | | |
1183
// | group | currency
1184
// | |
1185
// minusSign fraction
1186
//
1187
// The sign is a part. Each comma is a part, splitting the integer field
1188
// into parts for trillions/billions/&c. digits. The decimal point is a
1189
// part. Cents are a part. The space between cents and currency is a part
1190
// (outside any field). Last, the currency field is a part.
1191
//
1192
// Because parts fully partition the formatted string, we only track the
1193
// end of each part -- the beginning is implicitly the last part's end.
1194
struct Part {
1195
uint32_t end;
1196
FieldType type;
1197
};
1198
1199
class PartGenerator {
1200
// The fields in order from start to end, then least to most nested.
1201
const FieldsVector& fields;
1202
1203
// Index of the current field, in |fields|, being considered to
1204
// determine part boundaries. |lastEnd <= fields[index].begin| is an
1205
// invariant.
1206
size_t index;
1207
1208
// The end index of the last part produced, always less than or equal
1209
// to |limit|, strictly increasing.
1210
uint32_t lastEnd;
1211
1212
// The length of the overall formatted string.
1213
const uint32_t limit;
1214
1215
Vector<size_t, 4> enclosingFields;
1216
1217
void popEnclosingFieldsEndingAt(uint32_t end) {
1218
MOZ_ASSERT_IF(enclosingFields.length() > 0,
1219
fields[enclosingFields.back()].end >= end);
1220
1221
while (enclosingFields.length() > 0 &&
1222
fields[enclosingFields.back()].end == end) {
1223
enclosingFields.popBack();
1224
}
1225
}
1226
1227
bool nextPartInternal(Part* part) {
1228
size_t len = fields.length();
1229
MOZ_ASSERT(index <= len);
1230
1231
// If we're out of fields, all that remains are part(s) consisting
1232
// of trailing portions of enclosing fields, and maybe a final
1233
// literal part.
1234
if (index == len) {
1235
if (enclosingFields.length() > 0) {
1236
const auto& enclosing = fields[enclosingFields.popCopy()];
1237
part->end = enclosing.end;
1238
part->type = enclosing.type;
1239
1240
// If additional enclosing fields end where this part ends,
1241
// pop them as well.
1242
popEnclosingFieldsEndingAt(part->end);
1243
} else {
1244
part->end = limit;
1245
part->type = &JSAtomState::literal;
1246
}
1247
1248
return true;
1249
}
1250
1251
// Otherwise we still have a field to process.
1252
const Field* current = &fields[index];
1253
MOZ_ASSERT(lastEnd <= current->begin);
1254
MOZ_ASSERT(current->begin < current->end);
1255
1256
// But first, deal with inter-field space.
1257
if (lastEnd < current->begin) {
1258
if (enclosingFields.length() > 0) {
1259
// Space between fields, within an enclosing field, is part
1260
// of that enclosing field, until the start of the current
1261
// field or the end of the enclosing field, whichever is
1262
// earlier.
1263
const auto& enclosing = fields[enclosingFields.back()];
1264
part->end = std::min(enclosing.end, current->begin);
1265
part->type = enclosing.type;
1266
popEnclosingFieldsEndingAt(part->end);
1267
} else {
1268
// If there's no enclosing field, the space is a literal.
1269
part->end = current->begin;
1270
part->type = &JSAtomState::literal;
1271
}
1272
1273
return true;
1274
}
1275
1276
// Otherwise, the part spans a prefix of the current field. Find
1277
// the most-nested field containing that prefix.
1278
const Field* next;
1279
do {
1280
current = &fields[index];
1281
1282
// If the current field is last, the part extends to its end.
1283
if (++index == len) {
1284
part->end = current->end;
1285
part->type = current->type;
1286
return true;
1287
}
1288
1289
next = &fields[index];
1290
MOZ_ASSERT(current->begin <= next->begin);
1291
MOZ_ASSERT(current->begin < next->end);
1292
1293
// If the next field nests within the current field, push an
1294
// enclosing field. (If there are no nested fields, don't
1295
// bother pushing a field that'd be immediately popped.)
1296
if (current->end > next->begin) {
1297
if (!enclosingFields.append(index - 1)) {
1298
return false;
1299
}
1300
}
1301
1302
// Do so until the next field begins after this one.
1303
} while (current->begin == next->begin);
1304
1305
part->type = current->type;
1306
1307
if (current->end <= next->begin) {
1308
// The next field begins after the current field ends. Therefore
1309
// the current part ends at the end of the current field.
1310
part->end = current->end;
1311
popEnclosingFieldsEndingAt(part->end);
1312
} else {
1313
// The current field encloses the next one. The current part
1314
// ends where the next field/part will start.
1315
part->end = next->begin;
1316
}
1317
1318
return true;
1319
}
1320
1321
public:
1322
PartGenerator(JSContext* cx, const FieldsVector& vec, uint32_t limit)
1323
: fields(vec),
1324
index(0),
1325
lastEnd(0),
1326
limit(limit),
1327
enclosingFields(cx) {}
1328
1329
bool nextPart(bool* hasPart, Part* part) {
1330
// There are no parts left if we've partitioned the entire string.
1331
if (lastEnd == limit) {
1332
MOZ_ASSERT(enclosingFields.length() == 0);
1333
*hasPart = false;
1334
return true;
1335
}
1336
1337
if (!nextPartInternal(part)) {
1338
return false;
1339
}
1340
1341
*hasPart = true;
1342
lastEnd = part->end;
1343
return true;
1344
}
1345
};
1346
1347
// Finally, generate the result array.
1348
size_t lastEndIndex = 0;
1349
RootedObject singlePart(cx);
1350
RootedValue propVal(cx);
1351
1352
RootedArrayObject partsArray(cx, NewDenseEmptyArray(cx));
1353
if (!partsArray) {
1354
return nullptr;
1355
}
1356
1357
PartGenerator gen(cx, fields_, overallResult->length());
1358
do {
1359
bool hasPart;
1360
Part part;
1361
if (!gen.nextPart(&hasPart, &part)) {
1362
return nullptr;
1363
}
1364
1365
if (!hasPart) {
1366
break;
1367
}
1368
1369
FieldType type = part.type;
1370
size_t endIndex = part.end;
1371
1372
MOZ_ASSERT(lastEndIndex < endIndex);
1373
1374
singlePart = NewBuiltinClassInstance<PlainObject>(cx);
1375
if (!singlePart) {
1376
return nullptr;
1377
}
1378
1379
propVal.setString(cx->names().*type);
1380
if (!DefineDataProperty(cx, singlePart, cx->names().type, propVal)) {
1381
return nullptr;
1382
}
1383
1384
JSLinearString* partSubstr = NewDependentString(
1385
cx, overallResult, lastEndIndex, endIndex - lastEndIndex);
1386
if (!partSubstr) {
1387
return nullptr;
1388
}
1389
1390
propVal.setString(partSubstr);
1391
if (!DefineDataProperty(cx, singlePart, cx->names().value, propVal)) {
1392
return nullptr;
1393
}
1394
1395
if (unitType != nullptr && type != &JSAtomState::literal) {
1396
propVal.setString(cx->names().*unitType);
1397
if (!DefineDataProperty(cx, singlePart, cx->names().unit, propVal)) {
1398
return nullptr;
1399
}
1400
}
1401
1402
if (!NewbornArrayPush(cx, partsArray, ObjectValue(*singlePart))) {
1403
return nullptr;
1404
}
1405
1406
lastEndIndex = endIndex;
1407
} while (true);
1408
1409
MOZ_ASSERT(lastEndIndex == overallResult->length(),
1410
"result array must partition the entire string");
1411
1412
return partsArray;
1413
}
1414
1415
#ifndef U_HIDE_DRAFT_API
1416
bool js::intl::FormattedNumberToParts(JSContext* cx,
1417
const UFormattedValue* formattedValue,
1418
HandleValue number, FieldType unitType,
1419
MutableHandleValue result) {
1420
RootedString overallResult(cx, FormattedNumberToString(cx, formattedValue));
1421
if (!overallResult) {
1422
return false;
1423
}
1424
1425
UErrorCode status = U_ZERO_ERROR;
1426
UConstrainedFieldPosition* fpos = ucfpos_open(&status);
1427
if (U_FAILURE(status)) {
1428
intl::ReportInternalError(cx);
1429
return false;
1430
}
1431
ScopedICUObject<UConstrainedFieldPosition, ucfpos_close> toCloseFpos(fpos);
1432
1433
// We're only interested in UFIELD_CATEGORY_NUMBER fields.
1434
ucfpos_constrainCategory(fpos, UFIELD_CATEGORY_NUMBER, &status);
1435
if (U_FAILURE(status)) {
1436
intl::ReportInternalError(cx);
1437
return false;
1438
}
1439
1440
// Vacuum up fields in the overall formatted string.
1441
1442
NumberFormatFields fields(cx, number);
1443
1444
while (true) {
1445
bool hasMore = ufmtval_nextPosition(formattedValue, fpos, &status);
1446
if (U_FAILURE(status)) {
1447
intl::ReportInternalError(cx);
1448
return false;
1449
}
1450
if (!hasMore) {
1451
break;
1452
}
1453
1454
int32_t field = ucfpos_getField(fpos, &status);
1455
if (U_FAILURE(status)) {
1456
intl::ReportInternalError(cx);
1457
return false;
1458
}
1459
1460
int32_t beginIndex, endIndex;
1461
ucfpos_getIndexes(fpos, &beginIndex, &endIndex, &status);
1462
if (U_FAILURE(status)) {
1463
intl::ReportInternalError(cx);
1464
return false;
1465
}
1466
1467
if (!fields.append(field, beginIndex, endIndex)) {
1468
return false;
1469
}
1470
}
1471
1472
ArrayObject* array = fields.toArray(cx, overallResult, unitType);
1473
if (!array) {
1474
return false;
1475
}
1476
1477
result.setObject(*array);
1478
return true;
1479
}
1480
#else
1481
static ArrayObject* LegacyFormattedNumberToParts(
1482
JSContext* cx, const UFormattedNumber* formatted, HandleValue x,
1483
MutableHandleValue result) {
1484
RootedString overallResult(cx, FormattedNumberToString(cx, formatted));
1485
if (!overallResult) {
1486
return false;
1487
}
1488
1489
UErrorCode status = U_ZERO_ERROR;
1490
UFieldPositionIterator* fpositer = ufieldpositer_open(&status);
1491
if (U_FAILURE(status)) {
1492
intl::ReportInternalError(cx);
1493
return false;
1494
}
1495
1496
MOZ_ASSERT(fpositer);
1497
ScopedICUObject<UFieldPositionIterator, ufieldpositer_close> toClose(
1498
fpositer);
1499
1500
unumf_resultGetAllFieldPositions(formatted, fpositer, &status);
1501
if (U_FAILURE(status)) {
1502
intl::ReportInternalError(cx);
1503
return false;
1504
}
1505
1506
// Vacuum up fields in the overall formatted string.
1507
1508
NumberFormatFields fields(cx, x);
1509
1510
int32_t field, beginIndex, endIndex;
1511
while ((field = ufieldpositer_next(fpositer, &beginIndex, &endIndex)) >= 0) {
1512
if (!fields.append(field, beginIndex, endIndex)) {
1513
return false;
1514
}
1515
}
1516
1517
ArrayObject* array = fields.toArray(cx, overallResult, nullptr);
1518
if (!array) {
1519
return false;
1520
}
1521
1522
result.setObject(*array);
1523
return true;
1524
}
1525
#endif
1526
1527
static bool FormatNumericToParts(JSContext* cx, const UNumberFormatter* nf,
1528
UFormattedNumber* formatted, HandleValue x,
1529
MutableHandleValue result) {
1530
PartitionNumberPatternResult formattedValue =
1531
PartitionNumberPattern(cx, nf, formatted, x);
1532
if (!formattedValue) {
1533
return false;
1534
}
1535
1536
#ifndef U_HIDE_DRAFT_API
1537
return intl::FormattedNumberToParts(cx, formattedValue, x, nullptr, result);
1538
#else
1539
return LegacyFormattedNumberToParts(cx, formattedValue, x, result);
1540
#endif
1541
}
1542
1543
bool js::intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp) {
1544
CallArgs args = CallArgsFromVp(argc, vp);
1545
MOZ_ASSERT(args.length() == 3);
1546
MOZ_ASSERT(args[0].isObject());
1547
MOZ_ASSERT(args[1].isNumeric());
1548
MOZ_ASSERT(args[2].isBoolean());
1549
1550
Rooted<NumberFormatObject*> numberFormat(
1551
cx, &args[0].toObject().as<NumberFormatObject>());
1552
1553
// Obtain a cached UNumberFormatter object.
1554
UNumberFormatter* nf = numberFormat->getNumberFormatter();
1555
if (!nf) {
1556
nf = NewUNumberFormatter(cx, numberFormat);
1557
if (!nf) {
1558
return false;
1559
}
1560
numberFormat->setNumberFormatter(nf);
1561
}
1562
1563
// Obtain a cached UFormattedNumber object.
1564
UFormattedNumber* formatted = numberFormat->getFormattedNumber();
1565
if (!formatted) {
1566
formatted = NewUFormattedNumber(cx);
1567
if (!formatted) {
1568
return false;
1569
}
1570
numberFormat->setFormattedNumber(formatted);
1571
}
1572
1573
// Use the UNumberFormatter to actually format the number.
1574
if (args[2].toBoolean()) {
1575
return FormatNumericToParts(cx, nf, formatted, args[1], args.rval());
1576
}
1577
1578
return FormatNumeric(cx, nf, formatted, args[1], args.rval());
1579
}