Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 27 additions & 32 deletions core/engine/src/builtins/iterable/iterator_constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,34 +33,33 @@ use super::{
wrap_for_valid_iterator::WrapForValidIterator,
};

#[cfg(feature = "experimental")]
use super::{
IteratorHint,
iterator_helper::{ZipMode, ZipResultKind},
};

#[cfg(feature = "experimental")]
use crate::{JsVariant, PanicError, builtins::options::get_options_object, property::PropertyKey};
use crate::{JsVariant, builtins::options::get_options_object, property::PropertyKey};

/// [`IfAbruptCloseIterators ( value, iteratorRecords )`][spec]
///
/// `IfAbruptCloseIterators` is a shorthand for a sequence of algorithm steps that
/// use a list of Iterator Records.
///
/// [spec]: https://tc39.es/proposal-joint-iteration/#sec-ifabruptcloseiterators
#[cfg(feature = "experimental")]
macro_rules! if_abrupt_close_iterators {
($value:expr, $iterators:expr, $context:expr) => {
// 1. Assert: value is a Completion Record.
match $value {
// 2. If value is an abrupt completion, return ? IteratorCloseAll(iteratorRecords, value).
Err(err) => {
let mut completion = Err(err);
for iterator in $iterators {
// 1. For each element iterator of iterators, in reverse List order, do
for iterator in $iterators.rev() {
// 1.a. Set completion to Completion(IteratorClose(iterator, completion)).
completion = iterator.close(completion, $context);
}
return match completion {
Ok(_) => Err(PanicError::new(
Ok(_) => Err($crate::PanicError::new(
"closing an iterator with an error should yield the error",
)
.into()),
Expand All @@ -85,18 +84,13 @@ pub(crate) struct IteratorConstructor;
impl IntrinsicObject for IteratorConstructor {
fn init(realm: &Realm) {
let iterator_prototype = realm.intrinsics().constructors().iterator().prototype();
let builder = BuiltInBuilder::from_standard_constructor::<Self>(realm)
BuiltInBuilder::from_standard_constructor::<Self>(realm)
.inherits(Some(iterator_prototype.clone()))
// Static methods
.static_method(Self::from, js_string!("from"), 1)
.static_method(Self::concat, js_string!("concat"), 0);

#[cfg(feature = "experimental")]
let builder = builder
.static_method(Self::concat, js_string!("concat"), 0)
.static_method(Self::zip, js_string!("zip"), 1)
.static_method(Self::zip_keyed, js_string!("zipKeyed"), 1);

builder
.static_method(Self::zip_keyed, js_string!("zipKeyed"), 1)
.static_property(PROTOTYPE, iterator_prototype, Attribute::empty())
.build_without_prototype();
}
Expand Down Expand Up @@ -253,8 +247,7 @@ impl IteratorConstructor {
/// More information:
/// - [TC39 proposal][spec]
///
/// [spec]: https://tc39.es/proposal-joint-iteration/#sec-iterator.zip
#[cfg(feature = "experimental")]
/// [spec]: https://tc39.es/ecma262/#sec-iterator.zip
fn zip(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let iterables = args.get_or_undefined(0);
let options = args.get_or_undefined(1);
Expand All @@ -281,15 +274,15 @@ impl IteratorConstructor {
// 12.a. Set next to Completion(IteratorStepValue(inputIter)).
// 12.b. IfAbruptCloseIterators(next, iters).
while let Some(next) =
if_abrupt_close_iterators!(input_iter.step_value(context), iters, context)
if_abrupt_close_iterators!(input_iter.step_value(context), iters.iter(), context)
{
// 12.c. If next is not done, then
// 12.c.i. Let iter be Completion(GetIteratorFlattenable(next, reject-primitives)).
// 12.c.ii. IfAbruptCloseIterators(iter, the list-concatenation of « inputIter » and iters).
// 12.c.iii. Append iter to iters.
let iter = if_abrupt_close_iterators!(
get_iterator_flattenable(&next, false, context),
iters,
std::iter::once(&input_iter).chain(iters.iter()),
context
);
iters.push(iter);
Expand All @@ -312,8 +305,7 @@ impl IteratorConstructor {
/// More information:
/// - [TC39 proposal][spec]
///
/// [spec]: https://tc39.es/proposal-joint-iteration/#sec-iterator.zipkeyed
#[cfg(feature = "experimental")]
/// [spec]: https://tc39.es/ecma262/#sec-iterator.zipkeyed
fn zip_keyed(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let iterables = args.get_or_undefined(0);
let options = args.get_or_undefined(1);
Expand Down Expand Up @@ -343,7 +335,7 @@ impl IteratorConstructor {
// 12.b. IfAbruptCloseIterators(desc, iters).
let Some(desc) = if_abrupt_close_iterators!(
iterables.__get_own_property__(&key, &mut context.into()),
iters,
iters.iter(),
context
) else {
continue;
Expand All @@ -357,8 +349,11 @@ impl IteratorConstructor {
// 12.c.i. Let value be Completion(Get(iterables, key)).
// 12.c.ii. IfAbruptCloseIterators(value, iters).
// 12.c.iii. If value is not undefined, then
let value =
if_abrupt_close_iterators!(iterables.get(key.clone(), context), iters, context);
let value = if_abrupt_close_iterators!(
iterables.get(key.clone(), context),
iters.iter(),
context
);
if value.is_undefined() {
continue;
}
Expand All @@ -370,7 +365,7 @@ impl IteratorConstructor {
// 12.c.iii.3. IfAbruptCloseIterators(iter, iters).
let iter = if_abrupt_close_iterators!(
get_iterator_flattenable(&value, false, context),
iters,
iters.iter(),
context
);

Expand Down Expand Up @@ -399,7 +394,6 @@ impl IteratorConstructor {
}

/// Parses the `mode` option from the options object.
#[cfg(feature = "experimental")]
fn parse_options(
options: &JsValue,
context: &mut Context,
Expand Down Expand Up @@ -439,7 +433,6 @@ fn parse_options(
}

/// Builds the padding list for "longest" mode on zip
#[cfg(feature = "experimental")]
fn build_padding_zip(
mode: ZipMode,
padding_option: Option<JsObject>,
Expand All @@ -465,7 +458,7 @@ fn build_padding_zip(
// 14.b.ii. IfAbruptCloseIterators(paddingIter, iters).
let mut padding_iter = if_abrupt_close_iterators!(
padding_option.get_iterator(IteratorHint::Sync, context),
iters,
iters.iter(),
context
);
let mut padding = Vec::new();
Expand All @@ -484,7 +477,7 @@ fn build_padding_zip(
// 14.b.iv.1.a. Set next to Completion(IteratorStepValue(paddingIter)).
// 14.b.iv.1.b. IfAbruptCloseIterators(next, iters).
if let Some(next) =
if_abrupt_close_iterators!(padding_iter.step_value(context), iters, context)
if_abrupt_close_iterators!(padding_iter.step_value(context), iters.iter(), context)
{
// 14.b.iv.1.d. Else,
// 14.b.iv.1.d.i. Append next to padding.
Expand All @@ -504,7 +497,7 @@ fn build_padding_zip(
// 14.b.iv.2.v.2. IfAbruptCloseIterators(completion, iters).
if_abrupt_close_iterators!(
padding_iter.close(Ok(JsValue::undefined()), context),
iters,
iters.iter(),
context
);
}
Expand All @@ -513,7 +506,6 @@ fn build_padding_zip(
}

/// Builds the padding list for "longest" mode on zipKeyed
#[cfg(feature = "experimental")]
fn build_padding_zip_keyed(
mode: ZipMode,
padding_option: Option<JsObject>,
Expand Down Expand Up @@ -541,8 +533,11 @@ fn build_padding_zip_keyed(
for key in keys {
// 14.b.i.1. Let value be Completion(Get(paddingOption, key)).
// 14.b.i.2. IfAbruptCloseIterators(value, iters).
let value =
if_abrupt_close_iterators!(padding_option.get(key.clone(), context), iters, context);
let value = if_abrupt_close_iterators!(
padding_option.get(key.clone(), context),
iters.iter(),
context
);

// 14.b.i.3. Append value to padding.
padding.push(value);
Expand Down
124 changes: 84 additions & 40 deletions core/engine/src/builtins/iterable/iterator_helper/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//!
//! [spec]: https://tc39.es/ecma262/#sec-iterator-helper-objects

use std::ops::ControlFlow;
use std::{mem, ops::ControlFlow};

use crate::{
Context, JsData, JsResult, JsValue,
Expand Down Expand Up @@ -55,7 +55,6 @@ mod filter;
mod flat_map;
mod map;
mod take;
#[cfg(feature = "experimental")]
mod zip;

pub(crate) use concat::{Concat, IterableRecord};
Expand All @@ -64,9 +63,17 @@ pub(crate) use filter::Filter;
pub(crate) use flat_map::FlatMap;
pub(crate) use map::Map;
pub(crate) use take::Take;
#[cfg(feature = "experimental")]
pub(crate) use zip::{Zip, ZipMode, ZipResultKind};

#[derive(Debug, Finalize, Trace)]
#[boa_gc(unsafe_no_drop)]
enum IteratorHelperState {
Executing,
Completed,
SuspendedStart(NativeCoroutine),
SuspendedYield(NativeCoroutine),
}

/// The internal representation of an `Iterator Helper` object.
///
/// More information:
Expand All @@ -75,7 +82,7 @@ pub(crate) use zip::{Zip, ZipMode, ZipResultKind};
/// [spec]: https://tc39.es/ecma262/#sec-iterator-helper-objects
#[derive(Debug, Finalize, Trace, JsData)]
pub(crate) struct IteratorHelper {
pub(crate) coroutine: Option<NativeCoroutine>,
coroutine: IteratorHelperState,
}

impl IntrinsicObject for IteratorHelper {
Expand Down Expand Up @@ -127,16 +134,27 @@ impl IteratorHelper {
// 5. Let state be generator.[[GeneratorState]].
// 6. If state is executing, throw a TypeError exception.
// 7. Return state.
let coroutine = helper
.borrow_mut()
.data_mut()
.coroutine
.take()
.ok_or_else(|| {
js_error!(
let coroutine = mem::replace(
&mut helper.borrow_mut().data_mut().coroutine,
IteratorHelperState::Executing,
);
let coroutine = match coroutine {
IteratorHelperState::Executing => {
return Err(js_error!(
TypeError: "Iterator Helper is already executing"
)
})?;
));
}
IteratorHelperState::Completed => {
helper.borrow_mut().data_mut().coroutine = IteratorHelperState::Completed;
return Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
));
}
IteratorHelperState::SuspendedStart(coro)
| IteratorHelperState::SuspendedYield(coro) => coro,
};

// 3. Assert: state is either suspended-start or suspended-yield.
// 4. Let genContext be generator.[[GeneratorContext]].
Expand All @@ -156,18 +174,26 @@ impl IteratorHelper {
// the code below as "suspending" the underlying generator or returning
// if the result is available.
let result = match coroutine.call(CompletionRecord::Normal(JsValue::undefined()), context) {
ControlFlow::Continue(value) => Ok(create_iter_result_object(value, false, context)),
ControlFlow::Continue(value) => {
helper.borrow_mut().data_mut().coroutine =
IteratorHelperState::SuspendedYield(coroutine);
Ok(create_iter_result_object(value, false, context))
}
// 2. If state is completed, return CreateIteratorResultObject(undefined, true).
ControlFlow::Break(Ok(())) => Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
)),
ControlFlow::Break(Err(err)) => Err(err),
ControlFlow::Break(Ok(())) => {
helper.borrow_mut().data_mut().coroutine = IteratorHelperState::Completed;
Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
))
}
ControlFlow::Break(Err(err)) => {
helper.borrow_mut().data_mut().coroutine = IteratorHelperState::Completed;
Err(err)
}
};

helper.borrow_mut().data_mut().coroutine = Some(coroutine);

// 11. Return ? result.
result
}
Expand Down Expand Up @@ -210,16 +236,30 @@ impl IteratorHelper {
// 5. Let state be generator.[[GeneratorState]].
// 6. If state is executing, throw a TypeError exception.
// 7. Return state.
let coroutine = helper
.borrow_mut()
.data_mut()
.coroutine
.take()
.ok_or_else(|| {
js_error!(
let coroutine = mem::replace(
&mut helper.borrow_mut().data_mut().coroutine,
IteratorHelperState::Executing,
);
let coroutine = match coroutine {
IteratorHelperState::Executing => {
return Err(js_error!(
TypeError: "Iterator Helper is already executing"
)
})?;
));
}
IteratorHelperState::Completed => {
helper.borrow_mut().data_mut().coroutine = IteratorHelperState::Completed;
return Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
));
}
IteratorHelperState::SuspendedStart(coro) => {
helper.borrow_mut().data_mut().coroutine = IteratorHelperState::Completed;
coro
}
IteratorHelperState::SuspendedYield(coro) => coro,
};

// 2. If state is suspended-start, then
// a. Set generator.[[GeneratorState]] to completed.
Expand Down Expand Up @@ -256,17 +296,21 @@ impl IteratorHelper {
)
.into()),
// Step 3.a
ControlFlow::Break(Ok(())) => Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
)),
ControlFlow::Break(Ok(())) => {
helper.borrow_mut().data_mut().coroutine = IteratorHelperState::Completed;
Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
))
}
// Step 3.b
ControlFlow::Break(Err(err)) => Err(err),
ControlFlow::Break(Err(err)) => {
helper.borrow_mut().data_mut().coroutine = IteratorHelperState::Completed;
Err(err)
}
};

helper.borrow_mut().data_mut().coroutine = Some(coroutine);

// 12. Return ? result.
result
}
Expand Down Expand Up @@ -307,7 +351,7 @@ impl IteratorHelper {
.iterator_prototypes()
.iterator_helper(),
Self {
coroutine: Some(op),
coroutine: IteratorHelperState::SuspendedStart(op),
},
)
.upcast()
Expand Down
Loading