mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 07:30:19 +00:00
LibJS: Implement Iterator.from and the WrapForValidIteratorPrototype
Iterator.from creates an Iterator from either an existing iterator or an iterator-like object. In the latter case, it sets the prototype of the returned iterator to WrapForValidIteratorPrototype to wrap around the iterator-like object's iteration methods.
This commit is contained in:
parent
5736b53013
commit
d9d245faa7
Notes:
sideshowbarker
2024-07-18 03:35:30 +09:00
Author: https://github.com/trflynn89 Commit: https://github.com/SerenityOS/serenity/commit/d9d245faa7 Pull-request: https://github.com/SerenityOS/serenity/pull/19642
11 changed files with 299 additions and 0 deletions
|
@ -257,6 +257,7 @@ set(SOURCES
|
|||
Runtime/WeakSet.cpp
|
||||
Runtime/WeakSetConstructor.cpp
|
||||
Runtime/WeakSetPrototype.cpp
|
||||
Runtime/WrapForValidIteratorPrototype.cpp
|
||||
Runtime/WrappedFunction.cpp
|
||||
Script.cpp
|
||||
SourceCode.cpp
|
||||
|
|
|
@ -229,6 +229,7 @@ class AsyncFromSyncIteratorPrototype;
|
|||
class AsyncGenerator;
|
||||
class AsyncGeneratorPrototype;
|
||||
class GeneratorPrototype;
|
||||
class WrapForValidIteratorPrototype;
|
||||
|
||||
class TypedArrayConstructor;
|
||||
class TypedArrayPrototype;
|
||||
|
|
|
@ -126,6 +126,7 @@
|
|||
#include <LibJS/Runtime/WeakRefPrototype.h>
|
||||
#include <LibJS/Runtime/WeakSetConstructor.h>
|
||||
#include <LibJS/Runtime/WeakSetPrototype.h>
|
||||
#include <LibJS/Runtime/WrapForValidIteratorPrototype.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
|
@ -199,6 +200,7 @@ ThrowCompletionOr<void> Intrinsics::initialize_intrinsics(Realm& realm)
|
|||
m_async_generator_prototype = heap().allocate<AsyncGeneratorPrototype>(realm, realm).release_allocated_value_but_fixme_should_propagate_errors();
|
||||
m_generator_prototype = heap().allocate<GeneratorPrototype>(realm, realm).release_allocated_value_but_fixme_should_propagate_errors();
|
||||
m_intl_segments_prototype = heap().allocate<Intl::SegmentsPrototype>(realm, realm).release_allocated_value_but_fixme_should_propagate_errors();
|
||||
m_wrap_for_valid_iterator_prototype = heap().allocate<WrapForValidIteratorPrototype>(realm, realm).release_allocated_value_but_fixme_should_propagate_errors();
|
||||
|
||||
// These must be initialized before allocating...
|
||||
// - AggregateErrorPrototype, which uses ErrorPrototype as its prototype
|
||||
|
@ -356,6 +358,7 @@ void Intrinsics::visit_edges(Visitor& visitor)
|
|||
visitor.visit(m_async_generator_prototype);
|
||||
visitor.visit(m_generator_prototype);
|
||||
visitor.visit(m_intl_segments_prototype);
|
||||
visitor.visit(m_wrap_for_valid_iterator_prototype);
|
||||
visitor.visit(m_eval_function);
|
||||
visitor.visit(m_is_finite_function);
|
||||
visitor.visit(m_is_nan_function);
|
||||
|
|
|
@ -29,6 +29,7 @@ public:
|
|||
NonnullGCPtr<Object> async_from_sync_iterator_prototype() { return *m_async_from_sync_iterator_prototype; }
|
||||
NonnullGCPtr<Object> async_generator_prototype() { return *m_async_generator_prototype; }
|
||||
NonnullGCPtr<Object> generator_prototype() { return *m_generator_prototype; }
|
||||
NonnullGCPtr<Object> wrap_for_valid_iterator_prototype() { return *m_wrap_for_valid_iterator_prototype; }
|
||||
|
||||
// Alias for the AsyncGenerator Prototype Object used by the spec (%AsyncGeneratorFunction.prototype.prototype%)
|
||||
NonnullGCPtr<Object> async_generator_function_prototype_prototype() { return *m_async_generator_prototype; }
|
||||
|
@ -128,6 +129,7 @@ private:
|
|||
GCPtr<Object> m_async_from_sync_iterator_prototype;
|
||||
GCPtr<Object> m_async_generator_prototype;
|
||||
GCPtr<Object> m_generator_prototype;
|
||||
GCPtr<Object> m_wrap_for_valid_iterator_prototype;
|
||||
|
||||
// Not included in JS_ENUMERATE_INTL_OBJECTS due to missing distinct constructor
|
||||
GCPtr<Object> m_intl_segments_prototype;
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Iterator.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
|
@ -24,4 +26,47 @@ Iterator::Iterator(Object& prototype)
|
|||
{
|
||||
}
|
||||
|
||||
// 2.1.1 GetIteratorDirect ( obj ), https://tc39.es/proposal-iterator-helpers/#sec-getiteratorflattenable
|
||||
ThrowCompletionOr<IteratorRecord> get_iterator_direct(VM& vm, Object& object)
|
||||
{
|
||||
// 1. Let nextMethod be ? Get(obj, "next").
|
||||
auto next_method = TRY(object.get(vm.names.next));
|
||||
|
||||
// 2. Let iteratorRecord be Record { [[Iterator]]: obj, [[NextMethod]]: nextMethod, [[Done]]: false }.
|
||||
IteratorRecord iterator_record { .iterator = object, .next_method = next_method, .done = false };
|
||||
|
||||
// 3. Return iteratorRecord.
|
||||
return iterator_record;
|
||||
}
|
||||
|
||||
ThrowCompletionOr<IteratorRecord> get_iterator_flattenable(VM& vm, Value object)
|
||||
{
|
||||
// 1. If obj is not an Object, throw a TypeError exception.
|
||||
if (!object.is_object())
|
||||
return vm.throw_completion<TypeError>(ErrorType::NotAnObject, "obj"sv);
|
||||
|
||||
// 2. Let method be ? GetMethod(obj, @@iterator).
|
||||
auto method = TRY(object.get_method(vm, vm.well_known_symbol_iterator()));
|
||||
|
||||
Value iterator;
|
||||
|
||||
// 3. If method is undefined, then
|
||||
if (!method) {
|
||||
// a. Let iterator be obj.
|
||||
iterator = object;
|
||||
}
|
||||
// 4. Else,
|
||||
else {
|
||||
// a. Let iterator be ? Call(method, obj).
|
||||
iterator = TRY(call(vm, method, object));
|
||||
}
|
||||
|
||||
// 5. If iterator is not an Object, throw a TypeError exception.
|
||||
if (!iterator.is_object())
|
||||
return vm.throw_completion<TypeError>(ErrorType::NotAnObject, "iterator"sv);
|
||||
|
||||
// 6. Return ? GetIteratorDirect(iterator).
|
||||
return TRY(get_iterator_direct(vm, iterator.as_object()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -35,4 +35,7 @@ private:
|
|||
IteratorRecord m_iterated; // [[Iterated]]
|
||||
};
|
||||
|
||||
ThrowCompletionOr<IteratorRecord> get_iterator_direct(VM&, Object&);
|
||||
ThrowCompletionOr<IteratorRecord> get_iterator_flattenable(VM&, Value);
|
||||
|
||||
}
|
||||
|
|
|
@ -29,6 +29,9 @@ ThrowCompletionOr<void> IteratorConstructor::initialize(Realm& realm)
|
|||
// 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);
|
||||
|
||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
define_native_function(realm, vm.names.from, from, 1, attr);
|
||||
|
||||
define_direct_property(vm.names.length, Value(0), Attribute::Configurable);
|
||||
|
||||
return {};
|
||||
|
@ -56,4 +59,35 @@ ThrowCompletionOr<NonnullGCPtr<Object>> IteratorConstructor::construct(FunctionO
|
|||
return TRY(ordinary_create_from_constructor<Iterator>(vm, new_target, &Intrinsics::iterator_prototype));
|
||||
}
|
||||
|
||||
// 3.1.1.2.2 Iterator.from ( O ), https://tc39.es/proposal-iterator-helpers/#sec-iterator.from
|
||||
JS_DEFINE_NATIVE_FUNCTION(IteratorConstructor::from)
|
||||
{
|
||||
auto& realm = *vm.current_realm();
|
||||
|
||||
auto object = vm.argument(0);
|
||||
|
||||
// 1. If O is a String, set O to ! ToObject(O).
|
||||
if (object.is_string())
|
||||
object = MUST_OR_THROW_OOM(object.to_object(vm));
|
||||
|
||||
// 2. Let iteratorRecord be ? GetIteratorFlattenable(O).
|
||||
auto iterator_record = TRY(get_iterator_flattenable(vm, object));
|
||||
|
||||
// 3. Let hasInstance be ? OrdinaryHasInstance(%Iterator%, iteratorRecord.[[Iterator]]).
|
||||
auto has_instance = TRY(ordinary_has_instance(vm, iterator_record.iterator, realm.intrinsics().iterator_constructor()));
|
||||
|
||||
// 4. If hasInstance is true, then
|
||||
if (has_instance.is_boolean() && has_instance.as_bool()) {
|
||||
// a. Return iteratorRecord.[[Iterator]].
|
||||
return iterator_record.iterator;
|
||||
}
|
||||
|
||||
// 5. Let wrapper be OrdinaryObjectCreate(%WrapForValidIteratorPrototype%, « [[Iterated]] »).
|
||||
// 6. Set wrapper.[[Iterated]] to iteratorRecord.
|
||||
auto wrapper = MUST_OR_THROW_OOM(Iterator::create(realm, realm.intrinsics().wrap_for_valid_iterator_prototype(), move(iterator_record)));
|
||||
|
||||
// 7. Return wrapper.
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ private:
|
|||
explicit IteratorConstructor(Realm&);
|
||||
|
||||
virtual bool has_constructor() const override { return true; }
|
||||
|
||||
JS_DECLARE_NATIVE_FUNCTION(from);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/IteratorOperations.h>
|
||||
#include <LibJS/Runtime/WrapForValidIteratorPrototype.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
// 3.1.1.2.2.1 The %WrapForValidIteratorPrototype% Object, https://tc39.es/proposal-iterator-helpers/#sec-wrapforvaliditeratorprototype-object
|
||||
WrapForValidIteratorPrototype::WrapForValidIteratorPrototype(Realm& realm)
|
||||
: PrototypeObject(realm.intrinsics().iterator_prototype())
|
||||
{
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> WrapForValidIteratorPrototype::initialize(Realm& realm)
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
MUST_OR_THROW_OOM(Base::initialize(realm));
|
||||
|
||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
define_native_function(realm, vm.names.next, next, 0, attr);
|
||||
define_native_function(realm, vm.names.return_, return_, 0, attr);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// 3.1.1.2.2.1.1 %WrapForValidIteratorPrototype%.next ( ), https://tc39.es/proposal-iterator-helpers/#sec-wrapforvaliditeratorprototype.next
|
||||
JS_DEFINE_NATIVE_FUNCTION(WrapForValidIteratorPrototype::next)
|
||||
{
|
||||
// 1. Let O be this value.
|
||||
// 2. Perform ? RequireInternalSlot(O, [[Iterated]]).
|
||||
auto object = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. Let iteratorRecord be O.[[Iterated]].
|
||||
auto const& iterator_record = object->iterated();
|
||||
|
||||
// 4. Return ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]).
|
||||
return TRY(call(vm, iterator_record.next_method, iterator_record.iterator));
|
||||
}
|
||||
|
||||
// 3.1.1.2.2.1.2 %WrapForValidIteratorPrototype%.return ( ), https://tc39.es/proposal-iterator-helpers/#sec-wrapforvaliditeratorprototype.return
|
||||
JS_DEFINE_NATIVE_FUNCTION(WrapForValidIteratorPrototype::return_)
|
||||
{
|
||||
// 1. Let O be this value.
|
||||
// 2. Perform ? RequireInternalSlot(O, [[Iterated]]).
|
||||
auto object = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. Let iterator be O.[[Iterated]].[[Iterator]].
|
||||
auto iterator = object->iterated().iterator;
|
||||
|
||||
// 4. Assert: iterator is an Object.
|
||||
VERIFY(iterator);
|
||||
|
||||
// 5. Let returnMethod be ? GetMethod(iterator, "return").
|
||||
auto return_method = TRY(Value { iterator }.get_method(vm, vm.names.return_));
|
||||
|
||||
// 6. If returnMethod is undefined, then
|
||||
if (!return_method) {
|
||||
// a. Return CreateIterResultObject(undefined, true).
|
||||
return create_iterator_result_object(vm, js_undefined(), true);
|
||||
}
|
||||
|
||||
// 7. Return ? Call(returnMethod, iterator).
|
||||
return TRY(call(vm, return_method, iterator));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Runtime/Completion.h>
|
||||
#include <LibJS/Runtime/Iterator.h>
|
||||
#include <LibJS/Runtime/PrototypeObject.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
class WrapForValidIteratorPrototype final : public PrototypeObject<WrapForValidIteratorPrototype, Iterator> {
|
||||
JS_PROTOTYPE_OBJECT(WrapForValidIteratorPrototype, Iterator, Iterator);
|
||||
|
||||
public:
|
||||
virtual ThrowCompletionOr<void> initialize(Realm&) override;
|
||||
|
||||
private:
|
||||
explicit WrapForValidIteratorPrototype(Realm&);
|
||||
|
||||
JS_DECLARE_NATIVE_FUNCTION(next);
|
||||
JS_DECLARE_NATIVE_FUNCTION(return_);
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
describe("errors", () => {
|
||||
test("called with non-Object", () => {
|
||||
expect(() => {
|
||||
Iterator.from(Symbol.hasInstance);
|
||||
}).toThrowWithMessage(TypeError, "obj is not an object");
|
||||
});
|
||||
|
||||
test("@@iterator is not callable", () => {
|
||||
const iterable = {};
|
||||
iterable[Symbol.iterator] = 12389;
|
||||
|
||||
expect(() => {
|
||||
Iterator.from(iterable);
|
||||
}).toThrowWithMessage(TypeError, "12389 is not a function");
|
||||
});
|
||||
|
||||
test("@@iterator throws an exception", () => {
|
||||
function TestError() {}
|
||||
|
||||
const iterable = {};
|
||||
iterable[Symbol.iterator] = () => {
|
||||
throw new TestError();
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
Iterator.from(iterable);
|
||||
}).toThrow(TestError);
|
||||
});
|
||||
|
||||
test("@@iterator return a non-Object", () => {
|
||||
const iterable = {};
|
||||
iterable[Symbol.iterator] = () => {
|
||||
return Symbol.hasInstance;
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
Iterator.from(iterable);
|
||||
}).toThrowWithMessage(TypeError, "iterator is not an object");
|
||||
});
|
||||
});
|
||||
|
||||
describe("normal behavior", () => {
|
||||
test("length is 1", () => {
|
||||
expect(Iterator.from).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("create Iterator from a string", () => {
|
||||
const iterator = Iterator.from("ab");
|
||||
|
||||
let result = iterator.next();
|
||||
expect(result.value).toBe("a");
|
||||
expect(result.done).toBeFalse();
|
||||
|
||||
result = iterator.next();
|
||||
expect(result.value).toBe("b");
|
||||
expect(result.done).toBeFalse();
|
||||
|
||||
result = iterator.next();
|
||||
expect(result.value).toBeUndefined();
|
||||
expect(result.done).toBeTrue();
|
||||
});
|
||||
|
||||
test("create Iterator from generator", () => {
|
||||
function* generator() {
|
||||
yield 1;
|
||||
yield 2;
|
||||
}
|
||||
|
||||
const iterator = Iterator.from(generator());
|
||||
|
||||
let result = iterator.next();
|
||||
expect(result.value).toBe(1);
|
||||
expect(result.done).toBeFalse();
|
||||
|
||||
result = iterator.next();
|
||||
expect(result.value).toBe(2);
|
||||
expect(result.done).toBeFalse();
|
||||
|
||||
result = iterator.next();
|
||||
expect(result.value).toBeUndefined();
|
||||
expect(result.done).toBeTrue();
|
||||
});
|
||||
|
||||
test("create Iterator from iterator-like object", () => {
|
||||
class TestIterator {
|
||||
next() {
|
||||
if (this.#first) {
|
||||
this.#first = false;
|
||||
return { value: 1, done: false };
|
||||
}
|
||||
|
||||
return { value: undefined, done: true };
|
||||
}
|
||||
|
||||
#first = true;
|
||||
}
|
||||
|
||||
const testIterator = new TestIterator();
|
||||
const iterator = Iterator.from(testIterator);
|
||||
|
||||
let result = iterator.next();
|
||||
expect(result.value).toBe(1);
|
||||
expect(result.done).toBeFalse();
|
||||
|
||||
result = iterator.next();
|
||||
expect(result.value).toBeUndefined();
|
||||
expect(result.done).toBeTrue();
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue