Przeglądaj źródła

LibJS: Add Array.prototype.splice

Adds splice to Array.prototype according to the specification:
https://tc39.es/ecma262/#sec-array.prototype.splice
Luke 5 lat temu
rodzic
commit
f9f7cb4583

+ 101 - 0
Libraries/LibJS/Runtime/ArrayPrototype.cpp

@@ -67,6 +67,7 @@ ArrayPrototype::ArrayPrototype()
     put_native_function("findIndex", find_index, 1, attr);
     put_native_function("some", some, 1, attr);
     put_native_function("every", every, 1, attr);
+    put_native_function("splice", splice, 2, attr);
     put("length", Value(0), Attribute::Configurable);
 }
 
@@ -642,4 +643,104 @@ Value ArrayPrototype::every(Interpreter& interpreter)
     return Value(result);
 }
 
+Value ArrayPrototype::splice(Interpreter& interpreter)
+{
+    auto* this_object = interpreter.this_value().to_object(interpreter);
+    if (!this_object)
+        return {};
+
+    auto initial_length = get_length(interpreter, *this_object);
+    if (interpreter.exception())
+        return {};
+
+    auto relative_start = interpreter.argument(0).to_i32(interpreter);
+    if (interpreter.exception())
+        return {};
+
+    size_t actual_start;
+
+    if (relative_start < 0)
+        actual_start = max((ssize_t)initial_length + relative_start, (ssize_t)0);
+    else
+        actual_start = min((size_t)relative_start, initial_length);
+
+    size_t insert_count = 0;
+    size_t actual_delete_count = 0;
+
+    if (interpreter.argument_count() == 1) {
+        actual_delete_count = initial_length - actual_start;
+    } else if (interpreter.argument_count() >= 2) {
+        insert_count = interpreter.argument_count() - 2;
+        i32 delete_count = interpreter.argument(1).to_i32(interpreter);
+        if (interpreter.exception())
+            return {};
+
+        actual_delete_count = min((size_t)max(delete_count, 0), initial_length - actual_start);
+    }
+
+    size_t new_length = initial_length + insert_count - actual_delete_count;
+
+    if (new_length > MAX_ARRAY_LIKE_INDEX)
+        return interpreter.throw_exception<TypeError>("Maximum array size exceeded");
+
+    auto removed_elements = Array::create(interpreter.global_object());
+
+    for (size_t i = 0; i < actual_delete_count; ++i) {
+        auto value = this_object->get_by_index(actual_start + i);
+        if (interpreter.exception())
+            return {};
+
+        removed_elements->elements().append(value);
+    }
+
+    if (insert_count < actual_delete_count) {
+        for (size_t i = actual_start; i < initial_length - actual_delete_count; ++i) {
+            auto from = this_object->get_by_index(i + actual_delete_count);
+            if (interpreter.exception())
+                return {};
+
+            auto to = i + insert_count;
+
+            if (!from.is_empty()) {
+                this_object->put_by_index(to, from);
+                if (interpreter.exception())
+                    return {};
+            }
+            else
+                this_object->delete_property(PropertyName(to));
+        }
+
+        for (size_t i = initial_length; i > new_length; --i)
+            this_object->delete_property(PropertyName(i - 1));
+    } else if (insert_count > actual_delete_count) {
+        for (size_t i = initial_length - actual_delete_count; i > actual_start; --i) {
+            auto from = this_object->get_by_index(i + actual_delete_count - 1);
+            if (interpreter.exception())
+                return {};
+
+            auto to = i + insert_count - 1;
+
+            if (!from.is_empty()) {
+                this_object->put_by_index(to, from);
+                if (interpreter.exception())
+                    return {};
+            }
+            else
+                this_object->delete_property(PropertyName(to));
+        }
+    }
+
+    for (size_t i = 0; i < insert_count; ++i) {
+        this_object->put_by_index(actual_start + i, interpreter.argument(i + 2));
+        if (interpreter.exception())
+            return {};
+    }
+
+    this_object->put("length", Value((i32)new_length));
+    if (interpreter.exception())
+        return {};
+
+    return removed_elements;
+}
+
 }

