зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1130636 - Reimplement Array.prototype.toLocaleString as per ECMA-402, 2nd edition. r=Waldo
This commit is contained in:
Родитель
b2bb341033
Коммит
7a0641b5c2
|
@ -877,6 +877,61 @@ function ArrayToString() {
|
|||
return callContentFunction(func, array);
|
||||
}
|
||||
|
||||
// ES2017 draft rev f8a9be8ea4bd97237d176907a1e3080dce20c68f
|
||||
// 22.1.3.27 Array.prototype.toLocaleString ([ reserved1 [ , reserved2 ] ])
|
||||
// ES2017 Intl draft rev 78bbe7d1095f5ff3760ac4017ed366026e4cb276
|
||||
// 13.4.1 Array.prototype.toLocaleString ([ locales [ , options ]])
|
||||
function ArrayToLocaleString(locales, options) {
|
||||
// Step 1 (ToObject already performed in native code).
|
||||
assert(IsObject(this), "|this| should be an object");
|
||||
var array = this;
|
||||
|
||||
// Step 2.
|
||||
var len = ToLength(array.length);
|
||||
|
||||
// Step 4.
|
||||
if (len === 0)
|
||||
return "";
|
||||
|
||||
// Step 5.
|
||||
var firstElement = array[0];
|
||||
|
||||
// Steps 6-7.
|
||||
var R;
|
||||
if (firstElement === undefined || firstElement === null) {
|
||||
R = "";
|
||||
} else {
|
||||
#if EXPOSE_INTL_API
|
||||
R = ToString(callContentFunction(firstElement.toLocaleString, firstElement, locales, options));
|
||||
#else
|
||||
R = ToString(callContentFunction(firstElement.toLocaleString, firstElement));
|
||||
#endif
|
||||
}
|
||||
|
||||
// Step 3 (reordered).
|
||||
// We don't (yet?) implement locale-dependent separators.
|
||||
var separator = ",";
|
||||
|
||||
// Steps 8-9.
|
||||
for (var k = 1; k < len; k++) {
|
||||
// Step 9.b.
|
||||
var nextElement = array[k];
|
||||
|
||||
// Steps 9.a, 9.c-e.
|
||||
R += separator;
|
||||
if (!(nextElement === undefined || nextElement === null)) {
|
||||
#if EXPOSE_INTL_API
|
||||
R += ToString(callContentFunction(nextElement.toLocaleString, nextElement, locales, options));
|
||||
#else
|
||||
R += ToString(callContentFunction(nextElement.toLocaleString, nextElement));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Step 10.
|
||||
return R;
|
||||
}
|
||||
|
||||
// ES 2016 draft Mar 25, 2016 22.1.2.5.
|
||||
function ArraySpecies() {
|
||||
// Step 1.
|
||||
|
|
|
@ -1128,14 +1128,14 @@ struct ArrayJoinDenseKernelFunctor {
|
|||
}
|
||||
};
|
||||
|
||||
template <bool Locale, typename SeparatorOp>
|
||||
template <typename SeparatorOp>
|
||||
static bool
|
||||
ArrayJoinKernel(JSContext* cx, SeparatorOp sepOp, HandleObject obj, uint32_t length,
|
||||
StringBuffer& sb)
|
||||
{
|
||||
uint32_t i = 0;
|
||||
|
||||
if (!Locale && !ObjectMayHaveExtraIndexedProperties(obj)) {
|
||||
if (!ObjectMayHaveExtraIndexedProperties(obj)) {
|
||||
ArrayJoinDenseKernelFunctor<SeparatorOp> functor(cx, sepOp, obj, length, sb, &i);
|
||||
DenseElementResult result = CallBoxedOrUnboxedSpecialization(functor, obj);
|
||||
if (result == DenseElementResult::Failure)
|
||||
|
@ -1152,14 +1152,6 @@ ArrayJoinKernel(JSContext* cx, SeparatorOp sepOp, HandleObject obj, uint32_t len
|
|||
if (!GetElement(cx, obj, i, &hole, &v))
|
||||
return false;
|
||||
if (!hole && !v.isNullOrUndefined()) {
|
||||
if (Locale) {
|
||||
RootedValue fun(cx);
|
||||
if (!GetProperty(cx, v, cx->names().toLocaleString, &fun))
|
||||
return false;
|
||||
|
||||
if (!Call(cx, fun, v, &v))
|
||||
return false;
|
||||
}
|
||||
if (!ValueToStringBuffer(cx, v, sb))
|
||||
return false;
|
||||
}
|
||||
|
@ -1172,13 +1164,14 @@ ArrayJoinKernel(JSContext* cx, SeparatorOp sepOp, HandleObject obj, uint32_t len
|
|||
return true;
|
||||
}
|
||||
|
||||
template <bool Locale>
|
||||
/* ES5 15.4.4.5 */
|
||||
bool
|
||||
ArrayJoin(JSContext* cx, CallArgs& args)
|
||||
js::array_join(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
// This method is shared by Array.prototype.join and
|
||||
// Array.prototype.toLocaleString. The steps in ES5 are nearly the same, so
|
||||
// the annotations in this function apply to both toLocaleString and join.
|
||||
JS_CHECK_RECURSION(cx, return false);
|
||||
|
||||
AutoSPSEntry pseudoFrame(cx->runtime(), "Array.prototype.join");
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
// Step 1
|
||||
RootedObject obj(cx, ToObject(cx, args.thisv()));
|
||||
|
@ -1201,7 +1194,7 @@ ArrayJoin(JSContext* cx, CallArgs& args)
|
|||
|
||||
// Steps 4 and 5
|
||||
RootedLinearString sepstr(cx);
|
||||
if (!Locale && args.hasDefined(0)) {
|
||||
if (args.hasDefined(0)) {
|
||||
JSString *s = ToString<CanGC>(cx, args[0]);
|
||||
if (!s)
|
||||
return false;
|
||||
|
@ -1217,7 +1210,7 @@ ArrayJoin(JSContext* cx, CallArgs& args)
|
|||
// An optimized version of a special case of steps 7-11: when length==1 and
|
||||
// the 0th element is a string, ToString() of that element is a no-op and
|
||||
// so it can be immediately returned as the result.
|
||||
if (length == 1 && !Locale && GetAnyBoxedOrUnboxedInitializedLength(obj) == 1) {
|
||||
if (length == 1 && GetAnyBoxedOrUnboxedInitializedLength(obj) == 1) {
|
||||
Value elem0 = GetAnyBoxedOrUnboxedDenseElement(obj, 0);
|
||||
if (elem0.isString()) {
|
||||
args.rval().set(elem0);
|
||||
|
@ -1244,22 +1237,22 @@ ArrayJoin(JSContext* cx, CallArgs& args)
|
|||
// Various optimized versions of steps 7-10.
|
||||
if (seplen == 0) {
|
||||
EmptySeparatorOp op;
|
||||
if (!ArrayJoinKernel<Locale>(cx, op, obj, length, sb))
|
||||
if (!ArrayJoinKernel(cx, op, obj, length, sb))
|
||||
return false;
|
||||
} else if (seplen == 1) {
|
||||
char16_t c = sepstr->latin1OrTwoByteChar(0);
|
||||
if (c <= JSString::MAX_LATIN1_CHAR) {
|
||||
CharSeparatorOp<Latin1Char> op(c);
|
||||
if (!ArrayJoinKernel<Locale>(cx, op, obj, length, sb))
|
||||
if (!ArrayJoinKernel(cx, op, obj, length, sb))
|
||||
return false;
|
||||
} else {
|
||||
CharSeparatorOp<char16_t> op(c);
|
||||
if (!ArrayJoinKernel<Locale>(cx, op, obj, length, sb))
|
||||
if (!ArrayJoinKernel(cx, op, obj, length, sb))
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
StringSeparatorOp op(sepstr);
|
||||
if (!ArrayJoinKernel<Locale>(cx, op, obj, length, sb))
|
||||
if (!ArrayJoinKernel(cx, op, obj, length, sb))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1272,25 +1265,49 @@ ArrayJoin(JSContext* cx, CallArgs& args)
|
|||
return true;
|
||||
}
|
||||
|
||||
/* ES5 15.4.4.3 */
|
||||
// ES2017 draft rev f8a9be8ea4bd97237d176907a1e3080dce20c68f
|
||||
// 22.1.3.27 Array.prototype.toLocaleString ([ reserved1 [ , reserved2 ] ])
|
||||
// ES2017 Intl draft rev 78bbe7d1095f5ff3760ac4017ed366026e4cb276
|
||||
// 13.4.1 Array.prototype.toLocaleString ([ locales [ , options ]])
|
||||
static bool
|
||||
array_toLocaleString(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
JS_CHECK_RECURSION(cx, return false);
|
||||
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return ArrayJoin<true>(cx, args);
|
||||
|
||||
// Step 1
|
||||
RootedObject obj(cx, ToObject(cx, args.thisv()));
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
// Avoid calling into self-hosted code if the array is empty.
|
||||
if (obj->is<ArrayObject>() && obj->as<ArrayObject>().length() == 0) {
|
||||
args.rval().setString(cx->names().empty);
|
||||
return true;
|
||||
}
|
||||
if (obj->is<UnboxedArrayObject>() && obj->as<UnboxedArrayObject>().length() == 0) {
|
||||
args.rval().setString(cx->names().empty);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ES5 15.4.4.5 */
|
||||
bool
|
||||
js::array_join(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
JS_CHECK_RECURSION(cx, return false);
|
||||
AutoCycleDetector detector(cx, obj);
|
||||
if (!detector.init())
|
||||
return false;
|
||||
|
||||
AutoSPSEntry pseudoFrame(cx->runtime(), "Array.prototype.join");
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return ArrayJoin<false>(cx, args);
|
||||
if (detector.foundCycle()) {
|
||||
args.rval().setString(cx->names().empty);
|
||||
return true;
|
||||
}
|
||||
|
||||
FixedInvokeArgs<2> args2(cx);
|
||||
|
||||
args2[0].set(args.get(0));
|
||||
args2[1].set(args.get(1));
|
||||
|
||||
// Steps 2-10.
|
||||
RootedValue thisv(cx, ObjectValue(*obj));
|
||||
return CallSelfHostedFunction(cx, cx->names().ArrayToLocaleString, thisv, args2, args.rval());
|
||||
}
|
||||
|
||||
/* vector must point to rooted memory. */
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
if (typeof Intl === "object") {
|
||||
const localeSep = [,,].toLocaleString();
|
||||
|
||||
const date = new Date(Date.UTC(2012, 11, 12, 3, 0, 0));
|
||||
|
||||
assertEq([date].toLocaleString("en-us", {timeZone: "UTC"}), "12/12/2012, 3:00:00 AM");
|
||||
assertEq([date].toLocaleString(["de", "en"], {timeZone: "UTC"}), "12.12.2012, 03:00:00");
|
||||
assertEq([date].toLocaleString("th-th", {timeZone: "UTC"}), "12/12/2555 03:00:00");
|
||||
assertEq([date].toLocaleString("th-th-u-nu-thai", {timeZone: "UTC"}), "๑๒/๑๒/๒๕๕๕ ๐๓:๐๐:๐๐");
|
||||
|
||||
const sampleValues = [
|
||||
date, new Date(0),
|
||||
];
|
||||
const sampleLocales = [
|
||||
void 0,
|
||||
"en",
|
||||
"th-th-u-nu-thai",
|
||||
"ja-jp",
|
||||
"ar-ma-u-ca-islamicc",
|
||||
["tlh", "de"],
|
||||
];
|
||||
const numericFormatOptions = {
|
||||
timeZone: "UTC",
|
||||
year: "numeric", month: "numeric", day: "numeric",
|
||||
hour: "numeric", minute: "numeric", second: "numeric",
|
||||
};
|
||||
const longFormatOptions = {
|
||||
timeZone: "UTC",
|
||||
year: "numeric", month: "long", day: "numeric",
|
||||
hour: "numeric", minute: "numeric", second: "numeric"
|
||||
};
|
||||
const sampleOptions = [
|
||||
{timeZone: "UTC"},
|
||||
longFormatOptions,
|
||||
];
|
||||
|
||||
for (let locale of sampleLocales) {
|
||||
for (let options of sampleOptions) {
|
||||
let dtfOptions;
|
||||
if (options === longFormatOptions) {
|
||||
dtfOptions = longFormatOptions;
|
||||
} else {
|
||||
dtfOptions = numericFormatOptions;
|
||||
}
|
||||
let dtf = new Intl.DateTimeFormat(locale, dtfOptions);
|
||||
let expected = sampleValues.map(dtf.format).join(localeSep);
|
||||
assertEq(sampleValues.toLocaleString(locale, options), expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
|
@ -0,0 +1,34 @@
|
|||
if (typeof Intl === "object") {
|
||||
const localeSep = [,,].toLocaleString();
|
||||
|
||||
assertEq([NaN].toLocaleString("ar"), "ليس رقم");
|
||||
assertEq([NaN].toLocaleString(["zh-hant", "ar"]), "非數值");
|
||||
assertEq([Infinity].toLocaleString("dz"), "གྲངས་མེད");
|
||||
assertEq([-Infinity].toLocaleString(["fr", "en"]), "-∞");
|
||||
|
||||
const sampleValues = [
|
||||
-0, +0, -1, +1, -2, +2, -0.5, +0.5,
|
||||
];
|
||||
const sampleLocales = [
|
||||
void 0,
|
||||
"en",
|
||||
"th-th-u-nu-thai",
|
||||
["tlh", "de"],
|
||||
];
|
||||
const sampleOptions = [
|
||||
void 0,
|
||||
{},
|
||||
{style: "percent"},
|
||||
{style: "currency", currency: "USD", minimumIntegerDigits: 4},
|
||||
];
|
||||
for (let locale of sampleLocales) {
|
||||
for (let options of sampleOptions) {
|
||||
let nf = new Intl.NumberFormat(locale, options);
|
||||
let expected = sampleValues.map(nf.format).join(localeSep);
|
||||
assertEq(sampleValues.toLocaleString(locale, options), expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
|
@ -0,0 +1,35 @@
|
|||
if (typeof Intl === "object") {
|
||||
const localeSep = [,,].toLocaleString();
|
||||
|
||||
// Missing arguments are passed as |undefined|.
|
||||
const objNoArgs = {
|
||||
toLocaleString() {
|
||||
assertEq(arguments.length, 2);
|
||||
assertEq(arguments[0], undefined);
|
||||
assertEq(arguments[1], undefined);
|
||||
return "pass";
|
||||
}
|
||||
};
|
||||
// - Single element case.
|
||||
assertEq([objNoArgs].toLocaleString(), "pass");
|
||||
// - More than one element.
|
||||
assertEq([objNoArgs, objNoArgs].toLocaleString(), "pass" + localeSep + "pass");
|
||||
|
||||
// Ensure "locales" and "options" arguments are passed to the array elements.
|
||||
const locales = {}, options = {};
|
||||
const objWithArgs = {
|
||||
toLocaleString() {
|
||||
assertEq(arguments.length, 2);
|
||||
assertEq(arguments[0], locales);
|
||||
assertEq(arguments[1], options);
|
||||
return "pass";
|
||||
}
|
||||
};
|
||||
// - Single element case.
|
||||
assertEq([objWithArgs].toLocaleString(locales, options), "pass");
|
||||
// - More than one element.
|
||||
assertEq([objWithArgs, objWithArgs].toLocaleString(locales, options), "pass" + localeSep + "pass");
|
||||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
|
@ -0,0 +1,26 @@
|
|||
if (typeof Intl !== "object") {
|
||||
const localeSep = [,,].toLocaleString();
|
||||
|
||||
const obj = {
|
||||
toLocaleString() {
|
||||
assertEq(arguments.length, 0);
|
||||
return "pass";
|
||||
}
|
||||
};
|
||||
|
||||
// Ensure no arguments are passed to the array elements.
|
||||
// - Single element case.
|
||||
assertEq([obj].toLocaleString(), "pass");
|
||||
// - More than one element.
|
||||
assertEq([obj, obj].toLocaleString(), "pass" + localeSep + "pass");
|
||||
|
||||
// Ensure no arguments are passed to the array elements even if supplied.
|
||||
const locales = {}, options = {};
|
||||
// - Single element case.
|
||||
assertEq([obj].toLocaleString(locales, options), "pass");
|
||||
// - More than one element.
|
||||
assertEq([obj, obj].toLocaleString(locales, options), "pass" + localeSep + "pass");
|
||||
}
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
|
@ -23,6 +23,7 @@
|
|||
macro(ArrayIteratorNext, ArrayIteratorNext, "ArrayIteratorNext") \
|
||||
macro(ArraySpecies, ArraySpecies, "ArraySpecies") \
|
||||
macro(ArraySpeciesCreate, ArraySpeciesCreate, "ArraySpeciesCreate") \
|
||||
macro(ArrayToLocaleString, ArrayToLocaleString, "ArrayToLocaleString") \
|
||||
macro(ArrayType, ArrayType, "ArrayType") \
|
||||
macro(ArrayValues, ArrayValues, "ArrayValues") \
|
||||
macro(ArrayValuesAt, ArrayValuesAt, "ArrayValuesAt") \
|
||||
|
|
Загрузка…
Ссылка в новой задаче