LibJS: Add the SetIterator built-in and Set.prototype.{values, entries}

While this implementation should be complete it is based on HashTable's
iterator, which currently follows bucket-order instead of the required
insertion order. This can be simply fixed by replacing the underlying
HashTable member in Set with an enhanced one that maintains a linked
list in insertion order.
This commit is contained in:
Idan Horowitz 2021-06-09 00:17:17 +03:00 committed by Linus Groh
parent 0b0f1eda05
commit 2a3090d292
Notes: sideshowbarker 2024-07-18 12:34:11 +09:00
10 changed files with 209 additions and 0 deletions

View file

@ -78,6 +78,8 @@ set(SOURCES
Runtime/ScriptFunction.cpp
Runtime/Set.cpp
Runtime/SetConstructor.cpp
Runtime/SetIterator.cpp
Runtime/SetIteratorPrototype.cpp
Runtime/SetPrototype.cpp
Runtime/Shape.cpp
Runtime/StringConstructor.cpp

View file

@ -69,6 +69,7 @@
#define JS_ENUMERATE_ITERATOR_PROTOTYPES \
__JS_ENUMERATE(Iterator, iterator) \
__JS_ENUMERATE(ArrayIterator, array_iterator) \
__JS_ENUMERATE(SetIterator, set_iterator) \
__JS_ENUMERATE(StringIterator, string_iterator)
#define JS_ENUMERATE_BUILTIN_TYPES \

View file

@ -48,6 +48,7 @@
#include <LibJS/Runtime/RegExpConstructor.h>
#include <LibJS/Runtime/RegExpPrototype.h>
#include <LibJS/Runtime/SetConstructor.h>
#include <LibJS/Runtime/SetIteratorPrototype.h>
#include <LibJS/Runtime/SetPrototype.h>
#include <LibJS/Runtime/Shape.h>
#include <LibJS/Runtime/StringConstructor.h>

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/SetIterator.h>
namespace JS {
SetIterator* SetIterator::create(GlobalObject& global_object, Set& set, Object::PropertyKind iteration_kind)
{
return global_object.heap().allocate<SetIterator>(global_object, *global_object.set_iterator_prototype(), set, iteration_kind);
}
SetIterator::SetIterator(Object& prototype, Set& set, Object::PropertyKind iteration_kind)
: Object(prototype)
, m_set(set)
, m_iteration_kind(iteration_kind)
, m_iterator(set.values().begin())
{
}
SetIterator::~SetIterator()
{
}
void SetIterator::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(&m_set);
}
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/HashTable.h>
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/Set.h>
namespace JS {
class SetIterator final : public Object {
JS_OBJECT(SetIterator, Object);
public:
static SetIterator* create(GlobalObject&, Set& set, Object::PropertyKind iteration_kind);
explicit SetIterator(Object& prototype, Set& set, Object::PropertyKind iteration_kind);
virtual ~SetIterator() override;
Set& set() const { return m_set; }
bool done() const { return m_done; }
Object::PropertyKind iteration_kind() const { return m_iteration_kind; }
private:
friend class SetIteratorPrototype;
virtual void visit_edges(Cell::Visitor&) override;
Set& m_set;
bool m_done { false };
Object::PropertyKind m_iteration_kind;
HashTable<Value, ValueTraits>::Iterator m_iterator;
};
}

View file

@ -0,0 +1,67 @@
/*
* Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/IteratorOperations.h>
#include <LibJS/Runtime/Set.h>
#include <LibJS/Runtime/SetIterator.h>
#include <LibJS/Runtime/SetIteratorPrototype.h>
namespace JS {
SetIteratorPrototype::SetIteratorPrototype(GlobalObject& global_object)
: Object(*global_object.iterator_prototype())
{
}
void SetIteratorPrototype::initialize(GlobalObject& global_object)
{
auto& vm = this->vm();
Object::initialize(global_object);
define_native_function(vm.names.next, next, 0, Attribute::Configurable | Attribute::Writable);
define_property(vm.well_known_symbol_to_string_tag(), js_string(global_object.heap(), "Set Iterator"), Attribute::Configurable);
}
SetIteratorPrototype::~SetIteratorPrototype()
{
}
JS_DEFINE_NATIVE_FUNCTION(SetIteratorPrototype::next)
{
auto this_value = vm.this_value(global_object);
if (!this_value.is_object() || !is<SetIterator>(this_value.as_object())) {
vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Set Iterator");
return {};
}
auto& set_iterator = static_cast<SetIterator&>(this_value.as_object());
if (set_iterator.done())
return create_iterator_result_object(global_object, js_undefined(), true);
auto& set = set_iterator.set();
if (set_iterator.m_iterator == set.values().end()) {
set_iterator.m_done = true;
return create_iterator_result_object(global_object, js_undefined(), true);
}
auto iteration_kind = set_iterator.iteration_kind();
VERIFY(iteration_kind != Object::PropertyKind::Key);
auto value = *set_iterator.m_iterator;
++set_iterator.m_iterator;
if (iteration_kind == Object::PropertyKind::Value)
return create_iterator_result_object(global_object, value, false);
auto* entry_array = Array::create(global_object);
entry_array->define_property(0, value);
entry_array->define_property(1, value);
return create_iterator_result_object(global_object, entry_array, false);
}
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Runtime/Object.h>
namespace JS {
class SetIteratorPrototype final : public Object {
JS_OBJECT(SetIteratorPrototype, Object)
public:
SetIteratorPrototype(GlobalObject&);
virtual void initialize(GlobalObject&) override;
virtual ~SetIteratorPrototype() override;
private:
JS_DECLARE_NATIVE_FUNCTION(next);
};
}

View file

@ -5,6 +5,7 @@
*/
#include <AK/HashTable.h>
#include <LibJS/Runtime/SetIterator.h>
#include <LibJS/Runtime/SetPrototype.h>
namespace JS {
@ -23,11 +24,15 @@ void SetPrototype::initialize(GlobalObject& global_object)
define_native_function(vm.names.add, add, 1, attr);
define_native_function(vm.names.clear, clear, 0, attr);
define_native_function(vm.names.delete_, delete_, 1, attr);
define_native_function(vm.names.entries, entries, 0, attr);
define_native_function(vm.names.forEach, for_each, 1, attr);
define_native_function(vm.names.has, has, 1, attr);
define_native_function(vm.names.values, values, 0, attr);
define_native_property(vm.names.size, size_getter, {}, attr);
define_property(vm.names.keys, get(vm.names.values), attr);
define_property(vm.well_known_symbol_iterator(), get(vm.names.values), attr);
define_property(vm.well_known_symbol_to_string_tag(), js_string(global_object.heap(), vm.names.Set), Attribute::Configurable);
}
@ -64,6 +69,15 @@ JS_DEFINE_NATIVE_FUNCTION(SetPrototype::delete_)
return Value(set->values().remove(vm.argument(0)));
}
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::entries)
{
auto* set = typed_this(vm, global_object);
if (!set)
return {};
return SetIterator::create(global_object, *set, Object::PropertyKind::KeyAndValue);
}
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::for_each)
{
auto* set = typed_this(vm, global_object);
@ -91,6 +105,15 @@ JS_DEFINE_NATIVE_FUNCTION(SetPrototype::has)
return Value(values.find(vm.argument(0)) != values.end());
}
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::values)
{
auto* set = typed_this(vm, global_object);
if (!set)
return {};
return SetIterator::create(global_object, *set, Object::PropertyKind::Value);
}
JS_DEFINE_NATIVE_GETTER(SetPrototype::size_getter)
{
auto* set = typed_this(vm, global_object);

View file

@ -22,8 +22,10 @@ private:
JS_DECLARE_NATIVE_FUNCTION(add);
JS_DECLARE_NATIVE_FUNCTION(clear);
JS_DECLARE_NATIVE_FUNCTION(delete_);
JS_DECLARE_NATIVE_FUNCTION(entries);
JS_DECLARE_NATIVE_FUNCTION(for_each);
JS_DECLARE_NATIVE_FUNCTION(has);
JS_DECLARE_NATIVE_FUNCTION(values);
JS_DECLARE_NATIVE_GETTER(size_getter);
};

View file

@ -0,0 +1,14 @@
test("length", () => {
expect(Set.prototype.values.length).toBe(0);
});
test("basic functionality", () => {
const a = new Set([1, 2, 3]);
const it = a.values();
expect(it.next()).toEqual({ value: 1, done: false });
expect(it.next()).toEqual({ value: 2, done: false });
expect(it.next()).toEqual({ value: 3, done: false });
expect(it.next()).toEqual({ value: undefined, done: true });
expect(it.next()).toEqual({ value: undefined, done: true });
expect(it.next()).toEqual({ value: undefined, done: true });
});