use std::ops::ControlFlow;
use crate::{
Context, JsData, JsResult, JsValue,
builtins::{BuiltInBuilder, IntrinsicObject, iterable::create_iter_result_object},
context::intrinsics::Intrinsics,
error::PanicError,
js_error, js_string,
native_function::NativeCoroutine,
object::JsObject,
property::Attribute,
realm::Realm,
symbol::JsSymbol,
vm::CompletionRecord,
};
use boa_gc::{Finalize, Trace};
macro_rules! if_abrupt_close_iterator {
($value:expr, $iterator_record:expr, $context:expr) => {
match $value {
Err(err) => {
return $crate::native_function::CoroutineBranch::branch(
$iterator_record.close(Err(err), $context),
)
}
Ok(value) => value,
}
};
}
mod concat;
mod drop;
mod filter;
mod flat_map;
mod map;
mod take;
pub(crate) use concat::{Concat, IterableRecord};
pub(crate) use drop::Drop;
pub(crate) use filter::Filter;
pub(crate) use flat_map::FlatMap;
pub(crate) use map::Map;
pub(crate) use take::Take;
#[derive(Debug, Finalize, Trace, JsData)]
pub(crate) struct IteratorHelper {
pub(crate) coroutine: Option<NativeCoroutine>,
}
impl IntrinsicObject for IteratorHelper {
fn init(realm: &Realm) {
BuiltInBuilder::with_intrinsic::<Self>(realm)
.prototype(realm.intrinsics().constructors().iterator().prototype())
.static_method(Self::next, js_string!("next"), 0)
.static_method(Self::r#return, js_string!("return"), 0)
.static_property(
JsSymbol::to_string_tag(),
js_string!("Iterator Helper"),
Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
)
.build();
}
fn get(intrinsics: &Intrinsics) -> JsObject {
intrinsics.objects().iterator_prototypes().iterator_helper()
}
}
impl IteratorHelper {
pub(crate) fn next(
this: &JsValue,
_args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let helper = Self::generator_validate(this)?;
let coroutine = helper
.borrow_mut()
.data_mut()
.coroutine
.take()
.ok_or_else(|| {
js_error!(
TypeError: "Iterator Helper is already executing"
)
})?;
let result = match coroutine.call(CompletionRecord::Normal(JsValue::undefined()), context) {
ControlFlow::Continue(value) => Ok(create_iter_result_object(value, false, context)),
ControlFlow::Break(Ok(())) => Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
)),
ControlFlow::Break(Err(err)) => Err(err),
};
helper.borrow_mut().data_mut().coroutine = Some(coroutine);
result
}
pub(crate) fn r#return(
this: &JsValue,
_args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let helper = Self::generator_validate(this)?;
let coroutine = helper
.borrow_mut()
.data_mut()
.coroutine
.take()
.ok_or_else(|| {
js_error!(
TypeError: "Iterator Helper is already executing"
)
})?;
let result = match coroutine.call(CompletionRecord::Return(JsValue::undefined()), context) {
ControlFlow::Continue(_) => Err(PanicError::new(
"an iterator helper cannot yield after a return request",
)
.into()),
ControlFlow::Break(Ok(())) => Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
)),
ControlFlow::Break(Err(err)) => Err(err),
};
helper.borrow_mut().data_mut().coroutine = Some(coroutine);
result
}
#[track_caller]
pub(crate) fn generator_validate(this: &JsValue) -> JsResult<JsObject<IteratorHelper>> {
this.as_object()
.and_then(|o| o.downcast::<Self>().ok())
.ok_or_else(|| js_error!(TypeError: "Iterator Helper method called on non-object"))
}
pub(crate) fn create(op: NativeCoroutine, context: &mut Context) -> JsObject {
JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
context
.intrinsics()
.objects()
.iterator_prototypes()
.iterator_helper(),
Self {
coroutine: Some(op),
},
)
.upcast()
}
}