pub(crate) mod yield_stm;
use std::collections::VecDeque;
use crate::{
builtins::{
async_generator::{AsyncGenerator, AsyncGeneratorState},
generator::{GeneratorContext, GeneratorState},
},
error::JsNativeError,
js_string,
object::PROTOTYPE,
vm::{
call_frame::GeneratorResumeKind,
opcode::{Operation, ReThrow},
CallFrame, CompletionType,
},
Context, JsError, JsObject, JsResult,
};
pub(crate) use yield_stm::*;
use super::SetReturnValue;
#[derive(Debug, Clone, Copy)]
pub(crate) struct Generator;
impl Operation for Generator {
const NAME: &'static str = "Generator";
const INSTRUCTION: &'static str = "INST - Generator";
const COST: u8 = 8;
fn execute(context: &mut Context) -> JsResult<CompletionType> {
let r#async = context.vm.read::<u8>() != 0;
let active_function = context.vm.frame().function(&context.vm);
let this_function_object =
active_function.expect("active function should be set to the generator");
let mut frame = GeneratorContext::from_current(context);
let proto = this_function_object
.get(PROTOTYPE, context)
.expect("generator must have a prototype property")
.as_object()
.map_or_else(
|| {
if r#async {
context.intrinsics().objects().async_generator()
} else {
context.intrinsics().objects().generator()
}
},
Clone::clone,
);
let generator = if r#async {
JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
proto,
AsyncGenerator {
state: AsyncGeneratorState::SuspendedStart,
context: None,
queue: VecDeque::new(),
},
)
} else {
JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
proto,
crate::builtins::generator::Generator {
state: GeneratorState::Completed,
},
)
};
if r#async {
let rp = frame
.call_frame
.as_ref()
.map_or(0, |frame| frame.rp as usize);
frame.stack[rp + CallFrame::ASYNC_GENERATOR_OBJECT_REGISTER_INDEX as usize] =
generator.clone().into();
let mut gen = generator
.downcast_mut::<AsyncGenerator>()
.expect("must be object here");
gen.context = Some(frame);
} else {
let mut gen = generator
.downcast_mut::<crate::builtins::generator::Generator>()
.expect("must be object here");
gen.state = GeneratorState::SuspendedStart { context: frame };
}
context.vm.set_return_value(generator.into());
Ok(CompletionType::Yield)
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct AsyncGeneratorClose;
impl Operation for AsyncGeneratorClose {
const NAME: &'static str = "AsyncGeneratorClose";
const INSTRUCTION: &'static str = "INST - AsyncGeneratorClose";
const COST: u8 = 8;
fn execute(context: &mut Context) -> JsResult<CompletionType> {
let generator = context
.vm
.frame()
.async_generator_object(&context.vm.stack)
.expect("There should be a object")
.downcast::<AsyncGenerator>()
.expect("must be async generator");
let mut gen = generator.borrow_mut();
gen.data.state = AsyncGeneratorState::DrainingQueue;
let return_value = context.vm.take_return_value();
let result = context
.vm
.pending_exception
.take()
.map_or(Ok(return_value), Err);
drop(gen);
AsyncGenerator::complete_step(&generator, result, true, None, context);
AsyncGenerator::drain_queue(&generator, context);
Ok(CompletionType::Normal)
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct GeneratorNext;
impl Operation for GeneratorNext {
const NAME: &'static str = "GeneratorNext";
const INSTRUCTION: &'static str = "INST - GeneratorNext";
const COST: u8 = 1;
fn execute(context: &mut Context) -> JsResult<CompletionType> {
let generator_resume_kind = context.vm.pop().to_generator_resume_kind();
match generator_resume_kind {
GeneratorResumeKind::Normal => Ok(CompletionType::Normal),
GeneratorResumeKind::Throw => Err(JsError::from_opaque(context.vm.pop())),
GeneratorResumeKind::Return => {
assert!(context.vm.pending_exception.is_none());
SetReturnValue::execute(context)?;
ReThrow::execute(context)
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct JumpIfNotResumeKind;
impl Operation for JumpIfNotResumeKind {
const NAME: &'static str = "JumpIfNotResumeKind";
const INSTRUCTION: &'static str = "INST - JumpIfNotResumeKind";
const COST: u8 = 1;
fn execute(context: &mut Context) -> JsResult<CompletionType> {
let exit = context.vm.read::<u32>();
let resume_kind = context.vm.read::<u8>();
let generator_resume_kind = context.vm.pop().to_generator_resume_kind();
context.vm.push(generator_resume_kind);
if generator_resume_kind as u8 != resume_kind {
context.vm.frame_mut().pc = exit;
}
Ok(CompletionType::Normal)
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct GeneratorDelegateNext;
impl Operation for GeneratorDelegateNext {
const NAME: &'static str = "GeneratorDelegateNext";
const INSTRUCTION: &'static str = "INST - GeneratorDelegateNext";
const COST: u8 = 18;
fn execute(context: &mut Context) -> JsResult<CompletionType> {
let throw_method_undefined = context.vm.read::<u32>();
let return_method_undefined = context.vm.read::<u32>();
let generator_resume_kind = context.vm.pop().to_generator_resume_kind();
let received = context.vm.pop();
let iterator_record = context
.vm
.frame_mut()
.iterators
.pop()
.expect("iterator stack should have at least an iterator");
match generator_resume_kind {
GeneratorResumeKind::Normal => {
let result = iterator_record.next_method().call(
&iterator_record.iterator().clone().into(),
&[received],
context,
)?;
context.vm.push(false);
context.vm.push(result);
context.vm.push(GeneratorResumeKind::Normal);
}
GeneratorResumeKind::Throw => {
let throw = iterator_record
.iterator()
.get_method(js_string!("throw"), context)?;
if let Some(throw) = throw {
let result = throw.call(
&iterator_record.iterator().clone().into(),
&[received],
context,
)?;
context.vm.push(false);
context.vm.push(result);
context.vm.push(GeneratorResumeKind::Normal);
} else {
let error = JsNativeError::typ()
.with_message("iterator does not have a throw method")
.to_opaque(context);
context.vm.push(error);
context.vm.frame_mut().pc = throw_method_undefined;
}
}
GeneratorResumeKind::Return => {
let r#return = iterator_record
.iterator()
.get_method(js_string!("return"), context)?;
if let Some(r#return) = r#return {
let result = r#return.call(
&iterator_record.iterator().clone().into(),
&[received],
context,
)?;
context.vm.push(true);
context.vm.push(result);
context.vm.push(GeneratorResumeKind::Normal);
} else {
context.vm.push(received);
context.vm.frame_mut().pc = return_method_undefined;
return Ok(CompletionType::Normal);
}
}
}
context.vm.frame_mut().iterators.push(iterator_record);
Ok(CompletionType::Normal)
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct GeneratorDelegateResume;
impl Operation for GeneratorDelegateResume {
const NAME: &'static str = "GeneratorDelegateResume";
const INSTRUCTION: &'static str = "INST - GeneratorDelegateResume";
const COST: u8 = 7;
fn execute(context: &mut Context) -> JsResult<CompletionType> {
let return_gen = context.vm.read::<u32>();
let exit = context.vm.read::<u32>();
let mut iterator = context
.vm
.frame_mut()
.iterators
.pop()
.expect("iterator stack should have at least an iterator");
let generator_resume_kind = context.vm.pop().to_generator_resume_kind();
let result = context.vm.pop();
let is_return = context.vm.pop().to_boolean();
if generator_resume_kind == GeneratorResumeKind::Throw {
return Err(JsError::from_opaque(result));
}
iterator.update_result(result, context)?;
if iterator.done() {
let value = iterator.value(context)?;
context.vm.push(value);
context.vm.frame_mut().pc = if is_return { return_gen } else { exit };
return Ok(CompletionType::Normal);
}
context.vm.frame_mut().iterators.push(iterator);
Ok(CompletionType::Normal)
}
}