use crate::{
builtins::{BuiltInBuilder, IntrinsicObject},
context::intrinsics::Intrinsics,
error::JsNativeError,
js_string,
object::{JsObject, ObjectData},
realm::Realm,
symbol::JsSymbol,
Context, JsResult, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_profiler::Profiler;
mod async_from_sync_iterator;
pub(crate) use async_from_sync_iterator::AsyncFromSyncIterator;
macro_rules! if_abrupt_close_iterator {
($value:expr, $iterator_record:expr, $context:expr) => {
match $value {
Err(err) => return $iterator_record.close(Err(err), $context),
Ok(value) => value,
}
};
}
pub(crate) use if_abrupt_close_iterator;
#[derive(Debug, Default, Trace, Finalize)]
pub struct IteratorPrototypes {
iterator: JsObject,
async_iterator: JsObject,
async_from_sync_iterator: JsObject,
array: JsObject,
set: JsObject,
string: JsObject,
regexp_string: JsObject,
map: JsObject,
for_in: JsObject,
#[cfg(feature = "intl")]
segment: JsObject,
}
impl IteratorPrototypes {
#[inline]
pub fn array(&self) -> JsObject {
self.array.clone()
}
#[inline]
pub fn iterator(&self) -> JsObject {
self.iterator.clone()
}
#[inline]
pub fn async_iterator(&self) -> JsObject {
self.async_iterator.clone()
}
#[inline]
pub fn async_from_sync_iterator(&self) -> JsObject {
self.async_from_sync_iterator.clone()
}
#[inline]
pub fn set(&self) -> JsObject {
self.set.clone()
}
#[inline]
pub fn string(&self) -> JsObject {
self.string.clone()
}
#[inline]
pub fn regexp_string(&self) -> JsObject {
self.regexp_string.clone()
}
#[inline]
pub fn map(&self) -> JsObject {
self.map.clone()
}
#[inline]
pub fn for_in(&self) -> JsObject {
self.for_in.clone()
}
#[inline]
#[cfg(feature = "intl")]
pub fn segment(&self) -> JsObject {
self.segment.clone()
}
}
pub(crate) struct Iterator;
impl IntrinsicObject for Iterator {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event("Iterator Prototype", "init");
BuiltInBuilder::with_intrinsic::<Self>(realm)
.static_method(
|v, _, _| Ok(v.clone()),
(JsSymbol::iterator(), js_string!("[Symbol.iterator]")),
0,
)
.build();
}
fn get(intrinsics: &Intrinsics) -> JsObject {
intrinsics.objects().iterator_prototypes().iterator()
}
}
pub(crate) struct AsyncIterator;
impl IntrinsicObject for AsyncIterator {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event("AsyncIteratorPrototype", "init");
BuiltInBuilder::with_intrinsic::<Self>(realm)
.static_method(
|v, _, _| Ok(v.clone()),
(
JsSymbol::async_iterator(),
js_string!("[Symbol.asyncIterator]"),
),
0,
)
.build();
}
fn get(intrinsics: &Intrinsics) -> JsObject {
intrinsics.objects().iterator_prototypes().async_iterator()
}
}
pub fn create_iter_result_object(value: JsValue, done: bool, context: &mut Context<'_>) -> JsValue {
let _timer = Profiler::global().start_event("create_iter_result_object", "init");
let obj = context
.intrinsics()
.templates()
.iterator_result()
.create(ObjectData::ordinary(), vec![value, done.into()]);
obj.into()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IteratorHint {
Sync,
Async,
}
impl JsValue {
pub fn get_iterator(
&self,
context: &mut Context<'_>,
hint: Option<IteratorHint>,
method: Option<JsObject>,
) -> JsResult<IteratorRecord> {
let hint = hint.unwrap_or(IteratorHint::Sync);
let method = if method.is_some() {
method
} else {
if hint == IteratorHint::Async {
if let Some(method) = self.get_method(JsSymbol::async_iterator(), context)? {
Some(method)
} else {
let sync_method = self.get_method(JsSymbol::iterator(), context)?;
let sync_iterator_record =
self.get_iterator(context, Some(IteratorHint::Sync), sync_method)?;
return Ok(AsyncFromSyncIterator::create(sync_iterator_record, context));
}
} else {
self.get_method(JsSymbol::iterator(), context)?
}
}
.ok_or_else(|| {
JsNativeError::typ().with_message(format!(
"value with type `{}` is not iterable",
self.type_of()
))
})?;
let iterator = method.call(self, &[], context)?;
let iterator_obj = iterator.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("returned iterator is not an object")
})?;
let next_method = iterator.get_v(js_string!("next"), context)?;
Ok(IteratorRecord::new(iterator_obj.clone(), next_method))
}
}
#[derive(Debug, Clone, Trace, Finalize)]
pub struct IteratorResult {
object: JsObject,
}
impl IteratorResult {
pub(crate) fn from_value(value: JsValue) -> JsResult<Self> {
if let JsValue::Object(o) = value {
Ok(Self { object: o })
} else {
Err(JsNativeError::typ()
.with_message("next value should be an object")
.into())
}
}
pub(crate) const fn object(&self) -> &JsObject {
&self.object
}
#[inline]
pub fn complete(&self, context: &mut Context<'_>) -> JsResult<bool> {
Ok(self.object.get(js_string!("done"), context)?.to_boolean())
}
#[inline]
pub fn value(&self, context: &mut Context<'_>) -> JsResult<JsValue> {
self.object.get(js_string!("value"), context)
}
}
#[derive(Clone, Debug, Finalize, Trace)]
pub struct IteratorRecord {
iterator: JsObject,
next_method: JsValue,
done: bool,
last_result: IteratorResult,
}
impl IteratorRecord {
#[inline]
pub fn new(iterator: JsObject, next_method: JsValue) -> Self {
Self {
iterator,
next_method,
done: false,
last_result: IteratorResult {
object: JsObject::with_null_proto(),
},
}
}
pub(crate) const fn iterator(&self) -> &JsObject {
&self.iterator
}
pub(crate) const fn next_method(&self) -> &JsValue {
&self.next_method
}
pub(crate) const fn last_result(&self) -> &IteratorResult {
&self.last_result
}
fn set_done_on_err<R, F>(&mut self, f: F) -> JsResult<R>
where
F: FnOnce(&mut Self) -> JsResult<R>,
{
let result = f(self);
if result.is_err() {
self.done = true;
}
result
}
pub(crate) fn value(&mut self, context: &mut Context<'_>) -> JsResult<JsValue> {
self.set_done_on_err(|iter| iter.last_result.value(context))
}
pub(crate) const fn done(&self) -> bool {
self.done
}
pub(crate) fn update_result(
&mut self,
result: JsValue,
context: &mut Context<'_>,
) -> JsResult<()> {
self.set_done_on_err(|iter| {
let result = IteratorResult::from_value(result)?;
iter.done = result.complete(context)?;
iter.last_result = result;
Ok(())
})
}
pub(crate) fn step_with(
&mut self,
value: Option<&JsValue>,
context: &mut Context<'_>,
) -> JsResult<bool> {
let _timer = Profiler::global().start_event("IteratorRecord::step_with", "iterator");
self.set_done_on_err(|iter| {
let result = iter.next_method.call(
&iter.iterator.clone().into(),
value.map_or(&[], std::slice::from_ref),
context,
)?;
iter.update_result(result, context)?;
Ok(iter.done)
})
}
pub(crate) fn step(&mut self, context: &mut Context<'_>) -> JsResult<bool> {
self.step_with(None, context)
}
pub(crate) fn close(
&self,
completion: JsResult<JsValue>,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
let _timer = Profiler::global().start_event("IteratorRecord::close", "iterator");
let iterator = &self.iterator;
let inner_result = iterator.get_method(js_string!("return"), context);
let inner_result = match inner_result {
Ok(inner_result) => {
let r#return = inner_result;
if let Some(r#return) = r#return {
r#return.call(&iterator.clone().into(), &[], context)
} else {
return completion;
}
}
Err(inner_result) => {
completion?;
return Err(inner_result);
}
};
let completion = completion?;
let inner_result = inner_result?;
if inner_result.is_object() {
Ok(completion)
} else {
Err(JsNativeError::typ()
.with_message("inner result was not an object")
.into())
}
}
}
pub(crate) fn iterable_to_list(
context: &mut Context<'_>,
items: &JsValue,
method: Option<JsObject>,
) -> JsResult<Vec<JsValue>> {
let _timer = Profiler::global().start_event("iterable_to_list", "iterator");
let mut iterator_record = items.get_iterator(context, Some(IteratorHint::Sync), method)?;
let mut values = Vec::new();
while !iterator_record.step(context)? {
values.push(iterator_record.value(context)?);
}
Ok(values)
}