浏览代码

LibJS: Add the Set built-in object

Idan Horowitz 4 年之前
父节点
当前提交
670be04c81

+ 30 - 27
Userland/Libraries/LibJS/CMakeLists.txt

@@ -1,31 +1,31 @@
 set(SOURCES
-        AST.cpp
-        Bytecode/ASTCodegen.cpp
-        Bytecode/BasicBlock.cpp
-        Bytecode/Generator.cpp
-        Bytecode/Instruction.cpp
-        Bytecode/Interpreter.cpp
-        Bytecode/Op.cpp
-        Console.cpp
-        Heap/CellAllocator.cpp
-        Heap/BlockAllocator.cpp
-        Heap/Handle.cpp
-        Heap/HeapBlock.cpp
-        Heap/Heap.cpp
-        Interpreter.cpp
-        Lexer.cpp
-        MarkupGenerator.cpp
-        Parser.cpp
-        Runtime/Array.cpp
-        Runtime/ArrayBuffer.cpp
-        Runtime/ArrayBufferConstructor.cpp
-        Runtime/ArrayBufferPrototype.cpp
-        Runtime/ArrayConstructor.cpp
-        Runtime/ArrayIterator.cpp
-        Runtime/ArrayIteratorPrototype.cpp
-        Runtime/ArrayPrototype.cpp
-        Runtime/BigInt.cpp
-        Runtime/BigIntConstructor.cpp
+    AST.cpp
+    Bytecode/ASTCodegen.cpp
+    Bytecode/BasicBlock.cpp
+    Bytecode/Generator.cpp
+    Bytecode/Instruction.cpp
+    Bytecode/Interpreter.cpp
+    Bytecode/Op.cpp
+    Console.cpp
+    Heap/CellAllocator.cpp
+    Heap/BlockAllocator.cpp
+    Heap/Handle.cpp
+    Heap/HeapBlock.cpp
+    Heap/Heap.cpp
+    Interpreter.cpp
+    Lexer.cpp
+    MarkupGenerator.cpp
+    Parser.cpp
+    Runtime/Array.cpp
+    Runtime/ArrayBuffer.cpp
+    Runtime/ArrayBufferConstructor.cpp
+    Runtime/ArrayBufferPrototype.cpp
+    Runtime/ArrayConstructor.cpp
+    Runtime/ArrayIterator.cpp
+    Runtime/ArrayIteratorPrototype.cpp
+    Runtime/ArrayPrototype.cpp
+    Runtime/BigInt.cpp
+    Runtime/BigIntConstructor.cpp
     Runtime/BigIntObject.cpp
     Runtime/BigIntPrototype.cpp
     Runtime/BooleanConstructor.cpp
@@ -76,6 +76,9 @@ set(SOURCES
     Runtime/RegExpPrototype.cpp
     Runtime/ScopeObject.cpp
     Runtime/ScriptFunction.cpp
+    Runtime/Set.cpp
+    Runtime/SetConstructor.cpp
+    Runtime/SetPrototype.cpp
     Runtime/Shape.cpp
     Runtime/StringConstructor.cpp
     Runtime/StringIterator.cpp

+ 1 - 0
Userland/Libraries/LibJS/Forward.h

@@ -37,6 +37,7 @@
     __JS_ENUMERATE(Object, object, ObjectPrototype, ObjectConstructor, void)                      \
     __JS_ENUMERATE(Promise, promise, PromisePrototype, PromiseConstructor, void)                  \
     __JS_ENUMERATE(RegExpObject, regexp, RegExpPrototype, RegExpConstructor, void)                \
+    __JS_ENUMERATE(Set, set, SetPrototype, SetConstructor, void)                                  \
     __JS_ENUMERATE(StringObject, string, StringPrototype, StringConstructor, void)                \
     __JS_ENUMERATE(SymbolObject, symbol, SymbolPrototype, SymbolConstructor, void)
 

+ 1 - 0
Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h

@@ -239,6 +239,7 @@ namespace JS {
     P(sign)                                  \
     P(sin)                                   \
     P(sinh)                                  \
+    P(size)                                  \
     P(slice)                                 \
     P(small)                                 \
     P(some)                                  \

+ 4 - 1
Userland/Libraries/LibJS/Runtime/GlobalObject.cpp

@@ -47,6 +47,8 @@
 #include <LibJS/Runtime/ReflectObject.h>
 #include <LibJS/Runtime/RegExpConstructor.h>
 #include <LibJS/Runtime/RegExpPrototype.h>
+#include <LibJS/Runtime/SetConstructor.h>
+#include <LibJS/Runtime/SetPrototype.h>
 #include <LibJS/Runtime/Shape.h>
 #include <LibJS/Runtime/StringConstructor.h>
 #include <LibJS/Runtime/StringIteratorPrototype.h>
@@ -87,7 +89,7 @@ void GlobalObject::initialize_global_object()
     static_cast<FunctionPrototype*>(m_function_prototype)->initialize(*this);
     static_cast<ObjectPrototype*>(m_object_prototype)->initialize(*this);
 
-    set_prototype(m_object_prototype);
+    Object::set_prototype(m_object_prototype);
 
 #define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
     if (!m_##snake_name##_prototype)                                                     \
@@ -137,6 +139,7 @@ void GlobalObject::initialize_global_object()
     add_constructor(vm.names.Promise, m_promise_constructor, m_promise_prototype);
     add_constructor(vm.names.Proxy, m_proxy_constructor, nullptr);
     add_constructor(vm.names.RegExp, m_regexp_constructor, m_regexp_prototype);
+    add_constructor(vm.names.Set, m_set_constructor, m_set_prototype);
     add_constructor(vm.names.String, m_string_constructor, m_string_prototype);
     add_constructor(vm.names.Symbol, m_symbol_constructor, m_symbol_prototype);
 

+ 37 - 0
Userland/Libraries/LibJS/Runtime/Set.cpp

@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Runtime/Set.h>
+
+namespace JS {
+
+Set* Set::create(GlobalObject& global_object)
+{
+    return global_object.heap().allocate<Set>(global_object, *global_object.set_prototype());
+}
+
+Set::Set(Object& prototype)
+    : Object(prototype)
+{
+}
+
+Set::~Set()
+{
+}
+
+Set* Set::typed_this(VM& vm, GlobalObject& global_object)
+{
+    auto* this_object = vm.this_value(global_object).to_object(global_object);
+    if (!this_object)
+        return {};
+    if (!is<Set>(this_object)) {
+        vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "Set");
+        return nullptr;
+    }
+    return static_cast<Set*>(this_object);
+}
+
+}

+ 53 - 0
Userland/Libraries/LibJS/Runtime/Set.h

@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/HashTable.h>
+#include <LibJS/Runtime/BigInt.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/Object.h>
+#include <LibJS/Runtime/Value.h>
+
+namespace JS {
+
+struct ValueTraits : public Traits<Value> {
+    static unsigned hash(Value value)
+    {
+        VERIFY(!value.is_empty());
+        if (value.is_string())
+            return value.as_string().string().hash();
+
+        if (value.is_bigint())
+            return value.as_bigint().big_integer().hash();
+
+        return u64_hash(value.encoded()); // FIXME: Is this the best way to hash pointers, doubles & ints?
+    }
+    static bool equals(const Value a, const Value b)
+    {
+        return same_value_zero(a, b);
+    }
+};
+
+class Set : public Object {
+    JS_OBJECT(Set, Object);
+
+public:
+    static Set* create(GlobalObject&);
+
+    explicit Set(Object& prototype);
+    virtual ~Set() override;
+
+    static Set* typed_this(VM&, GlobalObject&);
+
+    HashTable<Value, ValueTraits> const& values() const { return m_values; };
+    HashTable<Value, ValueTraits>& values() { return m_values; };
+
+private:
+    HashTable<Value, ValueTraits> m_values; // FIXME: Replace with a HashTable that maintains a linked list of insertion order for correct iteration order
+};
+
+}

+ 71 - 0
Userland/Libraries/LibJS/Runtime/SetConstructor.cpp

@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Runtime/Error.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/IteratorOperations.h>
+#include <LibJS/Runtime/Set.h>
+#include <LibJS/Runtime/SetConstructor.h>
+
+namespace JS {
+
+SetConstructor::SetConstructor(GlobalObject& global_object)
+    : NativeFunction(vm().names.Set, *global_object.function_prototype())
+{
+}
+
+void SetConstructor::initialize(GlobalObject& global_object)
+{
+    auto& vm = this->vm();
+    NativeFunction::initialize(global_object);
+    define_property(vm.names.prototype, global_object.set_prototype(), 0);
+    define_property(vm.names.length, Value(0), Attribute::Configurable);
+
+    define_native_property(vm.well_known_symbol_species(), symbol_species_getter, {}, Attribute::Configurable);
+}
+
+SetConstructor::~SetConstructor()
+{
+}
+
+Value SetConstructor::call()
+{
+    auto& vm = this->vm();
+    vm.throw_exception<TypeError>(global_object(), ErrorType::ConstructorWithoutNew, vm.names.Set);
+    return {};
+}
+
+Value SetConstructor::construct(Function&)
+{
+    auto& vm = this->vm();
+    if (vm.argument(0).is_nullish())
+        return Set::create(global_object());
+
+    auto* set = Set::create(global_object());
+    auto adder = set->get(vm.names.add);
+    if (vm.exception())
+        return {};
+    if (!adder.is_function()) {
+        vm.throw_exception<TypeError>(global_object(), ErrorType::NotAFunction, "'add' property of Set");
+        return {};
+    }
+    get_iterator_values(global_object(), vm.argument(0), [&](Value iterator_value) {
+        if (vm.exception())
+            return IterationDecision::Break;
+        (void)vm.call(adder.as_function(), Value(set), iterator_value);
+        return vm.exception() ? IterationDecision::Break : IterationDecision::Continue;
+    });
+    if (vm.exception())
+        return {};
+    return set;
+}
+
+JS_DEFINE_NATIVE_GETTER(SetConstructor::symbol_species_getter)
+{
+    return vm.this_value(global_object);
+}
+
+}

+ 30 - 0
Userland/Libraries/LibJS/Runtime/SetConstructor.h

@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace JS {
+
+class SetConstructor final : public NativeFunction {
+    JS_OBJECT(SetConstructor, NativeFunction);
+
+public:
+    explicit SetConstructor(GlobalObject&);
+    virtual void initialize(GlobalObject&) override;
+    virtual ~SetConstructor() override;
+
+    virtual Value call() override;
+    virtual Value construct(Function&) override;
+
+private:
+    virtual bool has_constructor() const override { return true; }
+
+    JS_DECLARE_NATIVE_GETTER(symbol_species_getter);
+};
+
+}

+ 40 - 0
Userland/Libraries/LibJS/Runtime/SetPrototype.cpp

@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/HashTable.h>
+#include <LibJS/Runtime/SetPrototype.h>
+
+namespace JS {
+
+SetPrototype::SetPrototype(GlobalObject& global_object)
+    : Set(*global_object.object_prototype())
+{
+}
+
+void SetPrototype::initialize(GlobalObject& global_object)
+{
+    auto& vm = this->vm();
+    Set::initialize(global_object);
+    u8 attr = Attribute::Writable | Attribute::Configurable;
+
+    define_native_property(vm.names.size, size_getter, {}, attr);
+
+    define_property(vm.well_known_symbol_to_string_tag(), js_string(global_object.heap(), vm.names.Set), Attribute::Configurable);
+}
+
+SetPrototype::~SetPrototype()
+{
+}
+
+JS_DEFINE_NATIVE_GETTER(SetPrototype::size_getter)
+{
+    auto* set = typed_this(vm, global_object);
+    if (!set)
+        return {};
+    return Value(set->values().size());
+}
+
+}

+ 25 - 0
Userland/Libraries/LibJS/Runtime/SetPrototype.h

@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Set.h>
+
+namespace JS {
+
+class SetPrototype final : public Set {
+    JS_OBJECT(SetPrototype, Set);
+
+public:
+    SetPrototype(GlobalObject&);
+    virtual void initialize(GlobalObject&) override;
+    virtual ~SetPrototype() override;
+
+private:
+    JS_DECLARE_NATIVE_GETTER(size_getter);
+};
+
+}

+ 5 - 1
Userland/Libraries/LibJS/Runtime/Value.h

@@ -253,6 +253,8 @@ public:
     i32 as_i32() const;
     u32 as_u32() const;
 
+    u64 encoded() const { return m_value.encoded; }
+
     String to_string(GlobalObject&, bool legacy_null_to_empty_string = false) const;
     PrimitiveString* to_primitive_string(GlobalObject&);
     Value to_primitive(GlobalObject&, PreferredType preferred_type = PreferredType::Default) const;
@@ -301,7 +303,9 @@ private:
         Accessor* as_accessor;
         BigInt* as_bigint;
         NativeProperty* as_native_property;
-    } m_value;
+
+        u64 encoded;
+    } m_value { .encoded = 0 };
 };
 
 inline Value js_undefined()

