Skip to content
Open
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
342 changes: 340 additions & 2 deletions core/engine/src/builtins/iterable/iterator_constructor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ use super::{
wrap_for_valid_iterator::WrapForValidIterator,
};

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

/// The `Iterator` constructor.
///
/// More information:
Expand Down Expand Up @@ -58,7 +64,7 @@ impl IntrinsicObject for IteratorConstructor {
// non-enumerable get/set accessor (web-compat requirement). We use the
// builder's `constructor_accessor` support so the property is part of the
// shared-shape allocation rather than a post-build override.
BuiltInBuilder::from_standard_constructor::<Self>(realm)
let builder = BuiltInBuilder::from_standard_constructor::<Self>(realm)
.inherits(Some(
realm
.intrinsics()
Expand All @@ -68,7 +74,14 @@ impl IntrinsicObject for IteratorConstructor {
))
// Static methods
.static_method(Self::from, js_string!("from"), 1)
.static_method(Self::concat, js_string!("concat"), 0)
.static_method(Self::concat, js_string!("concat"), 0);

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

builder
// Prototype methods — lazy (return IteratorHelper)
.method(Self::map, js_string!("map"), 1)
.method(Self::filter, js_string!("filter"), 1)
Expand Down Expand Up @@ -105,7 +118,10 @@ impl BuiltInObject for IteratorConstructor {

impl BuiltInConstructor for IteratorConstructor {
const PROTOTYPE_STORAGE_SLOTS: usize = 14; // 11 methods + @@toStringTag accessor (2 slots) + constructor accessor (2 slots)
#[cfg(not(feature = "experimental"))]
const CONSTRUCTOR_STORAGE_SLOTS: usize = 2;
#[cfg(feature = "experimental")]
const CONSTRUCTOR_STORAGE_SLOTS: usize = 4;
const CONSTRUCTOR_ARGUMENTS: usize = 0;
const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor =
StandardConstructors::iterator;
Expand Down Expand Up @@ -247,6 +263,328 @@ impl IteratorConstructor {
// 6. Return result.
Ok(helper.into())
}
// ==================== Static Methods — Experimental ====================

#[cfg(feature = "experimental")]
/// `Iterator.zip ( iterables [ , options ] )`
///
/// More information:
/// - [TC39 proposal][spec]
///
/// [spec]: https://tc39.es/proposal-joint-iteration/#sec-iterator.zip
fn zip(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you pushed unrelated commits from another PR 😅

let iterables = args.get_or_undefined(0);
let options = args.get_or_undefined(1);

// 1. If iterables is not an Object, throw a TypeError exception.
let iterables_obj = iterables.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("Iterator.zip requires an iterable object")
})?;

// 2. Set options to ? GetOptionsObject(options).
// 3. Let mode be ? Get(options, "mode").
// 4. If mode is undefined, set mode to "shortest".
// 5. If mode is not one of "shortest", "longest", or "strict", throw a TypeError exception.
let mode = Self::parse_zip_mode(options, context)?;

// 6. Let paddingOption be undefined.
// 7. If mode is "longest", then
// a. Set paddingOption to ? Get(options, "padding").
// b. If paddingOption is not undefined and paddingOption is not an Object, throw a TypeError exception.
let padding_option = if mode == ZipMode::Longest {
if let Some(opts) = options.as_object() {
let p = opts.get(js_string!("padding"), context)?;
if p.is_undefined() {
None
} else if !p.is_object() {
return Err(JsNativeError::typ()
.with_message("padding must be an object")
.into());
} else {
Some(p)
}
} else {
None
}
} else {
None
};

// 8. Let iters be a new empty List.
let mut iters: Vec<super::IteratorRecord> = Vec::new();

// 9. Let padding be a new empty List.
// (padding list built later in build_padding)

// 10. Let inputIter be ? GetIterator(iterables, sync).
let iterables_val: JsValue = iterables_obj.clone().into();
let mut input_iter = iterables_val.get_iterator(IteratorHint::Sync, context)?;

// 11. Let next be not-started.
// 12. Repeat, while next is not done,
// a. Set next to Completion(IteratorStepValue(inputIter)).
// b. IfAbruptCloseIterators(next, iters).
// c. If next is not done, then
// i. Let iter be Completion(GetIteratorFlattenable(next, reject-primitives)).
// ii. IfAbruptCloseIterators(iter, the list-concatenation of « inputIter » and iters).
// iii. Append iter to iters.
loop {
let next = input_iter.step_value(context);
match next {
Err(err) => {
// IfAbruptCloseIterators(next, iters)
for iter in &iters {
drop(iter.close(Ok(JsValue::undefined()), context));
}
return Err(err);
}
Ok(None) => break, // done
Ok(Some(value)) => {
// GetIteratorFlattenable(next, reject-primitives)
if !value.is_object() {
// Close all collected iterators and the input iterator.
for iter in &iters {
drop(iter.close(Ok(JsValue::undefined()), context));
}
drop(input_iter.close(Ok(JsValue::undefined()), context));
return Err(JsNativeError::typ()
.with_message("iterator value is not an object")
.into());
}
let iter_result = value.get_iterator(IteratorHint::Sync, context);
match iter_result {
Err(err) => {
for iter in &iters {
drop(iter.close(Ok(JsValue::undefined()), context));
}
drop(input_iter.close(Ok(JsValue::undefined()), context));
return Err(err);
}
Ok(iter) => iters.push(iter),
}
}
}
}

// 13. Let iterCount be the number of elements in iters.
let iter_count = iters.len();

// 14. If mode is "longest", then ... Build padding list.
let padding = Self::build_padding(padding_option, iter_count, &iters, context)?;

// 15. Let finishResults be a new Abstract Closure ... (handled in ZipIterator::create_zip_iterator)
// 16. Return ? IteratorZip(iters, mode, padding, finishResults).
Ok(ZipIterator::create_zip_iterator(
iters,
mode,
padding,
ZipResultKind::Array,
context,
))
}

#[cfg(feature = "experimental")]
/// `Iterator.zipKeyed ( iterables [ , options ] )`
///
/// More information:
/// - [TC39 proposal][spec]
///
/// [spec]: https://tc39.es/proposal-joint-iteration/#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);

// 1. If iterables is not an Object, throw a TypeError exception.
let iterables_obj = iterables.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("Iterator.zipKeyed requires an object")
})?;

