Bug 769871 - Reimplement Number.toLocaleString per ECMA-402. r=jwalden

--HG--
extra : rebase_source : 5a1e5a57e89056d128956cd7d7f2c7ce6b3dec54
This commit is contained in:
Norbert Lindenberg 2013-03-21 14:59:48 -07:00
parent dfa2e312ca
commit 31ef79f418
11 changed files with 132 additions and 28 deletions

View File

@ -963,6 +963,7 @@ selfhosting_srcs := \
$(srcdir)/builtin/Array.js \
$(srcdir)/builtin/Intl.js \
$(srcdir)/builtin/IntlData.js \
$(srcdir)/builtin/Number.js \
$(srcdir)/builtin/ParallelArray.js \
$(srcdir)/builtin/String.js \
$(NULL)

View File

@ -1101,14 +1101,11 @@ static JSFunctionSpec numberFormat_methods[] = {
* NumberFormat constructor.
* Spec: ECMAScript Internationalization API Specification, 11.1
*/
static JSBool
NumberFormat(JSContext *cx, unsigned argc, Value *vp)
static bool
NumberFormat(JSContext *cx, CallArgs args, bool construct)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject obj(cx);
bool construct = IsConstructing(args);
if (!construct) {
// 11.1.2.1 step 3
JSObject *intl = cx->global()->getOrCreateIntlObject(cx);
@ -1154,10 +1151,26 @@ NumberFormat(JSContext *cx, unsigned argc, Value *vp)
return true;
}
static JSBool
NumberFormat(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return NumberFormat(cx, args, IsConstructing(args));
}
JSBool
js::intl_NumberFormat(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JS_ASSERT(args.length() == 2);
return NumberFormat(cx, args, true);
}
static void
numberFormat_finalize(FreeOp *fop, JSObject *obj)
{
UNumberFormat *nf = static_cast<UNumberFormat *>(obj->getReservedSlot(UNUMBER_FORMAT_SLOT).toPrivate());
UNumberFormat *nf =
static_cast<UNumberFormat*>(obj->getReservedSlot(UNUMBER_FORMAT_SLOT).toPrivate());
if (nf)
unum_close(nf);
}

View File

@ -82,6 +82,16 @@ intl_CompareStrings(JSContext *cx, unsigned argc, Value *vp);
/******************** NumberFormat ********************/
/**
* Returns a new instance of the standard built-in NumberFormat constructor.
* Self-hosted code cannot cache this constructor (as it does for others in
* Utilities.js) because it is initialized after self-hosted code is compiled.
*
* Usage: numberFormat = intl_NumberFormat(locales, options)
*/
extern JSBool
intl_NumberFormat(JSContext *cx, unsigned argc, Value *vp);
/**
* Returns an object indicating the supported locales for number formatting
* by having a true-valued property for each such locale with the

41
js/src/builtin/Number.js Normal file
View File

@ -0,0 +1,41 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*global num_CheckThisNumber: false, intl_NumberFormat: false, */
var numberFormatCache = new Record();
/**
* Format this Number object into a string, using the locale and formatting options
* provided.
*
* Spec: ECMAScript Language Specification, 5.1 edition, 15.7.4.3.
* Spec: ECMAScript Internationalization API Specification, 13.2.1.
*/
function Number_toLocaleString() {
// Step 1.
callFunction(num_CheckThisNumber, this);
var x = callFunction(std_Number_valueOf, this);
// Steps 2-3.
var locales = arguments.length > 0 ? arguments[0] : undefined;
var options = arguments.length > 1 ? arguments[1] : undefined;
// Step 4.
var numberFormat;
if (locales === undefined && options === undefined) {
// This cache only optimizes for the old ES5 toLocaleString without
// locales and options.
if (numberFormatCache.numberFormat === undefined)
numberFormatCache.numberFormat = intl_NumberFormat(locales, options);
numberFormat = numberFormatCache.numberFormat;
} else {
numberFormat = intl_NumberFormat(locales, options);
}
// Step 5.
return intl_FormatNumber(numberFormat, x);
}

View File

@ -44,6 +44,7 @@ var std_Function_apply = Function.prototype.apply;
var std_Math_floor = Math.floor;
var std_Math_max = Math.max;
var std_Math_min = Math.min;
var std_Number_valueOf = Number.prototype.valueOf;
var std_Object_create = Object.create;
var std_Object_defineProperty = Object.defineProperty;
var std_Object_getOwnPropertyNames = Object.getOwnPropertyNames;

View File

@ -861,9 +861,11 @@ JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads)
structuredCloneCallbacks(NULL),
telemetryCallback(NULL),
propertyRemovals(0),
#if !ENABLE_INTL_API
thousandsSeparator(0),
decimalSeparator(0),
numGrouping(0),
#endif
mathCache_(NULL),
dtoaState(NULL),
trustedPrincipals_(NULL),
@ -1011,7 +1013,9 @@ JSRuntime::~JSRuntime()
}
#endif
#if !ENABLE_INTL_API
FinishRuntimeNumberState(this);
#endif
FinishAtoms(this);
if (dtoaState)

