Bug 1130636 - Reimplement Array.prototype.toLocaleString as per ECMA-402, 2nd edition. r=Waldo

This commit is contained in:
André Bargull 2016-10-05 03:25:57 -07:00
Родитель b2bb341033
Коммит 7a0641b5c2
8 изменённых файлов: 253 добавлений и 32 удалений

Просмотреть файл

@ -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);
}
/* ES5 15.4.4.5 */
bool
js::array_join(JSContext* cx, unsigned argc, Value* vp)
{
JS_CHECK_RECURSION(cx, return false);
// Step 1
RootedObject obj(cx, ToObject(cx, args.thisv()));
if (!obj)
return false;
AutoSPSEntry pseudoFrame(cx->runtime(), "Array.prototype.join");
CallArgs args = CallArgsFromVp(argc, vp);
return ArrayJoin<false>(cx, args);
// 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;
}
AutoCycleDetector detector(cx, obj);
if (!detector.init())
return false;
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") \