1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 11:37:34 +00:00

LibJS: Add the MapIterator built-in and the key/values/entries methods

While this implementation should be complete it is based on HashMap's
iterator, which currently follows bucket-order instead of the required
insertion order. This can be simply fixed by replacing the underlying
HashMap member in Map with an enhanced one that maintains a linked
list in insertion order.
This commit is contained in:
Idan Horowitz 2021-06-12 23:58:03 +03:00 committed by Linus Groh
parent 6c0d5163a1
commit 322c8a3995
12 changed files with 286 additions and 0 deletions

View file

@ -57,6 +57,8 @@ set(SOURCES
Runtime/LexicalEnvironment.cpp
Runtime/Map.cpp
Runtime/MapConstructor.cpp
Runtime/MapIterator.cpp
Runtime/MapIteratorPrototype.cpp
Runtime/MapPrototype.cpp
Runtime/MarkedValueList.cpp
Runtime/MathObject.cpp

View file

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

View file

@ -37,6 +37,7 @@
#include <LibJS/Runtime/IteratorPrototype.h>
#include <LibJS/Runtime/JSONObject.h>
#include <LibJS/Runtime/MapConstructor.h>
#include <LibJS/Runtime/MapIteratorPrototype.h>
#include <LibJS/Runtime/MapPrototype.h>
#include <LibJS/Runtime/MathObject.h>
#include <LibJS/Runtime/NativeFunction.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/MapIterator.h>
namespace JS {
MapIterator* MapIterator::create(GlobalObject& global_object, Map& map, Object::PropertyKind iteration_kind)
{
return global_object.heap().allocate<MapIterator>(global_object, *global_object.map_iterator_prototype(), map, iteration_kind);
}
MapIterator::MapIterator(Object& prototype, Map& map, Object::PropertyKind iteration_kind)
: Object(prototype)
, m_map(map)
, m_iteration_kind(iteration_kind)
, m_iterator(map.entries().begin())
{
}
MapIterator::~MapIterator()
{
}
void MapIterator::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(&m_map);
}
}

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/HashMap.h>
#include <LibJS/Runtime/Map.h>
#include <LibJS/Runtime/Object.h>
namespace JS {
class MapIterator final : public Object {
JS_OBJECT(MapIterator, Object);
public:
static MapIterator* create(GlobalObject&, Map& map, Object::PropertyKind iteration_kind);
explicit MapIterator(Object& prototype, Map& map, Object::PropertyKind iteration_kind);
virtual ~MapIterator() override;
Map& map() const { return m_map; }
bool done() const { return m_done; }
Object::PropertyKind iteration_kind() const { return m_iteration_kind; }
private:
friend class MapIteratorPrototype;
virtual void visit_edges(Cell::Visitor&) override;
Map& m_map;
bool m_done { false };
Object::PropertyKind m_iteration_kind;
HashMap<Value, Value, ValueTraits>::IteratorType 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/MapIterator.h>
#include <LibJS/Runtime/MapIteratorPrototype.h>
namespace JS {
MapIteratorPrototype::MapIteratorPrototype(GlobalObject& global_object)
: Object(*global_object.iterator_prototype())
{
}
void MapIteratorPrototype::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(), "Map Iterator"), Attribute::Configurable);
}
MapIteratorPrototype::~MapIteratorPrototype()
{
}
JS_DEFINE_NATIVE_FUNCTION(MapIteratorPrototype::next)
{
auto this_value = vm.this_value(global_object);
if (!this_value.is_object() || !is<MapIterator>(this_value.as_object())) {
vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Map Iterator");
return {};
}
auto& map_iterator = static_cast<MapIterator&>(this_value.as_object());
if (map_iterator.done())
return create_iterator_result_object(global_object, js_undefined(), true);
auto& map = map_iterator.map();
if (map_iterator.m_iterator == map.entries().end()) {
map_iterator.m_done = true;
return create_iterator_result_object(global_object, js_undefined(), true);
}
auto iteration_kind = map_iterator.iteration_kind();
auto entry = *map_iterator.m_iterator;
++map_iterator.m_iterator;
if (iteration_kind == Object::PropertyKind::Key)
return create_iterator_result_object(global_object, entry.key, false);
else if (iteration_kind == Object::PropertyKind::Value)
return create_iterator_result_object(global_object, entry.value, false);
auto* entry_array = Array::create(global_object);
entry_array->define_property(0, entry.key);
entry_array->define_property(1, entry.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 MapIteratorPrototype final : public Object {
JS_OBJECT(MapIteratorPrototype, Object)
public:
MapIteratorPrototype(GlobalObject&);
virtual void initialize(GlobalObject&) override;
virtual ~MapIteratorPrototype() override;
private:
JS_DECLARE_NATIVE_FUNCTION(next);
};
}

View file

@ -5,6 +5,7 @@
*/
#include <AK/HashMap.h>
#include <LibJS/Runtime/MapIterator.h>
#include <LibJS/Runtime/MapPrototype.h>
namespace JS {
@ -22,13 +23,17 @@ void MapPrototype::initialize(GlobalObject& global_object)
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.get, get, 1, attr);
define_native_function(vm.names.has, has, 1, attr);
define_native_function(vm.names.keys, keys, 0, attr);
define_native_function(vm.names.set, set, 2, attr);
define_native_function(vm.names.values, values, 0, attr);
define_native_accessor(vm.names.size, size_getter, {}, Attribute::Configurable);
define_property(vm.well_known_symbol_iterator(), Object::get(vm.names.entries), attr);
define_property(vm.well_known_symbol_to_string_tag(), js_string(global_object.heap(), vm.names.Map), Attribute::Configurable);
}
@ -67,6 +72,16 @@ JS_DEFINE_NATIVE_FUNCTION(MapPrototype::delete_)
return Value(map->entries().remove(vm.argument(0)));
}
// 24.1.3.4 Map.prototype.entries ( ), https://tc39.es/ecma262/#sec-map.prototype.entries
JS_DEFINE_NATIVE_FUNCTION(MapPrototype::entries)
{
auto* map = typed_this(vm, global_object);
if (!map)
return {};
return MapIterator::create(global_object, *map, Object::PropertyKind::KeyAndValue);
}
// 24.1.3.5 Map.prototype.forEach ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-map.prototype.foreach
JS_DEFINE_NATIVE_FUNCTION(MapPrototype::for_each)
{
@ -108,6 +123,16 @@ JS_DEFINE_NATIVE_FUNCTION(MapPrototype::has)
return Value(entries.find(vm.argument(0)) != entries.end());
}
// 24.1.3.8 Map.prototype.keys ( ), https://tc39.es/ecma262/#sec-map.prototype.keys
JS_DEFINE_NATIVE_FUNCTION(MapPrototype::keys)
{
auto* map = typed_this(vm, global_object);
if (!map)
return {};
return MapIterator::create(global_object, *map, Object::PropertyKind::Key);
}
// 24.1.3.9 Map.prototype.set ( key, value ), https://tc39.es/ecma262/#sec-map.prototype.set
JS_DEFINE_NATIVE_FUNCTION(MapPrototype::set)
{
@ -121,6 +146,16 @@ JS_DEFINE_NATIVE_FUNCTION(MapPrototype::set)
return map;
}
// 24.1.3.11 Map.prototype.values ( ), https://tc39.es/ecma262/#sec-map.prototype.values
JS_DEFINE_NATIVE_FUNCTION(MapPrototype::values)
{
auto* map = typed_this(vm, global_object);
if (!map)
return {};
return MapIterator::create(global_object, *map, Object::PropertyKind::Value);
}
// 24.1.3.10 get Map.prototype.size, https://tc39.es/ecma262/#sec-get-map.prototype.size
JS_DEFINE_NATIVE_GETTER(MapPrototype::size_getter)
{

View file

@ -23,10 +23,13 @@ private:
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(get);
JS_DECLARE_NATIVE_FUNCTION(has);
JS_DECLARE_NATIVE_FUNCTION(keys);
JS_DECLARE_NATIVE_FUNCTION(set);
JS_DECLARE_NATIVE_FUNCTION(values);
JS_DECLARE_NATIVE_GETTER(size_getter);
};