+ 31 - 0
Userland/Libraries/LibJS/Tests/builtins/Set/Set.js

@@ -0,0 +1,31 @@
+test("constructor properties", () => {
+    expect(Set).toHaveLength(0);
+    expect(Set.name).toBe("Set");
+});
+
+describe("errors", () => {
+    test("invalid array iterators", () => {
+        [-100, Infinity, NaN, {}, 152n].forEach(value => {
+            expect(() => {
+                new Set(value);
+            }).toThrowWithMessage(TypeError, "is not iterable");
+        });
+    });
+    test("called without new", () => {
+        expect(() => {
+            Set();
+        }).toThrowWithMessage(TypeError, "Set constructor must be called with 'new'");
+    });
+});
+
+describe("normal behavior", () => {
+    test("typeof", () => {
+        expect(typeof new Set()).toBe("object");
+    });
+
+    test("constructor with single array argument", () => {
+        var a = new Set([0, 1, 2]);
+        expect(a instanceof Set).toBeTrue();
+        expect(a).toHaveSize(3);
+    });
+});

+ 11 - 0
Userland/Libraries/LibJS/Tests/test-common.js

@@ -97,6 +97,17 @@ class ExpectationError extends Error {
             });
         }
 
+        toHaveSize(size) {
+            this.__expect(
+                typeof this.target.size === "number",
+                () => "toHaveSize: target.size not of type number"
+            );
+
+            this.__doMatcher(() => {
+                this.__expect(Object.is(this.target.size, size));
+            });
+        }
+
         toHaveProperty(property, value) {
             this.__doMatcher(() => {
                 let object = this.target;

+ 1 - 1
Userland/Libraries/LibWeb/Bindings/WindowObject.cpp

@@ -43,7 +43,7 @@ void WindowObject::initialize_global_object()
 {
     Base::initialize_global_object();
 
-    set_prototype(&ensure_web_prototype<EventTargetPrototype>("EventTarget"));
+    Object::set_prototype(&ensure_web_prototype<EventTargetPrototype>("EventTarget"));
 
     define_property("window", this, JS::Attribute::Enumerable);
     define_property("frames", this, JS::Attribute::Enumerable);

+ 19 - 0
Userland/Utilities/js.cpp

@@ -35,6 +35,7 @@
 #include <LibJS/Runtime/ProxyObject.h>
 #include <LibJS/Runtime/RegExpObject.h>
 #include <LibJS/Runtime/ScriptFunction.h>
+#include <LibJS/Runtime/Set.h>
 #include <LibJS/Runtime/Shape.h>
 #include <LibJS/Runtime/StringObject.h>
 #include <LibJS/Runtime/TypedArray.h>
@@ -277,6 +278,22 @@ static void print_proxy_object(const JS::Object& object, HashTable<JS::Object*>&
     print_value(&proxy_object.handler(), seen_objects);
 }
 
+static void print_set(const JS::Object& object, HashTable<JS::Object*>& seen_objects)
+{
+    auto& set = static_cast<const JS::Set&>(object);
+    auto& values = set.values();
+    print_type("Set");
+    out(" {{");
+    bool first = true;
+    for (auto& value : values) {
+        print_separator(first);
+        print_value(value, seen_objects);
+    }
+    if (!first)
+        out(" ");
+    out("}}");
+}
+
 static void print_promise(const JS::Object& object, HashTable<JS::Object*>& seen_objects)
 {
     auto& promise = static_cast<const JS::Promise&>(object);
@@ -398,6 +415,8 @@ static void print_value(JS::Value value, HashTable<JS::Object*>& seen_objects)
             return print_error(object, seen_objects);
         if (is<JS::RegExpObject>(object))
             return print_regexp_object(object, seen_objects);
+        if (is<JS::Set>(object))
+            return print_set(object, seen_objects);
         if (is<JS::ProxyObject>(object))
             return print_proxy_object(object, seen_objects);
         if (is<JS::Promise>(object))