Bug 869996 - Implement Set.prototype.{keys, values, entries}. r=jorendorff.

--HG--
extra : rebase_source : 6d978d49d2fb696162b1cef5b643a209a01a4711
This commit is contained in:
Sankha Narayan Guria 2013-06-05 14:17:30 -05:00
parent 05ffce93db
commit cb3aa2346e
8 changed files with 143 additions and 15 deletions

View File

@ -1041,8 +1041,6 @@ const JSFunctionSpec MapObject::methods[] = {
JS_FN("delete", delete_, 1, 0),
JS_FN("keys", keys, 0, 0),
JS_FN("values", values, 0, 0),
JS_FN("entries", entries, 0, 0),
JS_FN("iterator", entries, 0, 0),
JS_FN("clear", clear, 0, 0),
JS_FS_END
};
@ -1071,7 +1069,20 @@ JSObject *
MapObject::initClass(JSContext *cx, JSObject *obj)
{
Rooted<GlobalObject*> global(cx, &obj->asGlobal());
return InitClass(cx, global, &class_, JSProto_Map, construct, properties, methods);
RootedObject proto(cx,
InitClass(cx, global, &class_, JSProto_Map, construct, properties, methods));
if (proto) {
// Define the "entries" method.
JSFunction *fun = JS_DefineFunction(cx, proto, "entries", entries, 0, 0);
if (!fun)
return NULL;
// Define its alias.
RootedValue funval(cx, ObjectValue(*fun));
if (!JS_DefineProperty(cx, proto, "iterator", funval, NULL, NULL, 0))
return NULL;
}
return proto;
}
template <class Range>
@ -1422,14 +1433,16 @@ js_InitMapClass(JSContext *cx, HandleObject obj)
class js::SetIteratorObject : public JSObject
{
public:
enum { TargetSlot, RangeSlot, SlotCount };
enum { TargetSlot, KindSlot, RangeSlot, SlotCount };
static const JSFunctionSpec methods[];
static SetIteratorObject *create(JSContext *cx, HandleObject setobj, ValueSet *data);
static SetIteratorObject *create(JSContext *cx, HandleObject setobj, ValueSet *data,
SetObject::IteratorKind kind);
static void finalize(FreeOp *fop, JSObject *obj);
private:
static inline bool is(const Value &v);
inline ValueSet::Range *range();
inline SetObject::IteratorKind kind() const;
static bool next_impl(JSContext *cx, CallArgs args);
static JSBool next(JSContext *cx, unsigned argc, Value *vp);
};
@ -1466,6 +1479,14 @@ SetIteratorObject::range()
return static_cast<ValueSet::Range *>(getSlot(RangeSlot).toPrivate());
}
inline SetObject::IteratorKind
SetIteratorObject::kind() const
{
int32_t i = getSlot(KindSlot).toInt32();
JS_ASSERT(i == SetObject::Values || i == SetObject::Entries);
return SetObject::IteratorKind(i);
}
bool
GlobalObject::initSetIteratorProto(JSContext *cx, Handle<GlobalObject*> global)
{
@ -1483,7 +1504,8 @@ GlobalObject::initSetIteratorProto(JSContext *cx, Handle<GlobalObject*> global)
}
SetIteratorObject *
SetIteratorObject::create(JSContext *cx, HandleObject setobj, ValueSet *data)
SetIteratorObject::create(JSContext *cx, HandleObject setobj, ValueSet *data,
SetObject::IteratorKind kind)
{
Rooted<GlobalObject *> global(cx, &setobj->global());
Rooted<JSObject*> proto(cx, global->getOrCreateSetIteratorPrototype(cx));
@ -1500,6 +1522,7 @@ SetIteratorObject::create(JSContext *cx, HandleObject setobj, ValueSet *data)
return NULL;
}
iterobj->setSlot(TargetSlot, ObjectValue(*setobj));
iterobj->setSlot(KindSlot, Int32Value(int32_t(kind)));
iterobj->setSlot(RangeSlot, PrivateValue(range));
return static_cast<SetIteratorObject *>(iterobj);
}
@ -1529,7 +1552,23 @@ SetIteratorObject::next_impl(JSContext *cx, CallArgs args)
return js_ThrowStopIteration(cx);
}
args.rval().set(range->front().get());
switch (thisobj.kind()) {
case SetObject::Values:
args.rval().set(range->front().get());
break;
case SetObject::Entries: {
Value pair[2] = { range->front().get(), range->front().get() };
AutoValueArray root(cx, pair, 2);
JSObject *pairobj = NewDenseCopiedArray(cx, 2, pair);
if (!pairobj)
return false;
args.rval().setObject(*pairobj);
break;
}
}
range->popFront();
return true;
}
@ -1579,7 +1618,7 @@ const JSFunctionSpec SetObject::methods[] = {
JS_FN("has", has, 1, 0),
JS_FN("add", add, 1, 0),
JS_FN("delete", delete_, 1, 0),
JS_FN("iterator", iterator, 0, 0),
JS_FN("entries", entries, 0, 0),
JS_FN("clear", clear, 0, 0),
JS_FS_END
};
@ -1588,7 +1627,22 @@ JSObject *
SetObject::initClass(JSContext *cx, JSObject *obj)
{
Rooted<GlobalObject*> global(cx, &obj->asGlobal());
return InitClass(cx, global, &class_, JSProto_Set, construct, properties, methods);
RootedObject proto(cx,
InitClass(cx, global, &class_, JSProto_Set, construct, properties, methods));
if (proto) {
// Define the "values" method.
JSFunction *fun = JS_DefineFunction(cx, proto, "values", values, 0, 0);
if (!fun)
return NULL;
// Define its aliases.
RootedValue funval(cx, ObjectValue(*fun));
if (!JS_DefineProperty(cx, proto, "keys", funval, NULL, NULL, 0))
return NULL;
if (!JS_DefineProperty(cx, proto, "iterator", funval, NULL, NULL, 0))
return NULL;
}
return proto;
}
void
@ -1743,22 +1797,41 @@ SetObject::delete_(JSContext *cx, unsigned argc, Value *vp)
}
bool
SetObject::iterator_impl(JSContext *cx, CallArgs args)
SetObject::iterator_impl(JSContext *cx, CallArgs args, IteratorKind kind)
{
Rooted<SetObject*> setobj(cx, &args.thisv().toObject().asSet());
ValueSet &set = *setobj->getData();
Rooted<JSObject*> iterobj(cx, SetIteratorObject::create(cx, setobj, &set));
Rooted<JSObject*> iterobj(cx, SetIteratorObject::create(cx, setobj, &set, kind));
if (!iterobj)
return false;
args.rval().setObject(*iterobj);
return true;
}
bool
SetObject::values_impl(JSContext *cx, CallArgs args)
{
return iterator_impl(cx, args, Values);
}
JSBool
SetObject::iterator(JSContext *cx, unsigned argc, Value *vp)
SetObject::values(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod(cx, is, iterator_impl, args);
return CallNonGenericMethod(cx, is, values_impl, args);
}
bool
SetObject::entries_impl(JSContext *cx, CallArgs args)
{
return iterator_impl(cx, args, Entries);
}
JSBool
SetObject::entries(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod(cx, is, entries_impl, args);
}
bool

