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