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