// 2. Set options to ? GetOptionsObject(options).
// 3. Let mode be ? Get(options, "mode").
// 4. If mode is undefined, set mode to "shortest".
// 5. If mode is not one of "shortest", "longest", or "strict", throw a TypeError exception.
let mode = Self::parse_zip_mode(options, context)?;

// 6. Let paddingOption be undefined.
// 7. If mode is "longest", then
// a. Set paddingOption to ? Get(options, "padding").
// b. If paddingOption is not undefined and paddingOption is not an Object, throw a TypeError exception.
let padding_option = if mode == ZipMode::Longest {
if let Some(opts) = options.as_object() {
let p = opts.get(js_string!("padding"), context)?;
if p.is_undefined() {
None
} else if !p.is_object() {
return Err(JsNativeError::typ()
.with_message("padding must be an object")
.into());
} else {
Some(p)
}
} else {
None
}
} else {
None
};

// 8. Let iters be a new empty List.
let mut iters: Vec<super::IteratorRecord> = Vec::new();
// 9. Let keys be a new empty List.
let mut keys: Vec<JsValue> = Vec::new();

// 10. Let iterablesKeys be ? EnumerableOwnProperties(iterables, key).
let all_keys = iterables_obj.own_property_keys(context)?;
// 11. For each element key of iterablesKeys, do
// a. Let value be ? Get(iterables, key).
// b. If value is not undefined, then
// i. Append key to keys.
// ii. Let iter be Completion(GetIteratorFlattenable(value, reject-primitives)).
// iii. IfAbruptCloseIterators(iter, iters).
// iv. Append iter to iters.
for key in all_keys {
let key_val: JsValue = key.clone().into();
let value = iterables_obj.get(key.clone(), context)?;
if !value.is_undefined() {
keys.push(key_val);
if !value.is_object() {
for iter in &iters {
drop(iter.close(Ok(JsValue::undefined()), context));
}
return Err(JsNativeError::typ()
.with_message("iterator value is not an object")
.into());
}
let iter = value.get_iterator(IteratorHint::Sync, context);
match iter {
Err(err) => {
for it in &iters {
drop(it.close(Ok(JsValue::undefined()), context));
}
return Err(err);
}
Ok(iter) => iters.push(iter),
}
}
}

