diff options
-rw-r--r-- | Libraries/LibJS/CMakeLists.txt | 4 | ||||
-rw-r--r-- | Libraries/LibJS/Forward.h | 4 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ArrayIterator.cpp | 49 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ArrayIterator.h | 56 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ArrayIteratorPrototype.cpp | 92 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ArrayIteratorPrototype.h | 45 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ArrayPrototype.cpp | 17 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ArrayPrototype.h | 1 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/GlobalObject.cpp | 9 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/GlobalObject.h | 11 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/IteratorOperations.cpp | 120 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/IteratorOperations.h | 45 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/IteratorPrototype.cpp | 55 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/IteratorPrototype.h | 45 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/Object.h | 1 | ||||
-rw-r--r-- | Libraries/LibJS/Tests/builtins/Array/Array.prototype.values.js | 44 | ||||
-rw-r--r-- | Libraries/LibJS/Tests/iterators/%IteratorPrototype%.js | 12 | ||||
-rw-r--r-- | Libraries/LibJS/Tests/iterators/array-iterator.js | 48 |
18 files changed, 657 insertions, 1 deletions
diff --git a/Libraries/LibJS/CMakeLists.txt b/Libraries/LibJS/CMakeLists.txt index af35aa7a84..4b7f4d2af0 100644 --- a/Libraries/LibJS/CMakeLists.txt +++ b/Libraries/LibJS/CMakeLists.txt @@ -10,6 +10,8 @@ set(SOURCES Parser.cpp Runtime/Array.cpp Runtime/ArrayConstructor.cpp + Runtime/ArrayIterator.cpp + Runtime/ArrayIteratorPrototype.cpp Runtime/ArrayPrototype.cpp Runtime/BigInt.cpp Runtime/BigIntConstructor.cpp @@ -34,6 +36,8 @@ set(SOURCES Runtime/FunctionPrototype.cpp Runtime/GlobalObject.cpp Runtime/IndexedProperties.cpp + Runtime/IteratorOperations.cpp + Runtime/IteratorPrototype.cpp Runtime/JSONObject.cpp Runtime/LexicalEnvironment.cpp Runtime/MarkedValueList.cpp diff --git a/Libraries/LibJS/Forward.h b/Libraries/LibJS/Forward.h index 833206e001..1883c67417 100644 --- a/Libraries/LibJS/Forward.h +++ b/Libraries/LibJS/Forward.h @@ -67,6 +67,10 @@ __JS_ENUMERATE(TypeError, type_error, TypeErrorPrototype, TypeErrorConstructor) \ __JS_ENUMERATE(URIError, uri_error, URIErrorPrototype, URIErrorConstructor) +#define JS_ENUMERATE_ITERATOR_PROTOTYPES \ + __JS_ENUMERATE(Iterator, iterator) \ + __JS_ENUMERATE(ArrayIterator, array_iterator) + #define JS_ENUMERATE_BUILTIN_TYPES \ JS_ENUMERATE_NATIVE_OBJECTS \ JS_ENUMERATE_ERROR_SUBCLASSES diff --git a/Libraries/LibJS/Runtime/ArrayIterator.cpp b/Libraries/LibJS/Runtime/ArrayIterator.cpp new file mode 100644 index 0000000000..6c01af00ab --- /dev/null +++ b/Libraries/LibJS/Runtime/ArrayIterator.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <LibJS/Interpreter.h> +#include <LibJS/Runtime/ArrayIterator.h> +#include <LibJS/Runtime/GlobalObject.h> + +namespace JS { + +ArrayIterator* ArrayIterator::create(GlobalObject& global_object, Value array, Object::PropertyKind iteration_kind) +{ + return global_object.heap().allocate<ArrayIterator>(global_object, *global_object.array_iterator_prototype(), array, iteration_kind); +} + +ArrayIterator::ArrayIterator(Object& prototype, Value array, Object::PropertyKind iteration_kind) + : Object(prototype) + , m_array(array) + , m_iteration_kind(iteration_kind) +{ +} + +ArrayIterator::~ArrayIterator() +{ +} + +} diff --git a/Libraries/LibJS/Runtime/ArrayIterator.h b/Libraries/LibJS/Runtime/ArrayIterator.h new file mode 100644 index 0000000000..408349f188 --- /dev/null +++ b/Libraries/LibJS/Runtime/ArrayIterator.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <LibJS/Runtime/Object.h> + +namespace JS { + +class ArrayIterator final : public Object { + JS_OBJECT(ArrayIterator, Object); + +public: + static ArrayIterator* create(GlobalObject&, Value array, Object::PropertyKind iteration_kind); + + explicit ArrayIterator(Object& prototype, Value array, Object::PropertyKind iteration_kind); + virtual ~ArrayIterator() override; + + Value array() const { return m_array; } + Object::PropertyKind iteration_kind() const { return m_iteration_kind; } + size_t index() const { return m_index; } + +private: + friend class ArrayIteratorPrototype; + + virtual bool is_array_iterator_object() const override { return true; } + + Value m_array; + Object::PropertyKind m_iteration_kind; + size_t m_index { 0 }; +}; + +} diff --git a/Libraries/LibJS/Runtime/ArrayIteratorPrototype.cpp b/Libraries/LibJS/Runtime/ArrayIteratorPrototype.cpp new file mode 100644 index 0000000000..e5e381d34d --- /dev/null +++ b/Libraries/LibJS/Runtime/ArrayIteratorPrototype.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <LibJS/Runtime/Array.h> +#include <LibJS/Runtime/ArrayIterator.h> +#include <LibJS/Runtime/ArrayIteratorPrototype.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/IteratorOperations.h> + +namespace JS { + +ArrayIteratorPrototype::ArrayIteratorPrototype(GlobalObject& global_object) + : Object(*global_object.iterator_prototype()) +{ +} + +void ArrayIteratorPrototype::initialize(Interpreter& interpreter, GlobalObject& global_object) +{ + Object::initialize(interpreter, global_object); + define_native_function("next", next, 0, Attribute::Writable | Attribute::Configurable); +} + +ArrayIteratorPrototype::~ArrayIteratorPrototype() +{ +} + +JS_DEFINE_NATIVE_FUNCTION(ArrayIteratorPrototype::next) +{ + auto this_value = interpreter.this_value(global_object); + if (!this_value.is_object() || !this_value.as_object().is_array_iterator_object()) + return interpreter.throw_exception<TypeError>(ErrorType::NotAn, "Array Iterator"); + + auto& this_object = this_value.as_object(); + + auto& iterator = static_cast<ArrayIterator&>(this_object); + auto target_array = iterator.array(); + if (target_array.is_undefined()) + return create_iterator_result_object(interpreter, global_object, js_undefined(), true); + ASSERT(target_array.is_object()); + auto& array = target_array.as_object(); + + auto index = iterator.index(); + auto iteration_kind = iterator.iteration_kind(); + // FIXME: Typed array check + auto length = array.indexed_properties().array_like_size(); + + if (index >= length) { + iterator.m_array = js_undefined(); + return create_iterator_result_object(interpreter, global_object, js_undefined(), true); + } + + iterator.m_index++; + if (iteration_kind == Object::PropertyKind::Key) + return create_iterator_result_object(interpreter, global_object, Value(static_cast<i32>(index)), false); + + auto value = array.get(index); + if (interpreter.exception()) + return {}; + if (iteration_kind == Object::PropertyKind::Value) + return create_iterator_result_object(interpreter, global_object, value, false); + + auto* entry_array = Array::create(global_object); + entry_array->define_property(0, Value(static_cast<i32>(index))); + entry_array->define_property(1, value); + return create_iterator_result_object(interpreter, global_object, entry_array, false); +} + +} diff --git a/Libraries/LibJS/Runtime/ArrayIteratorPrototype.h b/Libraries/LibJS/Runtime/ArrayIteratorPrototype.h new file mode 100644 index 0000000000..a04c9aae08 --- /dev/null +++ b/Libraries/LibJS/Runtime/ArrayIteratorPrototype.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <LibJS/Runtime/Object.h> + +namespace JS { + +class ArrayIteratorPrototype final : public Object { + JS_OBJECT(ArrayIteratorPrototype, Object) + +public: + ArrayIteratorPrototype(GlobalObject&); + virtual void initialize(Interpreter&, GlobalObject&) override; + virtual ~ArrayIteratorPrototype() override; + +private: + JS_DECLARE_NATIVE_FUNCTION(next); +}; + +} diff --git a/Libraries/LibJS/Runtime/ArrayPrototype.cpp b/Libraries/LibJS/Runtime/ArrayPrototype.cpp index 7c376b56fa..2a3200690d 100644 --- a/Libraries/LibJS/Runtime/ArrayPrototype.cpp +++ b/Libraries/LibJS/Runtime/ArrayPrototype.cpp @@ -28,9 +28,9 @@ #include <AK/Function.h> #include <AK/StringBuilder.h> -#include <LibJS/Heap/Heap.h> #include <LibJS/Interpreter.h> #include <LibJS/Runtime/Array.h> +#include <LibJS/Runtime/ArrayIterator.h> #include <LibJS/Runtime/ArrayPrototype.h> #include <LibJS/Runtime/Error.h> #include <LibJS/Runtime/Function.h> @@ -75,7 +75,13 @@ void ArrayPrototype::initialize(Interpreter& interpreter, GlobalObject& global_o define_native_function("every", every, 1, attr); define_native_function("splice", splice, 2, attr); define_native_function("fill", fill, 1, attr); + define_native_function("values", values, 0, attr); define_property("length", Value(0), Attribute::Configurable); + + // Use define_property here instead of define_native_function so that + // Object.is(Array.prototype[Symbol.iterator], Array.prototype.values) + // evaluates to true + define_property(interpreter.get_well_known_symbol("iterator"), get("values"), attr); } ArrayPrototype::~ArrayPrototype() @@ -850,4 +856,13 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::fill) return this_object; } +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::values) +{ + auto* this_object = interpreter.this_value(global_object).to_object(interpreter, global_object); + if (!this_object) + return {}; + + return ArrayIterator::create(global_object, this_object, Object::PropertyKind::Value); +} + } diff --git a/Libraries/LibJS/Runtime/ArrayPrototype.h b/Libraries/LibJS/Runtime/ArrayPrototype.h index 989603dafa..a286725136 100644 --- a/Libraries/LibJS/Runtime/ArrayPrototype.h +++ b/Libraries/LibJS/Runtime/ArrayPrototype.h @@ -64,6 +64,7 @@ private: JS_DECLARE_NATIVE_FUNCTION(every); JS_DECLARE_NATIVE_FUNCTION(splice); JS_DECLARE_NATIVE_FUNCTION(fill); + JS_DECLARE_NATIVE_FUNCTION(values); }; } diff --git a/Libraries/LibJS/Runtime/GlobalObject.cpp b/Libraries/LibJS/Runtime/GlobalObject.cpp index 9dd2b4e9d7..6096eb1eb7 100644 --- a/Libraries/LibJS/Runtime/GlobalObject.cpp +++ b/Libraries/LibJS/Runtime/GlobalObject.cpp @@ -28,6 +28,7 @@ #include <AK/LogStream.h> #include <LibJS/Interpreter.h> #include <LibJS/Runtime/ArrayConstructor.h> +#include <LibJS/Runtime/ArrayIteratorPrototype.h> #include <LibJS/Runtime/ArrayPrototype.h> #include <LibJS/Runtime/BigIntConstructor.h> #include <LibJS/Runtime/BigIntPrototype.h> @@ -41,6 +42,7 @@ #include <LibJS/Runtime/FunctionConstructor.h> #include <LibJS/Runtime/FunctionPrototype.h> #include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/IteratorPrototype.h> #include <LibJS/Runtime/JSONObject.h> #include <LibJS/Runtime/MathObject.h> #include <LibJS/Runtime/NativeFunction.h> @@ -84,6 +86,13 @@ void GlobalObject::initialize() JS_ENUMERATE_BUILTIN_TYPES #undef __JS_ENUMERATE +#define __JS_ENUMERATE(ClassName, snake_name) \ + if (!m_##snake_name##_prototype) \ + m_##snake_name##_prototype = heap().allocate<ClassName##Prototype>(*this, *this); + JS_ENUMERATE_ITERATOR_PROTOTYPES +#undef __JS_ENUMERATE + + u8 attr = Attribute::Writable | Attribute::Configurable; define_native_function("gc", gc, 0, attr); define_native_function("isNaN", is_nan, 1, attr); diff --git a/Libraries/LibJS/Runtime/GlobalObject.h b/Libraries/LibJS/Runtime/GlobalObject.h index 862522a0f1..36472a7f2f 100644 --- a/Libraries/LibJS/Runtime/GlobalObject.h +++ b/Libraries/LibJS/Runtime/GlobalObject.h @@ -49,6 +49,11 @@ public: JS_ENUMERATE_BUILTIN_TYPES #undef __JS_ENUMERATE +#define __JS_ENUMERATE(ClassName, snake_name) \ + Object* snake_name##_prototype() { return m_##snake_name##_prototype; } + JS_ENUMERATE_ITERATOR_PROTOTYPES +#undef __JS_ENUMERATE + protected: virtual void visit_children(Visitor&) override; @@ -68,6 +73,12 @@ private: Object* m_##snake_name##_prototype { nullptr }; JS_ENUMERATE_BUILTIN_TYPES #undef __JS_ENUMERATE + +#define __JS_ENUMERATE(ClassName, snake_name) \ + Object* m_##snake_name##_prototype { nullptr }; + JS_ENUMERATE_ITERATOR_PROTOTYPES +#undef __JS_ENUMERATE + }; template<typename ConstructorType> diff --git a/Libraries/LibJS/Runtime/IteratorOperations.cpp b/Libraries/LibJS/Runtime/IteratorOperations.cpp new file mode 100644 index 0000000000..3bee12548b --- /dev/null +++ b/Libraries/LibJS/Runtime/IteratorOperations.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <LibJS/Interpreter.h> +#include <LibJS/Runtime/IteratorOperations.h> + +namespace JS { + +Object* get_iterator(Object& obj, String hint, Value method) +{ + auto& interpreter = obj.interpreter(); + ASSERT(hint == "sync" || hint == "async"); + if (method.is_empty()) { + if (hint == "async") + TODO(); + method = obj.get(obj.interpreter().get_well_known_symbol("iterator")); + if (interpreter.exception()) + return {}; + } + if (!method.is_function()) + TODO(); + auto iterator = interpreter.call(method.as_function(), &obj); + if (interpreter.exception()) + return {}; + if (!iterator.is_object()) + TODO(); + return &iterator.as_object(); +} + +Value iterator_next(Object& iterator, Value value) +{ + auto& interpreter = iterator.interpreter(); + auto next_method = iterator.get("next"); + if (interpreter.exception()) + return {}; + + ASSERT(next_method.is_function()); + + Value result; + if (value.is_empty()) { + result = interpreter.call(next_method.as_function(), &iterator); + } else { + MarkedValueList arguments(iterator.heap()); + arguments.append(value); + result = interpreter.call(next_method.as_function(), &iterator, move(arguments)); + } + + if (interpreter.exception()) + return {}; + if (!result.is_object()) + TODO(); + + return result; +} + +bool is_iterator_complete(Object& iterator_result) +{ + auto done = iterator_result.get("done"); + if (iterator_result.interpreter().exception()) + return false; + return done.to_boolean(); +} + +Value iterator_value(Object& iterator_result) +{ + return iterator_result.get("value"); +} + +Value iterator_step(Object& iterator) +{ + auto& interpreter = iterator.interpreter(); + auto result = iterator_next(iterator); + if (interpreter.exception()) + return {}; + auto done = is_iterator_complete(result.as_object()); + if (interpreter.exception()) + return {}; + if (done) + return Value(false); + return result; +} + +void iterator_close(Object& iterator) +{ + (void)iterator; + TODO(); +} + +Value create_iterator_result_object(Interpreter& interpreter, GlobalObject& global_object, Value value, bool done) +{ + auto* object = Object::create_empty(interpreter, global_object); + object->put("value", value); + object->put("done", Value(done)); + return object; +} + +} diff --git a/Libraries/LibJS/Runtime/IteratorOperations.h b/Libraries/LibJS/Runtime/IteratorOperations.h new file mode 100644 index 0000000000..9b9caae4e9 --- /dev/null +++ b/Libraries/LibJS/Runtime/IteratorOperations.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <LibJS/Runtime/Object.h> + +namespace JS { + +// Common iterator operations defined in ECMA262 7.4 +// https://tc39.es/ecma262/#sec-operations-on-iterator-objects + +Object* get_iterator(Object& obj, String hint = "sync", Value method = {}); +bool is_iterator_complete(Object& iterator_result); +Value create_iterator_result_object(Interpreter&, GlobalObject&, Value value, bool done); + +Value iterator_next(Object& iterator, Value value = {}); +Value iterator_value(Object& iterator_result); +Value iterator_step(Object& iterator); +void iterator_close(Object& iterator); + +} diff --git a/Libraries/LibJS/Runtime/IteratorPrototype.cpp b/Libraries/LibJS/Runtime/IteratorPrototype.cpp new file mode 100644 index 0000000000..e1fe68e125 --- /dev/null +++ b/Libraries/LibJS/Runtime/IteratorPrototype.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/IteratorPrototype.h> + +namespace JS { + +IteratorPrototype::IteratorPrototype(GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void IteratorPrototype::initialize(Interpreter& interpreter, GlobalObject& global_object) +{ + Object::initialize(interpreter, global_object); + define_native_function(interpreter.get_well_known_symbol("iterator"), symbol_iterator, 0, Attribute::Writable | Attribute::Enumerable); +} + +IteratorPrototype::~IteratorPrototype() +{ +} + +JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::symbol_iterator) +{ + auto* this_object = interpreter.this_value(global_object).to_object(interpreter, global_object); + if (!this_object) + return {}; + return this_object; +} + +} diff --git a/Libraries/LibJS/Runtime/IteratorPrototype.h b/Libraries/LibJS/Runtime/IteratorPrototype.h new file mode 100644 index 0000000000..1cfd954632 --- /dev/null +++ b/Libraries/LibJS/Runtime/IteratorPrototype.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <LibJS/Runtime/Object.h> + +namespace JS { + +class IteratorPrototype : public Object { + JS_OBJECT(IteratorPrototype, Object) + +public: + IteratorPrototype(GlobalObject&); + virtual void initialize(Interpreter&, GlobalObject&) override; + virtual ~IteratorPrototype() override; + +private: + JS_DECLARE_NATIVE_FUNCTION(symbol_iterator); +}; + +} diff --git a/Libraries/LibJS/Runtime/Object.h b/Libraries/LibJS/Runtime/Object.h index 507795ce1e..ff325b845b 100644 --- a/Libraries/LibJS/Runtime/Object.h +++ b/Libraries/LibJS/Runtime/Object.h @@ -123,6 +123,7 @@ public: virtual bool is_number_object() const { return false; } virtual bool is_symbol_object() const { return false; } virtual bool is_bigint_object() const { return false; } + virtual bool is_array_iterator_object() const { return false; } virtual const char* class_name() const override { return "Object"; } virtual void visit_children(Cell::Visitor&) override; diff --git a/Libraries/LibJS/Tests/builtins/Array/Array.prototype.values.js b/Libraries/LibJS/Tests/builtins/Array/Array.prototype.values.js new file mode 100644 index 0000000000..c382fe837d --- /dev/null +++ b/Libraries/LibJS/Tests/builtins/Array/Array.prototype.values.js @@ -0,0 +1,44 @@ +test("length", () => { + expect(Array.prototype.values.length).toBe(0); +}); + +test("basic functionality", () => { + const a = [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 }); +}); + +test("works when applied to non-object", () => { + [true, false, 9, 2n, Symbol()].forEach(primitive => { + const it = [].values.call(primitive); + expect(it.next()).toEqual({ value: undefined, done: true }); + expect(it.next()).toEqual({ value: undefined, done: true }); + expect(it.next()).toEqual({ value: undefined, done: true }); + }); +}); + +test("item added to array before exhaustion is accessible", () => { + const a = [1, 2]; + const it = a.values(); + expect(it.next()).toEqual({ value: 1, done: false }); + expect(it.next()).toEqual({ value: 2, done: false }); + a.push(3); + expect(it.next()).toEqual({ value: 3, done: false }); + expect(it.next()).toEqual({ value: undefined, done: true }); + expect(it.next()).toEqual({ value: undefined, done: true }); +}); + +test("item added to array after exhaustion is inaccesible", () => { + const a = [1, 2]; + 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: undefined, done: true }); + a.push(3); + expect(it.next()).toEqual({ value: undefined, done: true }); +}); diff --git a/Libraries/LibJS/Tests/iterators/%IteratorPrototype%.js b/Libraries/LibJS/Tests/iterators/%IteratorPrototype%.js new file mode 100644 index 0000000000..efdb32e51a --- /dev/null +++ b/Libraries/LibJS/Tests/iterators/%IteratorPrototype%.js @@ -0,0 +1,12 @@ +const getIteratorPrototype = () => + Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())); + +test("prototype of %IteratorPrototype% is %ObjectPrototype%", () => { + let itProto = getIteratorPrototype(); + expect(Object.getPrototypeOf(itProto)).toBe(Object.getPrototypeOf({})); +}); + +test("@@iterator of %IteratorPrototype% is itself", () => { + let itProto = getIteratorPrototype(); + expect(itProto[Symbol.iterator]()).toBe(itProto); +}); diff --git a/Libraries/LibJS/Tests/iterators/array-iterator.js b/Libraries/LibJS/Tests/iterators/array-iterator.js new file mode 100644 index 0000000000..117030ebdf --- /dev/null +++ b/Libraries/LibJS/Tests/iterators/array-iterator.js @@ -0,0 +1,48 @@ +test("length", () => { + expect(Array.prototype[Symbol.iterator].length).toBe(0); +}); + +test("same function as Array.prototype.values", () => { + expect(Array.prototype[Symbol.iterator]).toBe(Array.prototype.values); +}); + +test("basic functionality", () => { + const a = [1, 2, 3]; + const it = a[Symbol.iterator](); + 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 }); +}); + +test("works when applied to non-object", () => { + [true, false, 9, 2n, Symbol()].forEach(primitive => { + const it = [][Symbol.iterator].call(primitive); + expect(it.next()).toEqual({ value: undefined, done: true }); + expect(it.next()).toEqual({ value: undefined, done: true }); + expect(it.next()).toEqual({ value: undefined, done: true }); + }); +}); + +test("item added to array before exhaustion is accessible", () => { + const a = [1, 2]; + const it = a[Symbol.iterator](); + expect(it.next()).toEqual({ value: 1, done: false }); + expect(it.next()).toEqual({ value: 2, done: false }); + a.push(3); + expect(it.next()).toEqual({ value: 3, done: false }); + expect(it.next()).toEqual({ value: undefined, done: true }); + expect(it.next()).toEqual({ value: undefined, done: true }); +}); + +test("item added to array after exhaustion is inaccesible", () => { + const a = [1, 2]; + const it = a[Symbol.iterator](); + expect(it.next()).toEqual({ value: 1, done: false }); + expect(it.next()).toEqual({ value: 2, done: false }); + expect(it.next()).toEqual({ value: undefined, done: true }); + a.push(3); + expect(it.next()).toEqual({ value: undefined, done: true }); +}); |