View File

@ -1181,10 +1181,12 @@ struct JSRuntime : js::RuntimeFriendFields,
*/
uint32_t propertyRemovals;
/* Number localization, used by jsnum.c */
#if !ENABLE_INTL_API
/* Number localization, used by jsnum.cpp. */
const char *thousandsSeparator;
const char *decimalSeparator;
const char *numGrouping;
#endif
private:
js::MathCache *mathCache_;

View File

@ -445,6 +445,26 @@ IsNumber(const Value &v)
return v.isNumber() || (v.isObject() && v.toObject().hasClass(&NumberClass));
}
JS_ALWAYS_INLINE bool
num_nop(JSContext *cx, CallArgs args)
{
JS_ASSERT(IsNumber(args.thisv()));
args.rval().setUndefined();
return true;
}
JSBool
js::num_CheckThisNumber(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// CallNonGenericMethod will handle proxies correctly and throw exceptions
// in the right circumstances, but will report date_CheckThisNumber as the
// function name in the message. We need a better solution:
// https://bugzilla.mozilla.org/show_bug.cgi?id=844677
return CallNonGenericMethod<IsNumber, num_nop>(cx, args);
}
inline double
Extract(const Value &v)
{
@ -612,6 +632,7 @@ num_toString(JSContext *cx, unsigned argc, Value *vp)
return CallNonGenericMethod<IsNumber, num_toString_impl>(cx, args);
}
#if !ENABLE_INTL_API
JS_ALWAYS_INLINE bool
num_toLocaleString_impl(JSContext *cx, CallArgs args)
{
@ -744,6 +765,7 @@ num_toLocaleString(JSContext *cx, unsigned argc, Value *vp)
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsNumber, num_toLocaleString_impl>(cx, args);
}
#endif
JS_ALWAYS_INLINE bool
num_valueOf_impl(JSContext *cx, CallArgs args)
@ -892,11 +914,19 @@ static JSFunctionSpec number_methods[] = {
JS_FN(js_toSource_str, num_toSource, 0, 0),
#endif
JS_FN(js_toString_str, num_toString, 1, 0),
JS_FN(js_toLocaleString_str, num_toLocaleString, 0, 0),
JS_FN(js_valueOf_str, js_num_valueOf, 0, 0),
JS_FN("toFixed", num_toFixed, 1, 0),
JS_FN("toExponential", num_toExponential, 1, 0),
JS_FN("toPrecision", num_toPrecision, 1, 0),
// This must be at the end because of bug 853075: functions listed after
// self-hosted methods aren't available in self-hosted code.
#if ENABLE_INTL_API
{js_toLocaleString_str, {NULL, NULL}, 0,0, "Number_toLocaleString"},
#else
JS_FN(js_toLocaleString_str, num_toLocaleString, 0,0),
#endif
JS_FS_END
};
@ -1044,6 +1074,10 @@ js::InitRuntimeNumberState(JSRuntime *rt)
number_constants[NC_MIN_VALUE].dval = MOZ_DOUBLE_MIN_VALUE();
// XXX If ENABLE_INTL_API becomes true all the time at some point,
// js::InitRuntimeNumberState is no longer fallible, and we should
// change its return type.
#if !ENABLE_INTL_API
/* Copy locale-specific separators into the runtime strings. */
const char *thousandsSeparator, *decimalPoint, *grouping;
#ifdef HAVE_LOCALECONV
@ -1087,9 +1121,11 @@ js::InitRuntimeNumberState(JSRuntime *rt)
js_memcpy(storage, grouping, groupingSize);
rt->numGrouping = grouping;
#endif
return true;
}
#if !ENABLE_INTL_API
void
js::FinishRuntimeNumberState(JSRuntime *rt)
{
@ -1100,6 +1136,7 @@ js::FinishRuntimeNumberState(JSRuntime *rt)
char *storage = const_cast<char *>(rt->thousandsSeparator);
js_free(storage);
}
#endif
JSObject *
js_InitNumberClass(JSContext *cx, HandleObject obj)

