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:
André Bargull 2019-07-10 14:12:03 +00:00
parent 4f98a59766
commit 8767ed1f43
5 changed files with 355 additions and 127 deletions

View File

@ -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

View File

@ -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',

View File

@ -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 {

View File

@ -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());
}

View File

@ -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 {