View file

@ -0,0 +1,26 @@
test("length", () => {
expect(Map.prototype.entries.length).toBe(0);
});
test("basic functionality", () => {
const original = [
["a", 0],
["b", 1],
["c", 2],
];
const a = new Map(original);
const it = a.entries();
// FIXME: This test should be rewritten once we have proper iteration order
const first = it.next();
expect(first.done).toBeFalse();
expect(a.has(first.value[0])).toBeTrue();
const second = it.next();
expect(second.done).toBeFalse();
expect(a.has(second.value[0])).toBeTrue();
const third = it.next();
expect(third.done).toBeFalse();
expect(a.has(third.value[0])).toBeTrue();
expect(it.next()).toEqual({ value: undefined, done: true });
expect(it.next()).toEqual({ value: undefined, done: true });
expect(it.next()).toEqual({ value: undefined, done: true });
});

View file

@ -0,0 +1,26 @@
test("length", () => {
expect(Map.prototype.keys.length).toBe(0);
});
test("basic functionality", () => {
const original = [
["a", 0],
["b", 1],
["c", 2],
];
const a = new Map(original);
const it = a.keys();
// FIXME: This test should be rewritten once we have proper iteration order
const first = it.next();
expect(first.done).toBeFalse();
expect(a.has(first.value)).toBeTrue();
const second = it.next();
expect(second.done).toBeFalse();
expect(a.has(second.value)).toBeTrue();
const third = it.next();
expect(third.done).toBeFalse();
expect(a.has(third.value)).toBeTrue();
expect(it.next()).toEqual({ value: undefined, done: true });
expect(it.next()).toEqual({ value: undefined, done: true });
expect(it.next()).toEqual({ value: undefined, done: true });
});

View file

@ -0,0 +1,26 @@
test("length", () => {
expect(Map.prototype.values.length).toBe(0);
});
test("basic functionality", () => {
const original = [
["a", 0],
["b", 1],
["c", 2],
];
const a = new Map(original);
const it = a.values();
// FIXME: This test should be rewritten once we have proper iteration order
const first = it.next();
expect(first.done).toBeFalse();
expect([0, 1, 2].includes(first.value)).toBeTrue();
const second = it.next();
expect(second.done).toBeFalse();
expect([0, 1, 2].includes(second.value)).toBeTrue();
const third = it.next();
expect(third.done).toBeFalse();
expect([0, 1, 2].includes(third.value)).toBeTrue();
expect(it.next()).toEqual({ value: undefined, done: true });
expect(it.next()).toEqual({ value: undefined, done: true });
expect(it.next()).toEqual({ value: undefined, done: true });
});