use crate::{
ecmascript::{
Agent, ArgumentsList, BUILTIN_STRING_MEMORY, ExceptionType, Function, JsError, JsResult,
Object, OrdinaryObject, PropertyKey, Value, call_function, get, get_method,
get_object_method, is_callable, to_boolean, try_get_object_method,
try_result_into_option_js,
},
engine::{
Bindable, GcScope, NoGcScope, Scopable, ScopableCollection, ScopedCollection,
VmIteratorRecord, bindable_handle,
},
heap::{
CompactionLists, HeapMarkAndSweep, ObjectEntry, ObjectEntryPropertyDescriptor,
WellKnownSymbols, WorkQueues,
},
};
#[derive(Debug, Clone, Copy)]
pub(crate) struct IteratorRecord<'a> {
pub(crate) iterator: Object<'a>,
pub(crate) next_method: Function<'a>,
}
bindable_handle!(IteratorRecord);
pub(crate) fn get_iterator_direct<'gc>(
agent: &mut Agent,
obj: Object,
mut gc: GcScope<'gc, '_>,
) -> JsResult<'gc, Option<IteratorRecord<'gc>>> {
let obj = obj.bind(gc.nogc());
let scoped_obj = obj.scope(agent, gc.nogc());
let next_method = get(
agent,
obj.unbind(),
BUILTIN_STRING_MEMORY.next.into(),
gc.reborrow(),
)
.unbind()?;
let gc = gc.into_nogc();
let Some(next_method) = is_callable(next_method, gc) else {
return Ok(None);
};
let iterator_record = IteratorRecord {
iterator: scoped_obj.get(agent).bind(gc),
next_method,
};
Ok(Some(iterator_record))
}
pub(crate) struct MaybeInvalidIteratorRecord<'a> {
pub(crate) iterator: Object<'a>,
pub(crate) next_method: Option<Function<'a>>,
}
impl<'a> MaybeInvalidIteratorRecord<'a> {
pub(crate) fn into_iterator_record(self) -> Option<IteratorRecord<'a>> {
if let MaybeInvalidIteratorRecord {
iterator,
next_method: Some(next_method),
} = self
{
Some(IteratorRecord {
iterator,
next_method,
})
} else {
None
}
}
pub(crate) fn into_vm_iterator_record(self) -> VmIteratorRecord<'a> {
let MaybeInvalidIteratorRecord {
iterator,
next_method,
} = self;
if let Some(next_method) = next_method {
VmIteratorRecord::GenericIterator(IteratorRecord {
iterator,
next_method,
})
} else {
VmIteratorRecord::InvalidIterator { iterator }
}
}
}
bindable_handle!(MaybeInvalidIteratorRecord);
pub(crate) fn get_iterator_from_method<'a>(
agent: &mut Agent,
obj: Value,
method: Function,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, MaybeInvalidIteratorRecord<'a>> {
let obj = obj.bind(gc.nogc());
let method = method.bind(gc.nogc());
let iterator = call_function(agent, method.unbind(), obj.unbind(), None, gc.reborrow())
.unbind()?
.bind(gc.nogc());
let Ok(iterator) = Object::try_from(iterator) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Iterator is not an object",
gc.into_nogc(),
));
};
let scoped_iterator = iterator.scope(agent, gc.nogc());
let next_method = get(
agent,
iterator.unbind(),
BUILTIN_STRING_MEMORY.next.into(),
gc.reborrow(),
)
.unbind()?;
let gc = gc.into_nogc();
let iterator = unsafe { scoped_iterator.take(agent).bind(gc) };
let next_method = is_callable(next_method, gc);
Ok(MaybeInvalidIteratorRecord {
iterator,
next_method,
})
}
pub(crate) fn get_iterator<'a>(
agent: &mut Agent,
obj: Value,
is_async: bool,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, MaybeInvalidIteratorRecord<'a>> {
let obj = obj.bind(gc.nogc());
let scoped_obj = obj.scope(agent, gc.nogc());
let method = if is_async {
let method = get_method(
agent,
obj.unbind(),
PropertyKey::Symbol(WellKnownSymbols::AsyncIterator.into()),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
if method.is_none() {
let Some(sync_method) = get_method(
agent,
scoped_obj.get(agent),
PropertyKey::Symbol(WellKnownSymbols::Iterator.into()),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc()) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"No iterator on object",
gc.into_nogc(),
));
};
let _sync_iterator_record = get_iterator_from_method(
agent,
scoped_obj.get(agent),
sync_method.unbind(),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
todo!("Implement create_async_from_sync_iterator(sync_iterator_record)")
} else {
method
}
} else {
get_method(
agent,
obj.unbind(),
PropertyKey::Symbol(WellKnownSymbols::Iterator.into()),
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc())
};
let Some(method) = method else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Iterator method cannot be undefined",
gc.into_nogc(),
));
};
get_iterator_from_method(agent, scoped_obj.get(agent), method.unbind(), gc)
}
pub(crate) fn iterator_next<'a>(
agent: &mut Agent,
iterator_record: IteratorRecord,
mut value: Option<Value<'static>>,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, Object<'a>> {
let result = call_function(
agent,
iterator_record.next_method,
iterator_record.iterator.into(),
value.as_mut().map(ArgumentsList::from_mut_value),
gc.reborrow(),
)
.unbind()?;
let gc = gc.into_nogc();
let result = result.bind(gc);
result
.try_into()
.or(Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"The iterator result was not an object",
gc,
)))
}
pub(crate) fn iterator_complete<'a>(
agent: &mut Agent,
iter_result: Object,
gc: GcScope<'a, '_>,
) -> JsResult<'a, bool> {
let done = get(agent, iter_result, BUILTIN_STRING_MEMORY.done.into(), gc)?;
Ok(to_boolean(agent, done))
}
pub(crate) fn iterator_value<'a>(
agent: &mut Agent,
iter_result: Object,
gc: GcScope<'a, '_>,
) -> JsResult<'a, Value<'a>> {
get(agent, iter_result, BUILTIN_STRING_MEMORY.value.into(), gc)
}
pub(crate) fn iterator_step<'a>(
agent: &mut Agent,
iterator_record: IteratorRecord,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, Option<Object<'a>>> {
let result = iterator_next(agent, iterator_record, None, gc.reborrow())
.unbind()?
.bind(gc.nogc());
let scoped_result = result.scope(agent, gc.nogc());
let done = iterator_complete(agent, result.unbind(), gc.reborrow()).unbind()?;
if done {
return Ok(None);
}
Ok(Some(unsafe {
scoped_result.take(agent).bind(gc.into_nogc())
}))
}
pub(crate) fn iterator_step_value<'a>(
agent: &mut Agent,
iterator_record: IteratorRecord,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, Option<Value<'a>>> {
let result = iterator_next(agent, iterator_record, None, gc.reborrow());
let result = match result {
Err(err) => {
return Err(err.unbind());
}
Ok(result) => result.unbind().bind(gc.nogc()),
};
let scoped_result = result.scope(agent, gc.nogc());
let done = iterator_complete(agent, result.unbind(), gc.reborrow())
.unbind()
.bind(gc.nogc());
let result = unsafe { scoped_result.take(agent) }.bind(gc.nogc());
let done = match done {
Err(err) => {
return Err(err.unbind());
}
Ok(done) => done,
};
if done {
return Ok(None);
}
let value = get(
agent,
result.unbind(),
BUILTIN_STRING_MEMORY.value.into(),
gc,
);
value.map(Some)
}
pub(crate) fn iterator_close_with_value<'a>(
agent: &mut Agent,
iterator: Object,
completion: Value,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, Value<'a>> {
let mut iterator = iterator.bind(gc.nogc());
let completion = completion.scope(agent, gc.nogc());
let inner_result = if let Some(inner_result) = try_result_into_option_js(try_get_object_method(
agent,
iterator,
BUILTIN_STRING_MEMORY.r#return.into(),
gc.nogc(),
)) {
inner_result
} else {
let scoped_iterator = iterator.scope(agent, gc.nogc());
let inner_result = get_object_method(
agent,
iterator.unbind(),
BUILTIN_STRING_MEMORY.r#return.into(),
gc.reborrow(),
)
.unbind()
.bind(gc.nogc());
iterator = unsafe { scoped_iterator.take(agent) }.bind(gc.nogc());
inner_result
};
let inner_result = match inner_result {
Ok(return_function) => {
let Some(return_function) = return_function else {
return Ok(unsafe { completion.take(agent) });
};
call_function(
agent,
return_function.unbind(),
iterator.unbind().into(),
None,
gc.reborrow(),
)
.unbind()
.bind(gc.nogc())
}
Err(inner_result) => Err(inner_result),
};
let completion = unsafe { completion.take(agent) }.bind(gc.nogc());
let inner_result = inner_result.unbind()?.bind(gc.nogc());
if !inner_result.is_object() {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Invalid iterator 'return' method return value",
gc.into_nogc(),
));
}
Ok(completion.unbind())
}
pub(crate) fn iterator_close_with_error<'a>(
agent: &mut Agent,
iterator: Object,
completion: JsError,
mut gc: GcScope<'a, '_>,
) -> JsError<'a> {
let mut iterator = iterator.bind(gc.nogc());
let completion = completion.scope(agent, gc.nogc());
let inner_result = if let Some(inner_result) = try_result_into_option_js(try_get_object_method(
agent,
iterator,
BUILTIN_STRING_MEMORY.r#return.into(),
gc.nogc(),
)) {
inner_result
} else {
let scoped_iterator = iterator.scope(agent, gc.nogc());
let inner_result = get_object_method(
agent,
iterator.unbind(),
BUILTIN_STRING_MEMORY.r#return.into(),
gc.reborrow(),
)
.unbind()
.bind(gc.nogc());
iterator = unsafe { scoped_iterator.take(agent) }.bind(gc.nogc());
inner_result
};
if let Ok(Some(r#return)) = inner_result {
let _ = call_function(
agent,
r#return.unbind(),
iterator.unbind().into(),
None,
gc.reborrow(),
);
}
unsafe { completion.take(agent) }
}
macro_rules! if_abrupt_close_iterator {
($agent:ident, $value:ident, $iterator_record:ident, $gc:ident) => {
if let Err(err) = $value {
return Err(
crate::ecmascript::abstract_operations::iterator_close_with_error(
$agent,
$iterator_record.iterator.unbind(),
err.unbind(),
$gc,
),
);
} else if let Ok(value) = $value {
value.unbind().bind($gc.nogc())
} else {
unreachable!();
}
};
}
pub(crate) use if_abrupt_close_iterator;
pub(crate) fn create_iter_result_object<'a>(
agent: &mut Agent,
value: Value<'a>,
done: bool,
gc: NoGcScope<'a, '_>,
) -> JsResult<'a, OrdinaryObject<'a>> {
OrdinaryObject::create_object(
agent,
Some(
agent
.current_realm_record()
.intrinsics()
.object_prototype()
.into(),
),
&[
ObjectEntry {
key: PropertyKey::from(BUILTIN_STRING_MEMORY.value),
value: ObjectEntryPropertyDescriptor::Data {
value,
writable: true,
enumerable: true,
configurable: true,
},
},
ObjectEntry {
key: PropertyKey::from(BUILTIN_STRING_MEMORY.done),
value: ObjectEntryPropertyDescriptor::Data {
value: done.into(),
writable: true,
enumerable: true,
configurable: true,
},
},
],
)
.map_err(|err| agent.throw_allocation_exception(err, gc))
}
pub(crate) fn iterator_to_list<'a, 'b>(
agent: &mut Agent,
iterator_record: IteratorRecord,
mut gc: GcScope<'a, 'b>,
) -> JsResult<'a, ScopedCollection<'b, Vec<Value<'static>>>> {
let mut values = Vec::<Value>::new().scope(agent, gc.nogc());
while let Some(next) = iterator_step(agent, iterator_record, gc.reborrow())
.unbind()?
.bind(gc.nogc())
{
let next_value = iterator_value(agent, next.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
values.push(agent, next_value);
}
Ok(values)
}
impl HeapMarkAndSweep for IteratorRecord<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
let Self {
iterator,
next_method,
} = self;
iterator.mark_values(queues);
next_method.mark_values(queues);
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
let Self {
iterator,
next_method,
} = self;
iterator.sweep_values(compactions);
next_method.sweep_values(compactions);
}
}