// 12. Let iterCount be the number of elements in iters.
let iter_count = iters.len();

// 13. Let padding be a new empty List.
// 14. If mode is "longest", then ... (Build padding for zipKeyed)
let padding = if mode == ZipMode::Longest {
match padding_option {
None => vec![JsValue::undefined(); iter_count],
Some(pad_obj) => {
let pad = pad_obj
.as_object()
.expect("padding object verification already executed above");
let mut padding = Vec::with_capacity(iter_count);
for key in &keys {
let prop_key = key.to_string(context).unwrap_or_default();
let val = pad.get(prop_key, context)?;
padding.push(val);
}
padding
}
}
} else {
Vec::new()
};

// 15. Let finishResults be a new Abstract Closure ... (handled in ZipIterator::create_zip_iterator)
// 16. Return ? IteratorZip(iters, mode, padding, finishResults).
Ok(ZipIterator::create_zip_iterator(
iters,
mode,
padding,
ZipResultKind::Keyed(keys),
context,
))
}

#[cfg(feature = "experimental")]
/// Parses the `mode` option from the options object.
fn parse_zip_mode(options: &JsValue, context: &mut Context) -> JsResult<ZipMode> {
if options.is_undefined() || options.is_null() {
return Ok(ZipMode::Shortest);
}
let opts = options
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("options must be an object"))?;
let mode_val = opts.get(js_string!("mode"), context)?;
if mode_val.is_undefined() {
return Ok(ZipMode::Shortest);
}
let mode_str = mode_val.to_string(context)?;
match mode_str.to_std_string_escaped().as_str() {
"shortest" => Ok(ZipMode::Shortest),
"longest" => Ok(ZipMode::Longest),
"strict" => Ok(ZipMode::Strict),
_ => Err(JsNativeError::typ()
.with_message("mode must be \"shortest\", \"longest\", or \"strict\"")
.into()),
}
}

#[cfg(feature = "experimental")]
/// Builds the padding list for "longest" mode.
fn build_padding(
padding_option: Option<JsValue>,
iter_count: usize,
iters: &[super::IteratorRecord],
context: &mut Context,
) -> JsResult<Vec<JsValue>> {
match padding_option {
None => Ok(vec![JsValue::undefined(); iter_count]),
Some(pad_val) => {
let mut padding_iter = pad_val
.get_iterator(IteratorHint::Sync, context)
.inspect_err(|_err| {
for iter in iters {
drop(iter.close(Ok(JsValue::undefined()), context));
}
})?;
let mut padding = Vec::new();
let mut using_iterator = true;

for _ in 0..iter_count {
if using_iterator {
match padding_iter.step_value(context) {
Err(err) => {
for iter in iters {
drop(iter.close(Ok(JsValue::undefined()), context));
}
return Err(err);
}
Ok(None) => {
using_iterator = false;
padding.push(JsValue::undefined());
}
Ok(Some(val)) => {
padding.push(val);
}
}
} else {
padding.push(JsValue::undefined());
}
}

if using_iterator {
let close_result = padding_iter.close(Ok(JsValue::undefined()), context);
if let Err(err) = close_result {
for iter in iters {
drop(iter.close(Ok(JsValue::undefined()), context));
}
return Err(err);
}
}

Ok(padding)
}
}
}

// ==================== Prototype Accessor Properties ====================

Expand Down
Loading
Loading