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