View File

@ -23,8 +23,10 @@ namespace js {
extern bool
InitRuntimeNumberState(JSRuntime *rt);
#if !ENABLE_INTL_API
extern void
FinishRuntimeNumberState(JSRuntime *rt);
#endif
} /* namespace js */
@ -147,6 +149,15 @@ ToNumber(JSContext *cx, Value *vp)
JSBool
num_parseInt(JSContext *cx, unsigned argc, Value *vp);
/**
* Checks that the this value provided meets the requirements for "this Number
* object" in ES5.1, 15.7.4, and throws a TypeError if not.
*
* Usage: callFunction(num_CheckThisNumber, this);
*/
extern JSBool
num_CheckThisNumber(JSContext *cx, unsigned argc, js::Value *vp);
} /* namespace js */
/*

View File

@ -1,20 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//-----------------------------------------------------------------------------
var BUGNUMBER = "446494";
var summary = "num.toLocaleString should handle exponents";
var actual, expect;
printBugNumber(BUGNUMBER);
printStatus(summary);
expect = '1e-10';
actual = 1e-10.toLocaleString();
reportCompare(expect, actual, summary + ': ' + expect);
expect = 'Infinity';
actual = Infinity.toLocaleString();
reportCompare(expect, actual, summary + ': ' + expect);

View File

@ -475,6 +475,7 @@ JSFunctionSpec intrinsic_functions[] = {
JS_FN("intl_Collator_availableLocales", intl_Collator_availableLocales, 0,0),
JS_FN("intl_availableCollations", intl_availableCollations, 1,0),
JS_FN("intl_CompareStrings", intl_CompareStrings, 3,0),
JS_FN("intl_NumberFormat", intl_NumberFormat, 2,0),
JS_FN("intl_NumberFormat_availableLocales", intl_NumberFormat_availableLocales, 0,0),
JS_FN("intl_numberingSystem", intl_numberingSystem, 1,0),
JS_FN("intl_FormatNumber", intl_FormatNumber, 2,0),
@ -483,6 +484,9 @@ JSFunctionSpec intrinsic_functions[] = {
JS_FN("intl_patternForSkeleton", intl_patternForSkeleton, 2,0),
JS_FN("intl_FormatDateTime", intl_FormatDateTime, 2,0),
// See jsnum.h for descriptions of the num_* functions.
JS_FN("num_CheckThisNumber", num_CheckThisNumber, 2,0),
#ifdef DEBUG
JS_FN("Dump", intrinsic_Dump, 1,0),
#endif