From 5736b53013bd03bba6613f46a54a94987a91d888 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Sat, 24 Jun 2023 10:01:04 -0400 Subject: [PATCH] LibJS: Add an Iterator constructor and object The Iterator object cannot be constructed directly but can be subclassed or created with `Iterator.from` (not implemented here). --- Userland/Libraries/LibJS/CMakeLists.txt | 2 + Userland/Libraries/LibJS/Forward.h | 2 +- .../Libraries/LibJS/Runtime/GlobalObject.cpp | 2 + .../Libraries/LibJS/Runtime/Intrinsics.cpp | 1 + Userland/Libraries/LibJS/Runtime/Iterator.cpp | 27 +++++++++ Userland/Libraries/LibJS/Runtime/Iterator.h | 17 ++++++ .../LibJS/Runtime/IteratorConstructor.cpp | 59 +++++++++++++++++++ .../LibJS/Runtime/IteratorConstructor.h | 29 +++++++++ .../LibJS/Tests/builtins/Iterator/Iterator.js | 27 +++++++++ 9 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 Userland/Libraries/LibJS/Runtime/Iterator.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/IteratorConstructor.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/IteratorConstructor.h create mode 100644 Userland/Libraries/LibJS/Tests/builtins/Iterator/Iterator.js diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index d4ae494f21..c3d39f9950 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -141,6 +141,8 @@ set(SOURCES Runtime/Intl/SegmentIteratorPrototype.cpp Runtime/Intl/SegmentsPrototype.cpp Runtime/Intrinsics.cpp + Runtime/Iterator.cpp + Runtime/IteratorConstructor.cpp Runtime/IteratorOperations.cpp Runtime/IteratorPrototype.cpp Runtime/JSONObject.cpp diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index 4540ea4080..d3e846f9d9 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/Userland/Libraries/LibJS/Forward.h @@ -32,6 +32,7 @@ __JS_ENUMERATE(FinalizationRegistry, finalization_registry, FinalizationRegistryPrototype, FinalizationRegistryConstructor, void) \ __JS_ENUMERATE(FunctionObject, function, FunctionPrototype, FunctionConstructor, void) \ __JS_ENUMERATE(GeneratorFunction, generator_function, GeneratorFunctionPrototype, GeneratorFunctionConstructor, void) \ + __JS_ENUMERATE(Iterator, iterator, IteratorPrototype, IteratorConstructor, void) \ __JS_ENUMERATE(Map, map, MapPrototype, MapConstructor, void) \ __JS_ENUMERATE(NumberObject, number, NumberPrototype, NumberConstructor, void) \ __JS_ENUMERATE(Object, object, ObjectPrototype, ObjectConstructor, void) \ @@ -106,7 +107,6 @@ __JS_ENUMERATE(Temporal::Temporal, temporal) #define JS_ENUMERATE_ITERATOR_PROTOTYPES \ - __JS_ENUMERATE(Iterator, iterator) \ __JS_ENUMERATE(ArrayIterator, array_iterator) \ __JS_ENUMERATE(AsyncIterator, async_iterator) \ __JS_ENUMERATE(Intl::SegmentIterator, intl_segment_iterator) \ diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp index fe846dddfe..88528828ae 100644 --- a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -146,6 +147,7 @@ Object& set_default_global_bindings(Realm& realm) global.define_intrinsic_accessor(vm.names.Int8Array, attr, [](auto& realm) -> Value { return realm.intrinsics().int8_array_constructor(); }); global.define_intrinsic_accessor(vm.names.Int16Array, attr, [](auto& realm) -> Value { return realm.intrinsics().int16_array_constructor(); }); global.define_intrinsic_accessor(vm.names.Int32Array, attr, [](auto& realm) -> Value { return realm.intrinsics().int32_array_constructor(); }); + global.define_intrinsic_accessor(vm.names.Iterator, attr, [](auto& realm) -> Value { return realm.intrinsics().iterator_constructor(); }); global.define_intrinsic_accessor(vm.names.Map, attr, [](auto& realm) -> Value { return realm.intrinsics().map_constructor(); }); global.define_intrinsic_accessor(vm.names.Number, attr, [](auto& realm) -> Value { return realm.intrinsics().number_constructor(); }); global.define_intrinsic_accessor(vm.names.Object, attr, [](auto& realm) -> Value { return realm.intrinsics().object_constructor(); }); diff --git a/Userland/Libraries/LibJS/Runtime/Intrinsics.cpp b/Userland/Libraries/LibJS/Runtime/Intrinsics.cpp index 6491ff617e..a15b431a2e 100644 --- a/Userland/Libraries/LibJS/Runtime/Intrinsics.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intrinsics.cpp @@ -63,6 +63,7 @@ #include #include #include +#include #include #include #include diff --git a/Userland/Libraries/LibJS/Runtime/Iterator.cpp b/Userland/Libraries/LibJS/Runtime/Iterator.cpp new file mode 100644 index 0000000000..86793d5165 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Iterator.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +namespace JS { + +ThrowCompletionOr> Iterator::create(Realm& realm, Object& prototype, IteratorRecord iterated) +{ + return MUST_OR_THROW_OOM(realm.heap().allocate(realm, prototype, move(iterated))); +} + +Iterator::Iterator(Object& prototype, IteratorRecord iterated) + : Object(ConstructWithPrototypeTag::Tag, prototype) + , m_iterated(move(iterated)) +{ +} + +Iterator::Iterator(Object& prototype) + : Iterator(prototype, {}) +{ +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Iterator.h b/Userland/Libraries/LibJS/Runtime/Iterator.h index 1cce4ffa5c..dc5e272336 100644 --- a/Userland/Libraries/LibJS/Runtime/Iterator.h +++ b/Userland/Libraries/LibJS/Runtime/Iterator.h @@ -1,11 +1,13 @@ /* * Copyright (c) 2022, Linus Groh + * Copyright (c) 2023, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once +#include #include #include @@ -18,4 +20,19 @@ struct IteratorRecord { bool done { false }; // [[Done]] }; +class Iterator : public Object { + JS_OBJECT(Iterator, Object); + +public: + static ThrowCompletionOr> create(Realm&, Object& prototype, IteratorRecord iterated); + + IteratorRecord const& iterated() const { return m_iterated; } + +private: + Iterator(Object& prototype, IteratorRecord iterated); + explicit Iterator(Object& prototype); + + IteratorRecord m_iterated; // [[Iterated]] +}; + } diff --git a/Userland/Libraries/LibJS/Runtime/IteratorConstructor.cpp b/Userland/Libraries/LibJS/Runtime/IteratorConstructor.cpp new file mode 100644 index 0000000000..3f222ac704 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/IteratorConstructor.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace JS { + +// 3.1.1.1 The Iterator Constructor, https://tc39.es/proposal-iterator-helpers/#sec-iterator-constructor +IteratorConstructor::IteratorConstructor(Realm& realm) + : Base(realm.vm().names.Iterator.as_string(), realm.intrinsics().function_prototype()) +{ +} + +ThrowCompletionOr IteratorConstructor::initialize(Realm& realm) +{ + MUST_OR_THROW_OOM(Base::initialize(realm)); + + auto& vm = this->vm(); + + // 3.1.1.2.1 Iterator.prototype, https://tc39.es/proposal-iterator-helpers/#sec-iterator.prototype + define_direct_property(vm.names.prototype, realm.intrinsics().iterator_prototype(), 0); + + define_direct_property(vm.names.length, Value(0), Attribute::Configurable); + + return {}; +} + +// 3.1.1.1.1 Iterator ( ), https://tc39.es/proposal-iterator-helpers/#sec-iterator +ThrowCompletionOr IteratorConstructor::call() +{ + auto& vm = this->vm(); + + // 1. If NewTarget is undefined or the active function object, throw a TypeError exception. + return vm.throw_completion(ErrorType::ConstructorWithoutNew, "Iterator"); +} + +// 3.1.1.1.1 Iterator ( ), https://tc39.es/proposal-iterator-helpers/#sec-iterator +ThrowCompletionOr> IteratorConstructor::construct(FunctionObject& new_target) +{ + auto& vm = this->vm(); + + // 1. If NewTarget is undefined or the active function object, throw a TypeError exception. + if (&new_target == this) + return vm.throw_completion(ErrorType::ClassIsAbstract, "Iterator"); + + // 2. Return ? OrdinaryCreateFromConstructor(NewTarget, "%Iterator.prototype%"). + return TRY(ordinary_create_from_constructor(vm, new_target, &Intrinsics::iterator_prototype)); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/IteratorConstructor.h b/Userland/Libraries/LibJS/Runtime/IteratorConstructor.h new file mode 100644 index 0000000000..8b924c958d --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/IteratorConstructor.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace JS { + +class IteratorConstructor : public NativeFunction { + JS_OBJECT(IteratorConstructor, NativeFunction); + +public: + virtual ThrowCompletionOr initialize(Realm&) override; + + virtual ThrowCompletionOr call() override; + virtual ThrowCompletionOr> construct(FunctionObject& new_target) override; + +private: + explicit IteratorConstructor(Realm&); + + virtual bool has_constructor() const override { return true; } +}; + +} diff --git a/Userland/Libraries/LibJS/Tests/builtins/Iterator/Iterator.js b/Userland/Libraries/LibJS/Tests/builtins/Iterator/Iterator.js new file mode 100644 index 0000000000..9985799acf --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Iterator/Iterator.js @@ -0,0 +1,27 @@ +describe("errors", () => { + test("called without new", () => { + expect(() => { + Iterator(); + }).toThrowWithMessage(TypeError, "Iterator constructor must be called with 'new'"); + }); + + test("cannot be directly constructed", () => { + expect(() => { + new Iterator(); + }).toThrowWithMessage(TypeError, "Abstract class Iterator cannot be constructed directly"); + }); +}); + +describe("normal behavior", () => { + test("length is 0", () => { + expect(Iterator).toHaveLength(0); + }); + + test("can be constructed from with subclass", () => { + class TestIterator extends Iterator {} + + const iterator = new TestIterator(); + expect(iterator).toBeInstanceOf(TestIterator); + expect(iterator).toBeInstanceOf(Iterator); + }); +});