mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 21:01:08 +00:00
Bug 1543675 - Part 1: Change Intl.NumberFormat to use UNumberFormatter API. r=jwalden
- Add NumberFormatterSkeleton as a helper class to construct a number formatter skeleton. When the "Intl.NumberFormat Unified API" proposal gets implemented a more feature-rich class may be necessary. - In addition to a UNumberFormatter instance, NumberFormatObject now also stores a UFormattedNumber instance. This avoids a performance regression compared to creating a new UFormattedNumber object each time a number gets formatted. (In micro-benchmarks 20% regressions were seen, with a cached UFormattedNumber the same micro-benchmarks even improved by ~33%.) - Add accessors for reserved slot values to NumberFormatObject to improve readibility. Differential Revision: https://phabricator.services.mozilla.com/D27083 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
4f98a59766
commit
8767ed1f43
@ -102,6 +102,7 @@ included_inclnames_to_ignore = set([
|
||||
'unicode/unistr.h', # ICU
|
||||
'unicode/unorm2.h', # ICU
|
||||
'unicode/unum.h', # ICU
|
||||
'unicode/unumberformatter.h', # ICU
|
||||
'unicode/unumsys.h', # ICU
|
||||
'unicode/upluralrules.h', # ICU
|
||||
'unicode/ureldatefmt.h', # ICU
|
||||
|
@ -1334,6 +1334,7 @@ if CONFIG['MOZ_SYSTEM_ICU']:
|
||||
'unicode/unistr.h',
|
||||
'unicode/unorm.h',
|
||||
'unicode/unum.h',
|
||||
'unicode/unumberformatter.h',
|
||||
'unicode/upluralrules.h',
|
||||
'unicode/ureldatefmt.h',
|
||||
'unicode/ustring.h',
|
||||
|
@ -31,6 +31,7 @@
|
||||
# include "unicode/uformattedvalue.h"
|
||||
# include "unicode/uloc.h"
|
||||
# include "unicode/unum.h"
|
||||
# include "unicode/unumberformatter.h"
|
||||
# include "unicode/unumsys.h"
|
||||
# include "unicode/upluralrules.h"
|
||||
# include "unicode/ureldatefmt.h"
|
||||
@ -163,10 +164,6 @@ using UNumberFormat = void*;
|
||||
|
||||
enum UNumberFormatStyle {
|
||||
UNUM_DECIMAL = 1,
|
||||
UNUM_CURRENCY,
|
||||
UNUM_PERCENT,
|
||||
UNUM_CURRENCY_ISO,
|
||||
UNUM_CURRENCY_PLURAL,
|
||||
};
|
||||
|
||||
enum UNumberFormatRoundingMode {
|
||||
@ -174,7 +171,6 @@ enum UNumberFormatRoundingMode {
|
||||
};
|
||||
|
||||
enum UNumberFormatAttribute {
|
||||
UNUM_GROUPING_USED,
|
||||
UNUM_MIN_INTEGER_DIGITS,
|
||||
UNUM_MAX_FRACTION_DIGITS,
|
||||
UNUM_MIN_FRACTION_DIGITS,
|
||||
@ -184,10 +180,6 @@ enum UNumberFormatAttribute {
|
||||
UNUM_MAX_SIGNIFICANT_DIGITS,
|
||||
};
|
||||
|
||||
enum UNumberFormatTextAttribute {
|
||||
UNUM_CURRENCY_CODE,
|
||||
};
|
||||
|
||||
inline int32_t unum_countAvailable() {
|
||||
MOZ_CRASH("unum_countAvailable: Intl API disabled");
|
||||
}
|
||||
@ -207,14 +199,6 @@ inline void unum_setAttribute(UNumberFormat* fmt, UNumberFormatAttribute attr,
|
||||
MOZ_CRASH("unum_setAttribute: Intl API disabled");
|
||||
}
|
||||
|
||||
inline int32_t unum_formatDoubleForFields(const UNumberFormat* fmt,
|
||||
double number, UChar* result,
|
||||
int32_t resultLength,
|
||||
UFieldPositionIterator* fpositer,
|
||||
UErrorCode* status) {
|
||||
MOZ_CRASH("unum_formatDoubleForFields: Intl API disabled");
|
||||
}
|
||||
|
||||
enum UNumberFormatFields {
|
||||
UNUM_INTEGER_FIELD,
|
||||
UNUM_GROUPING_SEPARATOR_FIELD,
|
||||
@ -236,13 +220,6 @@ inline void unum_close(UNumberFormat* fmt) {
|
||||
MOZ_CRASH("unum_close: Intl API disabled");
|
||||
}
|
||||
|
||||
inline void unum_setTextAttribute(UNumberFormat* fmt,
|
||||
UNumberFormatTextAttribute tag,
|
||||
const UChar* newValue, int32_t newValueLength,
|
||||
UErrorCode* status) {
|
||||
MOZ_CRASH("unum_setTextAttribute: Intl API disabled");
|
||||
}
|
||||
|
||||
using UNumberingSystem = void*;
|
||||
|
||||
inline UNumberingSystem* unumsys_open(const char* locale, UErrorCode* status) {
|
||||
@ -257,6 +234,45 @@ inline void unumsys_close(UNumberingSystem* unumsys) {
|
||||
MOZ_CRASH("unumsys_close: Intl API disabled");
|
||||
}
|
||||
|
||||
struct UNumberFormatter;
|
||||
struct UFormattedNumber;
|
||||
|
||||
inline UNumberFormatter* unumf_openForSkeletonAndLocale(const UChar* skeleton,
|
||||
int32_t skeletonLen,
|
||||
const char* locale,
|
||||
UErrorCode* status) {
|
||||
MOZ_CRASH("unumf_openForSkeletonAndLocale: Intl API disabled");
|
||||
}
|
||||
|
||||
inline void unumf_close(UNumberFormatter* f) {
|
||||
MOZ_CRASH("unumf_close: Intl API disabled");
|
||||
}
|
||||
|
||||
inline UFormattedNumber* unumf_openResult(UErrorCode* status) {
|
||||
MOZ_CRASH("unumf_openResult: Intl API disabled");
|
||||
}
|
||||
|
||||
inline void unumf_closeResult(UFormattedNumber* uresult) {
|
||||
MOZ_CRASH("unumf_closeResult: Intl API disabled");
|
||||
}
|
||||
|
||||
inline void unumf_formatDouble(const UNumberFormatter* uformatter, double value,
|
||||
UFormattedNumber* uresult, UErrorCode* status) {
|
||||
MOZ_CRASH("unumf_formatDouble: Intl API disabled");
|
||||
}
|
||||
|
||||
inline void unumf_resultGetAllFieldPositions(const UFormattedNumber* uresult,
|
||||
UFieldPositionIterator* ufpositer,
|
||||
UErrorCode* status) {
|
||||
MOZ_CRASH("unumf_resultGetAllFieldPositions: Intl API disabled");
|
||||
}
|
||||
|
||||
inline int32_t unumf_resultToString(const UFormattedNumber* uresult,
|
||||
UChar* buffer, int32_t bufferCapacity,
|
||||
UErrorCode* status) {
|
||||
MOZ_CRASH("unumf_resultToString: Intl API disabled");
|
||||
}
|
||||
|
||||
using UCalendar = void*;
|
||||
|
||||
enum UCalendarType {
|
||||
|
@ -108,10 +108,9 @@ static bool NumberFormat(JSContext* cx, const CallArgs& args, bool construct) {
|
||||
return false;
|
||||
}
|
||||
|
||||
numberFormat->setReservedSlot(NumberFormatObject::INTERNALS_SLOT,
|
||||
NullValue());
|
||||
numberFormat->setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT,
|
||||
PrivateValue(nullptr));
|
||||
numberFormat->setFixedSlot(NumberFormatObject::INTERNALS_SLOT, NullValue());
|
||||
numberFormat->setNumberFormatter(nullptr);
|
||||
numberFormat->setFormattedNumber(nullptr);
|
||||
|
||||
RootedValue thisValue(cx,
|
||||
construct ? ObjectValue(*numberFormat) : args.thisv());
|
||||
@ -142,10 +141,15 @@ bool js::intl_NumberFormat(JSContext* cx, unsigned argc, Value* vp) {
|
||||
void js::NumberFormatObject::finalize(FreeOp* fop, JSObject* obj) {
|
||||
MOZ_ASSERT(fop->onMainThread());
|
||||
|
||||
const Value& slot = obj->as<NumberFormatObject>().getReservedSlot(
|
||||
NumberFormatObject::UNUMBER_FORMAT_SLOT);
|
||||
if (UNumberFormat* nf = static_cast<UNumberFormat*>(slot.toPrivate())) {
|
||||
unum_close(nf);
|
||||
auto* numberFormat = &obj->as<NumberFormatObject>();
|
||||
UNumberFormatter* nf = numberFormat->getNumberFormatter();
|
||||
UFormattedNumber* formatted = numberFormat->getFormattedNumber();
|
||||
|
||||
if (nf) {
|
||||
unumf_close(nf);
|
||||
}
|
||||
if (formatted) {
|
||||
unumf_closeResult(formatted);
|
||||
}
|
||||
}
|
||||
|
||||
@ -242,11 +246,81 @@ bool js::intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool js::intl::NumberFormatterSkeleton::currency(CurrencyDisplay display,
|
||||
JSLinearString* currency) {
|
||||
MOZ_ASSERT(currency->length() == 3,
|
||||
"IsWellFormedCurrencyCode permits only length-3 strings");
|
||||
|
||||
char16_t currencyChars[] = {currency->latin1OrTwoByteChar(0),
|
||||
currency->latin1OrTwoByteChar(1),
|
||||
currency->latin1OrTwoByteChar(2), '\0'};
|
||||
|
||||
if (!(append(u"currency/") && append(currencyChars) && append(' '))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (display) {
|
||||
case CurrencyDisplay::Code:
|
||||
return appendToken(u"unit-width-iso-code");
|
||||
case CurrencyDisplay::Name:
|
||||
return appendToken(u"unit-width-full-name");
|
||||
case CurrencyDisplay::Symbol:
|
||||
// Default, no additional tokens needed.
|
||||
return true;
|
||||
}
|
||||
|
||||
MOZ_CRASH("unexpected currency display type");
|
||||
}
|
||||
|
||||
bool js::intl::NumberFormatterSkeleton::percent() {
|
||||
return appendToken(u"percent scale/100");
|
||||
}
|
||||
|
||||
bool js::intl::NumberFormatterSkeleton::fractionDigits(uint32_t min,
|
||||
uint32_t max) {
|
||||
// Note: |min| can be zero here.
|
||||
MOZ_ASSERT(min <= max);
|
||||
return append('.') && appendN('0', min) && appendN('#', max - min) &&
|
||||
append(' ');
|
||||
}
|
||||
|
||||
bool js::intl::NumberFormatterSkeleton::integerWidth(uint32_t min) {
|
||||
MOZ_ASSERT(min > 0);
|
||||
return append(u"integer-width/+") && appendN('0', min) && append(' ');
|
||||
}
|
||||
|
||||
bool js::intl::NumberFormatterSkeleton::significantDigits(uint32_t min,
|
||||
uint32_t max) {
|
||||
MOZ_ASSERT(min > 0);
|
||||
MOZ_ASSERT(min <= max);
|
||||
return appendN('@', min) && appendN('#', max - min) && append(' ');
|
||||
}
|
||||
|
||||
bool js::intl::NumberFormatterSkeleton::useGrouping(bool on) {
|
||||
return on || appendToken(u"group-off");
|
||||
}
|
||||
|
||||
bool js::intl::NumberFormatterSkeleton::roundingModeHalfUp() {
|
||||
return appendToken(u"rounding-mode-half-up");
|
||||
}
|
||||
|
||||
UNumberFormatter* js::intl::NumberFormatterSkeleton::toFormatter(
|
||||
JSContext* cx, const char* locale) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
UNumberFormatter* nf = unumf_openForSkeletonAndLocale(
|
||||
vector_.begin(), vector_.length(), locale, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return nullptr;
|
||||
}
|
||||
return nf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new UNumberFormat with the locale and number formatting options
|
||||
* Returns a new UNumberFormatter with the locale and number formatting options
|
||||
* of the given NumberFormat.
|
||||
*/
|
||||
static UNumberFormat* NewUNumberFormat(
|
||||
static UNumberFormatter* NewUNumberFormatter(
|
||||
JSContext* cx, Handle<NumberFormatObject*> numberFormat) {
|
||||
RootedValue value(cx);
|
||||
|
||||
@ -263,19 +337,7 @@ static UNumberFormat* NewUNumberFormat(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// UNumberFormat options with default values
|
||||
UNumberFormatStyle uStyle = UNUM_DECIMAL;
|
||||
const UChar* uCurrency = nullptr;
|
||||
uint32_t uMinimumIntegerDigits = 1;
|
||||
uint32_t uMinimumFractionDigits = 0;
|
||||
uint32_t uMaximumFractionDigits = 3;
|
||||
int32_t uMinimumSignificantDigits = -1;
|
||||
int32_t uMaximumSignificantDigits = -1;
|
||||
bool uUseGrouping = true;
|
||||
|
||||
// Sprinkle appropriate rooting flavor over things the GC might care about.
|
||||
RootedString currency(cx);
|
||||
AutoStableStringChars stableChars(cx);
|
||||
intl::NumberFormatterSkeleton skeleton(cx);
|
||||
|
||||
// We don't need to look at numberingSystem - it can only be set via
|
||||
// the Unicode locale extension and is therefore already set on locale.
|
||||
@ -291,18 +353,7 @@ static UNumberFormat* NewUNumberFormat(
|
||||
}
|
||||
|
||||
if (StringEqualsAscii(style, "currency")) {
|
||||
if (!GetProperty(cx, internals, internals, cx->names().currency,
|
||||
&value)) {
|
||||
return nullptr;
|
||||
}
|
||||
currency = value.toString();
|
||||
MOZ_ASSERT(currency->length() == 3,
|
||||
"IsWellFormedCurrencyCode permits only length-3 strings");
|
||||
if (!stableChars.initTwoByte(cx, currency)) {
|
||||
return nullptr;
|
||||
}
|
||||
// uCurrency remains owned by stableChars.
|
||||
uCurrency = stableChars.twoByteRange().begin().get();
|
||||
using CurrencyDisplay = intl::NumberFormatterSkeleton::CurrencyDisplay;
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().currencyDisplay,
|
||||
&value)) {
|
||||
@ -312,101 +363,116 @@ static UNumberFormat* NewUNumberFormat(
|
||||
if (!currencyDisplay) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CurrencyDisplay display;
|
||||
if (StringEqualsAscii(currencyDisplay, "code")) {
|
||||
uStyle = UNUM_CURRENCY_ISO;
|
||||
display = CurrencyDisplay::Code;
|
||||
} else if (StringEqualsAscii(currencyDisplay, "symbol")) {
|
||||
uStyle = UNUM_CURRENCY;
|
||||
display = CurrencyDisplay::Symbol;
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsAscii(currencyDisplay, "name"));
|
||||
uStyle = UNUM_CURRENCY_PLURAL;
|
||||
display = CurrencyDisplay::Name;
|
||||
}
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().currency,
|
||||
&value)) {
|
||||
return nullptr;
|
||||
}
|
||||
JSLinearString* currency = value.toString()->ensureLinear(cx);
|
||||
if (!currency) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!skeleton.currency(display, currency)) {
|
||||
return nullptr;
|
||||
}
|
||||
} else if (StringEqualsAscii(style, "percent")) {
|
||||
uStyle = UNUM_PERCENT;
|
||||
if (!skeleton.percent()) {
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
MOZ_ASSERT(StringEqualsAscii(style, "decimal"));
|
||||
uStyle = UNUM_DECIMAL;
|
||||
}
|
||||
}
|
||||
|
||||
bool hasP;
|
||||
bool hasMinimumSignificantDigits;
|
||||
if (!HasProperty(cx, internals, cx->names().minimumSignificantDigits,
|
||||
&hasP)) {
|
||||
&hasMinimumSignificantDigits)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (hasP) {
|
||||
if (hasMinimumSignificantDigits) {
|
||||
if (!GetProperty(cx, internals, internals,
|
||||
cx->names().minimumSignificantDigits, &value)) {
|
||||
return nullptr;
|
||||
}
|
||||
uMinimumSignificantDigits = value.toInt32();
|
||||
uint32_t minimumSignificantDigits = AssertedCast<uint32_t>(value.toInt32());
|
||||
|
||||
if (!GetProperty(cx, internals, internals,
|
||||
cx->names().maximumSignificantDigits, &value)) {
|
||||
return nullptr;
|
||||
}
|
||||
uMaximumSignificantDigits = value.toInt32();
|
||||
uint32_t maximumSignificantDigits = AssertedCast<uint32_t>(value.toInt32());
|
||||
|
||||
if (!skeleton.significantDigits(minimumSignificantDigits,
|
||||
maximumSignificantDigits)) {
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
|
||||
&value)) {
|
||||
return nullptr;
|
||||
}
|
||||
uMinimumIntegerDigits = AssertedCast<uint32_t>(value.toInt32());
|
||||
uint32_t minimumIntegerDigits = AssertedCast<uint32_t>(value.toInt32());
|
||||
|
||||
if (!GetProperty(cx, internals, internals,
|
||||
cx->names().minimumFractionDigits, &value)) {
|
||||
return nullptr;
|
||||
}
|
||||
uMinimumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
|
||||
uint32_t minimumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
|
||||
|
||||
if (!GetProperty(cx, internals, internals,
|
||||
cx->names().maximumFractionDigits, &value)) {
|
||||
return nullptr;
|
||||
}
|
||||
uMaximumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
|
||||
uint32_t maximumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
|
||||
|
||||
if (!skeleton.fractionDigits(minimumFractionDigits,
|
||||
maximumFractionDigits)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!skeleton.integerWidth(minimumIntegerDigits)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().useGrouping, &value)) {
|
||||
return nullptr;
|
||||
}
|
||||
uUseGrouping = value.toBoolean();
|
||||
if (!skeleton.useGrouping(value.toBoolean())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!skeleton.roundingModeHalfUp()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return skeleton.toFormatter(cx, locale.get());
|
||||
}
|
||||
|
||||
static UFormattedNumber* NewUFormattedNumber(JSContext* cx) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
UNumberFormat* nf =
|
||||
unum_open(uStyle, nullptr, 0, IcuLocale(locale.get()), nullptr, &status);
|
||||
UFormattedNumber* formatted = unumf_openResult(&status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return nullptr;
|
||||
}
|
||||
ScopedICUObject<UNumberFormat, unum_close> toClose(nf);
|
||||
|
||||
if (uCurrency) {
|
||||
unum_setTextAttribute(nf, UNUM_CURRENCY_CODE, uCurrency, 3, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
if (uMinimumSignificantDigits != -1) {
|
||||
unum_setAttribute(nf, UNUM_SIGNIFICANT_DIGITS_USED, true);
|
||||
unum_setAttribute(nf, UNUM_MIN_SIGNIFICANT_DIGITS,
|
||||
uMinimumSignificantDigits);
|
||||
unum_setAttribute(nf, UNUM_MAX_SIGNIFICANT_DIGITS,
|
||||
uMaximumSignificantDigits);
|
||||
} else {
|
||||
unum_setAttribute(nf, UNUM_MIN_INTEGER_DIGITS, uMinimumIntegerDigits);
|
||||
unum_setAttribute(nf, UNUM_MIN_FRACTION_DIGITS, uMinimumFractionDigits);
|
||||
unum_setAttribute(nf, UNUM_MAX_FRACTION_DIGITS, uMaximumFractionDigits);
|
||||
}
|
||||
unum_setAttribute(nf, UNUM_GROUPING_USED, uUseGrouping);
|
||||
unum_setAttribute(nf, UNUM_ROUNDING_MODE, UNUM_ROUND_HALFUP);
|
||||
|
||||
return toClose.forget();
|
||||
return formatted;
|
||||
}
|
||||
|
||||
static JSString* PartitionNumberPattern(JSContext* cx, UNumberFormat* nf,
|
||||
double* x,
|
||||
UFieldPositionIterator* fpositer) {
|
||||
static JSString* PartitionNumberPattern(JSContext* cx, UNumberFormatter* nf,
|
||||
UFormattedNumber* formatted,
|
||||
double* x) {
|
||||
// ICU incorrectly formats NaN values with the sign bit set, as if they
|
||||
// were negative. Replace all NaNs with a single pattern with sign bit
|
||||
// unset ("positive", that is) until ICU is fixed.
|
||||
@ -414,17 +480,23 @@ static JSString* PartitionNumberPattern(JSContext* cx, UNumberFormat* nf,
|
||||
*x = SpecificNaN<double>(0, 1);
|
||||
}
|
||||
|
||||
return CallICU(cx, [nf, d = *x, fpositer](UChar* chars, int32_t size,
|
||||
UErrorCode* status) {
|
||||
return unum_formatDoubleForFields(nf, d, chars, size, fpositer, status);
|
||||
});
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
unumf_formatDouble(nf, *x, formatted, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return CallICU(cx,
|
||||
[formatted](UChar* chars, int32_t size, UErrorCode* status) {
|
||||
return unumf_resultToString(formatted, chars, size, status);
|
||||
});
|
||||
}
|
||||
|
||||
static bool intl_FormatNumber(JSContext* cx, UNumberFormat* nf, double x,
|
||||
static bool intl_FormatNumber(JSContext* cx, UNumberFormatter* nf,
|
||||
UFormattedNumber* formatted, double x,
|
||||
MutableHandleValue result) {
|
||||
// Passing null for |fpositer| will just not compute partition information,
|
||||
// letting us common up all ICU number-formatting code.
|
||||
JSString* str = PartitionNumberPattern(cx, nf, &x, nullptr);
|
||||
JSString* str = PartitionNumberPattern(cx, nf, formatted, &x);
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
@ -832,10 +904,15 @@ ArrayObject* js::intl::NumberFormatFields::toArray(JSContext* cx,
|
||||
return partsArray;
|
||||
}
|
||||
|
||||
static bool intl_FormatNumberToParts(JSContext* cx, UNumberFormat* nf, double x,
|
||||
static bool intl_FormatNumberToParts(JSContext* cx, UNumberFormatter* nf,
|
||||
UFormattedNumber* formatted, double x,
|
||||
MutableHandleValue result) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
RootedString overallResult(cx, PartitionNumberPattern(cx, nf, formatted, &x));
|
||||
if (!overallResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
UFieldPositionIterator* fpositer = ufieldpositer_open(&status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
@ -846,8 +923,9 @@ static bool intl_FormatNumberToParts(JSContext* cx, UNumberFormat* nf, double x,
|
||||
ScopedICUObject<UFieldPositionIterator, ufieldpositer_close> toClose(
|
||||
fpositer);
|
||||
|
||||
RootedString overallResult(cx, PartitionNumberPattern(cx, nf, &x, fpositer));
|
||||
if (!overallResult) {
|
||||
unumf_resultGetAllFieldPositions(formatted, fpositer, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -881,24 +959,31 @@ bool js::intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp) {
|
||||
Rooted<NumberFormatObject*> numberFormat(
|
||||
cx, &args[0].toObject().as<NumberFormatObject>());
|
||||
|
||||
// Obtain a cached UNumberFormat object.
|
||||
void* priv =
|
||||
numberFormat->getReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT)
|
||||
.toPrivate();
|
||||
UNumberFormat* nf = static_cast<UNumberFormat*>(priv);
|
||||
// Obtain a cached UNumberFormatter object.
|
||||
UNumberFormatter* nf = numberFormat->getNumberFormatter();
|
||||
if (!nf) {
|
||||
nf = NewUNumberFormat(cx, numberFormat);
|
||||
nf = NewUNumberFormatter(cx, numberFormat);
|
||||
if (!nf) {
|
||||
return false;
|
||||
}
|
||||
numberFormat->setReservedSlot(NumberFormatObject::UNUMBER_FORMAT_SLOT,
|
||||
PrivateValue(nf));
|
||||
numberFormat->setNumberFormatter(nf);
|
||||
}
|
||||
|
||||
// Use the UNumberFormat to actually format the number.
|
||||
// Obtain a cached UFormattedNumber object.
|
||||
UFormattedNumber* formatted = numberFormat->getFormattedNumber();
|
||||
if (!formatted) {
|
||||
formatted = NewUFormattedNumber(cx);
|
||||
if (!formatted) {
|
||||
return false;
|
||||
}
|
||||
numberFormat->setFormattedNumber(formatted);
|
||||
}
|
||||
|
||||
// Use the UNumberFormatter to actually format the number.
|
||||
if (args[2].toBoolean()) {
|
||||
return intl_FormatNumberToParts(cx, nf, args[1].toNumber(), args.rval());
|
||||
return intl_FormatNumberToParts(cx, nf, formatted, args[1].toNumber(),
|
||||
args.rval());
|
||||
}
|
||||
|
||||
return intl_FormatNumber(cx, nf, args[1].toNumber(), args.rval());
|
||||
return intl_FormatNumber(cx, nf, formatted, args[1].toNumber(), args.rval());
|
||||
}
|
||||
|
@ -18,6 +18,9 @@
|
||||
#include "vm/NativeObject.h"
|
||||
#include "vm/Runtime.h"
|
||||
|
||||
struct UFormattedNumber;
|
||||
struct UNumberFormatter;
|
||||
|
||||
namespace js {
|
||||
|
||||
class ArrayObject;
|
||||
@ -28,13 +31,38 @@ class NumberFormatObject : public NativeObject {
|
||||
static const Class class_;
|
||||
|
||||
static constexpr uint32_t INTERNALS_SLOT = 0;
|
||||
static constexpr uint32_t UNUMBER_FORMAT_SLOT = 1;
|
||||
static constexpr uint32_t SLOT_COUNT = 2;
|
||||
static constexpr uint32_t UNUMBER_FORMATTER_SLOT = 1;
|
||||
static constexpr uint32_t UFORMATTED_NUMBER_SLOT = 2;
|
||||
static constexpr uint32_t SLOT_COUNT = 3;
|
||||
|
||||
static_assert(INTERNALS_SLOT == INTL_INTERNALS_OBJECT_SLOT,
|
||||
"INTERNALS_SLOT must match self-hosting define for internals "
|
||||
"object slot");
|
||||
|
||||
UNumberFormatter* getNumberFormatter() const {
|
||||
const auto& slot = getFixedSlot(UNUMBER_FORMATTER_SLOT);
|
||||
if (slot.isUndefined()) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<UNumberFormatter*>(slot.toPrivate());
|
||||
}
|
||||
|
||||
void setNumberFormatter(UNumberFormatter* formatter) {
|
||||
setFixedSlot(UNUMBER_FORMATTER_SLOT, PrivateValue(formatter));
|
||||
}
|
||||
|
||||
UFormattedNumber* getFormattedNumber() const {
|
||||
const auto& slot = getFixedSlot(UFORMATTED_NUMBER_SLOT);
|
||||
if (slot.isUndefined()) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<UFormattedNumber*>(slot.toPrivate());
|
||||
}
|
||||
|
||||
void setFormattedNumber(UFormattedNumber* formatted) {
|
||||
setFixedSlot(UFORMATTED_NUMBER_SLOT, PrivateValue(formatted));
|
||||
}
|
||||
|
||||
private:
|
||||
static const ClassOps classOps_;
|
||||
|
||||
@ -90,6 +118,103 @@ extern MOZ_MUST_USE bool intl_FormatNumber(JSContext* cx, unsigned argc,
|
||||
|
||||
namespace intl {
|
||||
|
||||
/**
|
||||
* Class to create a number formatter skeleton.
|
||||
*
|
||||
* The skeleton syntax is documented at:
|
||||
* https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md
|
||||
*/
|
||||
class MOZ_STACK_CLASS NumberFormatterSkeleton final {
|
||||
static constexpr size_t DefaultVectorSize = 128;
|
||||
using SkeletonVector = Vector<char16_t, DefaultVectorSize>;
|
||||
|
||||
SkeletonVector vector_;
|
||||
|
||||
bool append(char16_t c) { return vector_.append(c); }
|
||||
|
||||
bool appendN(char16_t c, size_t times) { return vector_.appendN(c, times); }
|
||||
|
||||
template <size_t N>
|
||||
bool append(const char16_t (&chars)[N]) {
|
||||
static_assert(N > 0,
|
||||
"should only be used with string literals or properly "
|
||||
"null-terminated arrays");
|
||||
MOZ_ASSERT(chars[N - 1] == '\0',
|
||||
"should only be used with string literals or properly "
|
||||
"null-terminated arrays");
|
||||
return vector_.append(chars, N - 1); // Without trailing \0.
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
bool appendToken(const char16_t (&token)[N]) {
|
||||
return append(token) && append(' ');
|
||||
}
|
||||
|
||||
public:
|
||||
explicit NumberFormatterSkeleton(JSContext* cx) : vector_(cx) {}
|
||||
|
||||
/**
|
||||
* Return a new UNumberFormatter based on this skeleton.
|
||||
*/
|
||||
UNumberFormatter* toFormatter(JSContext* cx, const char* locale);
|
||||
|
||||
enum class CurrencyDisplay { Code, Name, Symbol };
|
||||
|
||||
/**
|
||||
* Set this skeleton to display a currency amount. |currency| must be a
|
||||
* three-letter currency code.
|
||||
*
|
||||
* https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#unit
|
||||
*/
|
||||
MOZ_MUST_USE bool currency(CurrencyDisplay display, JSLinearString* currency);
|
||||
|
||||
/**
|
||||
* Set this skeleton to display a percent number.
|
||||
*
|
||||
* https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#unit
|
||||
* https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#scale
|
||||
*/
|
||||
MOZ_MUST_USE bool percent();
|
||||
|
||||
/**
|
||||
* Set the fraction digits settings for this skeleton. |min| can be zero,
|
||||
* |max| must be larger-or-equal to |min|.
|
||||
*
|
||||
* https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#fraction-precision
|
||||
*/
|
||||
MOZ_MUST_USE bool fractionDigits(uint32_t min, uint32_t max);
|
||||
|
||||
/**
|
||||
* Set the integer-width settings for this skeleton. |min| must be a non-zero
|
||||
* number.
|
||||
*
|
||||
* https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#integer-width
|
||||
*/
|
||||
MOZ_MUST_USE bool integerWidth(uint32_t min);
|
||||
|
||||
/**
|
||||
* Set the significant digits settings for this skeleton. |min| must be a
|
||||
* non-zero number, |max| must be larger-or-equal to |min|.
|
||||
*
|
||||
* https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#significant-digits-precision
|
||||
*/
|
||||
MOZ_MUST_USE bool significantDigits(uint32_t min, uint32_t max);
|
||||
|
||||
/**
|
||||
* Enable or disable grouping for this skeleton.
|
||||
*
|
||||
* https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#grouping
|
||||
*/
|
||||
MOZ_MUST_USE bool useGrouping(bool on);
|
||||
|
||||
/**
|
||||
* Set the rounding mode to 'half-up' for this skeleton.
|
||||
*
|
||||
* https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#rounding-mode
|
||||
*/
|
||||
MOZ_MUST_USE bool roundingModeHalfUp();
|
||||
};
|
||||
|
||||
using FieldType = js::ImmutablePropertyNamePtr JSAtomState::*;
|
||||
|
||||
struct Field {
|
||||
|
Loading…
Reference in New Issue
Block a user