mod async_from_sync_iterator;
use crate::{
builtins::{
regexp::regexp_string_iterator::RegExpStringIterator,
string::string_iterator::StringIterator, ArrayIterator, ForInIterator, MapIterator,
SetIterator,
},
object::{JsObject, ObjectInitializer},
symbol::WellKnownSymbols,
Context, JsResult, JsValue,
};
use async_from_sync_iterator::create_async_from_sync_iterator_prototype;
use boa_gc::{Finalize, Trace};
use boa_profiler::Profiler;
pub(crate) use async_from_sync_iterator::AsyncFromSyncIterator;
#[derive(Debug, Default)]
pub struct IteratorPrototypes {
iterator_prototype: JsObject,
async_iterator_prototype: JsObject,
async_from_sync_iterator_prototype: JsObject,
array_iterator: JsObject,
set_iterator: JsObject,
string_iterator: JsObject,
regexp_string_iterator: JsObject,
map_iterator: JsObject,
for_in_iterator: JsObject,
}
impl IteratorPrototypes {
pub(crate) fn init(context: &mut Context) -> Self {
let _timer = Profiler::global().start_event("IteratorPrototypes::init", "init");
let iterator_prototype = create_iterator_prototype(context);
let async_iterator_prototype = create_async_iterator_prototype(context);
let async_from_sync_iterator_prototype = create_async_from_sync_iterator_prototype(context);
Self {
array_iterator: ArrayIterator::create_prototype(iterator_prototype.clone(), context),
set_iterator: SetIterator::create_prototype(iterator_prototype.clone(), context),
string_iterator: StringIterator::create_prototype(iterator_prototype.clone(), context),
regexp_string_iterator: RegExpStringIterator::create_prototype(
iterator_prototype.clone(),
context,
),
map_iterator: MapIterator::create_prototype(iterator_prototype.clone(), context),
for_in_iterator: ForInIterator::create_prototype(iterator_prototype.clone(), context),
iterator_prototype,
async_iterator_prototype,
async_from_sync_iterator_prototype,
}
}
#[inline]
pub fn array_iterator(&self) -> JsObject {
self.array_iterator.clone()
}
#[inline]
pub fn iterator_prototype(&self) -> JsObject {
self.iterator_prototype.clone()
}
#[inline]
pub fn async_iterator_prototype(&self) -> JsObject {
self.async_iterator_prototype.clone()
}
#[inline]
pub fn async_from_sync_iterator_prototype(&self) -> JsObject {
self.async_from_sync_iterator_prototype.clone()
}
#[inline]
pub fn set_iterator(&self) -> JsObject {
self.set_iterator.clone()
}
#[inline]
pub fn string_iterator(&self) -> JsObject {
self.string_iterator.clone()
}
#[inline]
pub fn regexp_string_iterator(&self) -> JsObject {
self.regexp_string_iterator.clone()
}
#[inline]
pub fn map_iterator(&self) -> JsObject {
self.map_iterator.clone()
}
#[inline]
pub fn for_in_iterator(&self) -> JsObject {
self.for_in_iterator.clone()
}
}
#[inline]
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.construct_object();
obj.create_data_property_or_throw("value", value, context)
.expect("this CreateDataPropertyOrThrow call must not fail");
obj.create_data_property_or_throw("done", done, context)
.expect("this CreateDataPropertyOrThrow call must not fail");
obj.into()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IteratorHint {
Sync,
Async,
}
impl JsValue {
#[inline]
pub fn get_iterator(
&self,
context: &mut Context,
hint: Option<IteratorHint>,
method: Option<Self>,
) -> JsResult<IteratorRecord> {
let hint = hint.unwrap_or(IteratorHint::Sync);
let method = if let Some(method) = method {
method
} else {
if hint == IteratorHint::Async {
if let Some(method) =
self.get_method(WellKnownSymbols::async_iterator(), context)?
{
method.into()
} else {
let sync_method = self
.get_method(WellKnownSymbols::iterator(), context)?
.map_or(Self::Undefined, Self::from);
let sync_iterator_record =
self.get_iterator(context, Some(IteratorHint::Sync), Some(sync_method))?;
return Ok(AsyncFromSyncIterator::create(sync_iterator_record, context));
}
} else {
self.get_method(WellKnownSymbols::iterator(), context)?
.map_or(Self::Undefined, Self::from)
}
};
let iterator = context.call(&method, self, &[])?;
let iterator_obj = iterator
.as_object()
.ok_or_else(|| context.construct_type_error("the iterator is not an object"))?;
let next_method = iterator.get_v("next", context)?;
Ok(IteratorRecord::new(
iterator_obj.clone(),
next_method,
false,
))
}
}
#[inline]
fn create_iterator_prototype(context: &mut Context) -> JsObject {
let _timer = Profiler::global().start_event("Iterator Prototype", "init");
let symbol_iterator = WellKnownSymbols::iterator();
let iterator_prototype = ObjectInitializer::new(context)
.function(
|v, _, _| Ok(v.clone()),
(symbol_iterator, "[Symbol.iterator]"),
0,
)
.build();
iterator_prototype
}
#[derive(Debug)]
pub struct IteratorResult {
object: JsObject,
}
impl IteratorResult {
pub(crate) fn new(object: JsObject) -> Self {
Self { object }
}
#[inline]
pub fn complete(&self, context: &mut Context) -> JsResult<bool> {
Ok(self.object.get("done", context)?.to_boolean())
}
#[inline]
pub fn value(&self, context: &mut Context) -> JsResult<JsValue> {
self.object.get("value", context)
}
}
#[derive(Clone, Debug, Finalize, Trace)]
pub struct IteratorRecord {
iterator: JsObject,
next_method: JsValue,
done: bool,
}
impl IteratorRecord {
#[inline]
pub fn new(iterator: JsObject, next_method: JsValue, done: bool) -> Self {
Self {
iterator,
next_method,
done,
}
}
#[inline]
pub(crate) fn iterator(&self) -> &JsObject {
&self.iterator
}
#[inline]
pub(crate) fn next_method(&self) -> &JsValue {
&self.next_method
}
#[inline]
pub(crate) fn done(&self) -> bool {
self.done
}
#[inline]
pub(crate) fn set_done(&mut self, done: bool) {
self.done = done;
}
#[inline]
pub(crate) fn next(
&self,
value: Option<JsValue>,
context: &mut Context,
) -> JsResult<IteratorResult> {
let _timer = Profiler::global().start_event("IteratorRecord::next", "iterator");
let next_method = if let Some(next_method) = self.next_method.as_callable() {
next_method
} else {
return context.throw_type_error("iterable next method not a function");
};
let result = if let Some(value) = value {
next_method.call(&self.iterator.clone().into(), &[value], context)?
} else {
next_method.call(&self.iterator.clone().into(), &[], context)?
};
if let Some(o) = result.as_object() {
Ok(IteratorResult { object: o.clone() })
} else {
context.throw_type_error("next value should be an object")
}
}
pub(crate) fn step(&self, context: &mut Context) -> JsResult<Option<IteratorResult>> {
let _timer = Profiler::global().start_event("IteratorRecord::step", "iterator");
let result = self.next(None, context)?;
let done = result.complete(context)?;
if done {
return Ok(None);
}
Ok(Some(result))
}
#[inline]
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("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 {
context.throw_type_error("inner result was not an object")
}
}
}
pub(crate) fn iterable_to_list(
context: &mut Context,
items: &JsValue,
method: Option<JsValue>,
) -> JsResult<Vec<JsValue>> {
let _timer = Profiler::global().start_event("iterable_to_list", "iterator");
let iterator_record = if let Some(method) = method {
items.get_iterator(context, Some(IteratorHint::Sync), Some(method))?
} else {
items.get_iterator(context, Some(IteratorHint::Sync), None)?
};
let mut values = Vec::new();
while let Some(next) = iterator_record.step(context)? {
let next_value = next.value(context)?;
values.push(next_value);
}
Ok(values)
}
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;
#[inline]
fn create_async_iterator_prototype(context: &mut Context) -> JsObject {
let _timer = Profiler::global().start_event("AsyncIteratorPrototype", "init");
let symbol_iterator = WellKnownSymbols::async_iterator();
let iterator_prototype = ObjectInitializer::new(context)
.function(
|v, _, _| Ok(v.clone()),
(symbol_iterator, "[Symbol.asyncIterator]"),
0,
)
.build();
iterator_prototype
}