View File

@ -127,6 +127,7 @@ class MapObject : public JSObject {
class SetObject : public JSObject {
public:
enum IteratorKind { Values, Entries };
static JSObject *initClass(JSContext *cx, JSObject *obj);
static Class class_;
private:
@ -140,6 +141,8 @@ class SetObject : public JSObject {
static bool is(const Value &v);
static bool iterator_impl(JSContext *cx, CallArgs args, IteratorKind kind);
static bool size_impl(JSContext *cx, CallArgs args);
static JSBool size(JSContext *cx, unsigned argc, Value *vp);
static bool has_impl(JSContext *cx, CallArgs args);
@ -148,8 +151,10 @@ class SetObject : public JSObject {
static JSBool add(JSContext *cx, unsigned argc, Value *vp);
static bool delete_impl(JSContext *cx, CallArgs args);
static JSBool delete_(JSContext *cx, unsigned argc, Value *vp);
static bool iterator_impl(JSContext *cx, CallArgs args);
static JSBool iterator(JSContext *cx, unsigned argc, Value *vp);
static bool values_impl(JSContext *cx, CallArgs args);
static JSBool values(JSContext *cx, unsigned argc, Value *vp);
static bool entries_impl(JSContext *cx, CallArgs args);
static JSBool entries(JSContext *cx, unsigned argc, Value *vp);
static bool clear_impl(JSContext *cx, CallArgs args);
static JSBool clear(JSContext *cx, unsigned argc, Value *vp);
};

View File

@ -31,6 +31,9 @@ checkMethod("get", 1);
checkMethod("has", 1);
checkMethod("set", 2);
checkMethod("delete", 1);
checkMethod("keys", 0);
checkMethod("values", 0);
checkMethod("entries", 0);
var desc = Object.getOwnPropertyDescriptor(Map.prototype, "size");
assertEq(desc.enumerable, false);
@ -39,3 +42,6 @@ assertEq(typeof desc.get, 'function');
assertEq(desc.get.length, 0);
assertEq(desc.set, undefined);
checkMethod("clear", 0);
// Map.prototype.iterator and .entries are the same function object.
assertEq(Map.prototype.iterator, Map.prototype.entries);

View File

@ -16,6 +16,9 @@ function test(obj) {
testcase(obj, Map.prototype.set, "x", 1);
testcase(obj, Map.prototype.delete, "x");
testcase(obj, Map.prototype.clear);
testcase(obj, Map.prototype.keys);
testcase(obj, Map.prototype.values);
testcase(obj, Map.prototype.entries);
testcase(obj, Map_size_getter);
}

View File

@ -30,6 +30,8 @@ function checkMethod(name, arity) {
checkMethod("has", 1);
checkMethod("add", 1);
checkMethod("delete", 1);
checkMethod("values", 0);
checkMethod("entries", 0);
var desc = Object.getOwnPropertyDescriptor(Set.prototype, "size");
assertEq(desc.enumerable, false);
@ -38,3 +40,7 @@ assertEq(typeof desc.get, 'function');
assertEq(desc.get.length, 0);
assertEq(desc.set, undefined);
checkMethod("clear", 0);
// Set.prototype.keys, .values, and .iterator are the same function object
assertEq(Set.prototype.keys, Set.prototype.values);
assertEq(Set.prototype.iterator, Set.prototype.values);

View File

@ -15,6 +15,9 @@ function test(obj) {
testcase(obj, Set.prototype.add, 12);
testcase(obj, Set.prototype.delete, 12);
testcase(obj, Set.prototype.clear);
testcase(obj, Set.prototype.keys);
testcase(obj, Set.prototype.values);
testcase(obj, Set.prototype.entries);
testcase(obj, Set_size_getter);
}

View File

@ -0,0 +1,14 @@
// set.keys(), .values(), and .entries() on an empty set produce empty iterators
var s = Set();
var ki = s.keys(), vi = s.values(), ei = s.entries();
var p = Object.getPrototypeOf(ki);
assertEq(Object.getPrototypeOf(vi), p);
assertEq(Object.getPrototypeOf(ei), p);
for (let k of ki)
throw "FAIL";
for (let v of vi)
throw "FAIL";
for (let [k, v] of ei)
throw "FAIL";

View File

@ -0,0 +1,18 @@
// set.keys() and set.values() return iterators over the elements
// and set.entries() returns an iterator that yields pairs [e, e].
load(libdir + "asserts.js");
var data = [1, 2, 3, 4];
var s = Set(data);
var ki = s.keys();
assertEq(ki.next(), 1);
assertEq(ki.next(), 2);
assertEq(ki.next(), 3);
assertEq(ki.next(), 4);
assertThrowsValue(function () { ki.next(); }, StopIteration);
assertEq([...s.keys()].toSource(), data.toSource());
assertEq([...s.values()].toSource(), data.toSource());
assertEq([...s.entries()].toSource(), [[1, 1], [2, 2], [3, 3], [4, 4]].toSource());