use crate::{
Context, JsData, JsResult,
builtins::{
Array, BuiltInBuilder, IntrinsicObject, JsValue, iterable::create_iter_result_object,
typed_array::TypedArray,
},
context::intrinsics::Intrinsics,
error::JsNativeError,
js_string,
object::JsObject,
property::{Attribute, PropertyNameKind},
realm::Realm,
symbol::JsSymbol,
};
use boa_gc::{Finalize, Trace};
#[derive(Debug, Clone, Finalize, Trace, JsData)]
pub(crate) struct ArrayIterator {
array: JsObject,
next_index: u64,
#[unsafe_ignore_trace]
kind: PropertyNameKind,
done: bool,
}
impl IntrinsicObject for ArrayIterator {
fn init(realm: &Realm) {
BuiltInBuilder::with_intrinsic::<Self>(realm)
.prototype(
realm
.intrinsics()
.objects()
.iterator_prototypes()
.iterator(),
)
.static_method(Self::next, js_string!("next"), 0)
.static_property(
JsSymbol::to_string_tag(),
js_string!("Array Iterator"),
Attribute::CONFIGURABLE,
)
.build();
}
fn get(intrinsics: &Intrinsics) -> JsObject {
intrinsics.objects().iterator_prototypes().array()
}
}
impl ArrayIterator {
fn new(array: JsObject, kind: PropertyNameKind) -> Self {
Self {
array,
kind,
next_index: 0,
done: false,
}
}
pub(crate) fn create_array_iterator(
array: JsObject,
kind: PropertyNameKind,
context: &Context,
) -> JsValue {
let array_iterator = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
context.intrinsics().objects().iterator_prototypes().array(),
Self::new(array, kind),
);
array_iterator.into()
}
pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let object = this.as_object();
let mut array_iterator = object
.as_ref()
.and_then(JsObject::downcast_mut::<Self>)
.ok_or_else(|| JsNativeError::typ().with_message("`this` is not an ArrayIterator"))?;
let index = array_iterator.next_index;
if array_iterator.done {
return Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
));
}
let len = if let Some(f) = array_iterator.array.downcast_ref::<TypedArray>() {
let buf = f.viewed_array_buffer().as_buffer();
let Some(buf) = buf
.bytes(std::sync::atomic::Ordering::SeqCst)
.filter(|buf| !f.is_out_of_bounds(buf.len()))
else {
return Err(JsNativeError::typ()
.with_message("Cannot get value from out of bounds typed array")
.into());
};
f.array_length(buf.len())
} else {
array_iterator.array.length_of_array_like(context)?
};
if index >= len {
array_iterator.done = true;
return Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
));
}
array_iterator.next_index = index + 1;
match array_iterator.kind {
PropertyNameKind::Key => Ok(create_iter_result_object(index.into(), false, context)),
PropertyNameKind::Value => {
let element_value = array_iterator.array.get(index, context)?;
Ok(create_iter_result_object(element_value, false, context))
}
PropertyNameKind::KeyAndValue => {
let element_value = array_iterator.array.get(index, context)?;
let result = Array::create_array_from_list([index.into(), element_value], context);
Ok(create_iter_result_object(result.into(), false, context))
}
}
}
}