+ 1 - 0
Libraries/LibJS/Runtime/ArrayPrototype.h

@@ -60,6 +60,7 @@ private:
     static Value find_index(Interpreter&);
     static Value some(Interpreter&);
     static Value every(Interpreter&);
+    static Value splice(Interpreter&);
 };
 
 }

+ 12 - 0
Libraries/LibJS/Tests/Array.prototype-generic-functions.js

@@ -27,6 +27,18 @@ try {
         assert(o.length === 0);
     });
 
+    {
+        const o = { length: 3, 0: "hello", 2: "serenity" };
+        const removed = Array.prototype.splice.call(o, 0, 2, "hello", "friends");
+        assert(o.length === 3);
+        assert(o[0] === "hello");
+        assert(o[1] === "friends");
+        assert(o[2] === "serenity");
+        assert(removed.length === 2);
+        assert(removed[0] === "hello");
+        assert(removed[1] === undefined);
+    }
+
     {
         assert(Array.prototype.join.call({}) === "");
         assert(Array.prototype.join.call({ length: "foo" }) === "");

+ 87 - 0
Libraries/LibJS/Tests/Array.prototype.splice.js

@@ -0,0 +1,87 @@
+load("test-common.js");
+
+try {
+    assert(Array.prototype.splice.length === 2);
+
+    var array = ["hello", "friends", "serenity", 1, 2];
+    var removed = array.splice(3);
+    assert(array.length === 3);
+    assert(array[0] === "hello");
+    assert(array[1] === "friends");
+    assert(array[2] === "serenity");
+    assert(removed.length === 2);
+    assert(removed[0] === 1);
+    assert(removed[1] === 2);
+
+    array = ["hello", "friends", "serenity", 1, 2];
+    removed = array.splice(-2);
+    assert(array.length === 3);
+    assert(array[0] === "hello");
+    assert(array[1] === "friends");
+    assert(array[2] === "serenity");
+    assert(removed.length === 2);
+    assert(removed[0] === 1);
+    assert(removed[1] === 2);
+
+    array = ["hello", "friends", "serenity", 1, 2];
+    removed = array.splice(-2, 1);
+    assert(array.length === 4);
+    assert(array[0] === "hello");
+    assert(array[1] === "friends");
+    assert(array[2] === "serenity");
+    assert(array[3] === 2);
+    assert(removed.length === 1);
+    assert(removed[0] === 1);
+
+    array = ["serenity"];
+    removed = array.splice(0, 0, "hello", "friends");
+    assert(array.length === 3);
+    assert(array[0] === "hello");
+    assert(array[1] === "friends");
+    assert(array[2] === "serenity");
+    assert(removed.length === 0);
+
+    array = ["goodbye", "friends", "serenity"];
+    removed = array.splice(0, 1, "hello");
+    assert(array.length === 3);
+    assert(array[0] === "hello");
+    assert(array[1] === "friends");
+    assert(array[2] === "serenity");
+    assert(removed.length === 1);
+    assert(removed[0] === "goodbye");
+
+    array = ["foo", "bar", "baz"];
+    removed = array.splice();
+    assert(array.length === 3);
+    assert(array[0] === "foo");
+    assert(array[1] === "bar");
+    assert(array[2] === "baz");
+    assert(removed.length === 0);
+
+    removed = array.splice(0, 123);
+    assert(array.length === 0);
+    assert(removed.length === 3);
+    assert(removed[0] === "foo");
+    assert(removed[1] === "bar");
+    assert(removed[2] === "baz");
+
+    array = ["foo", "bar", "baz"];
+    removed = array.splice(123, 123);
+    assert(array.length === 3);
+    assert(array[0] === "foo");
+    assert(array[1] === "bar");
+    assert(array[2] === "baz");
+    assert(removed.length === 0);
+
+    array = ["foo", "bar", "baz"];
+    removed = array.splice(-123, 123);
+    assert(array.length === 0);
+    assert(removed.length === 3);
+    assert(removed[0] === "foo");
+    assert(removed[1] === "bar");
+    assert(removed[2] === "baz");
+
+    console.log("PASS");
+} catch (e) {
+    console.log("FAIL: " + e);
+}