use crate::{
Context, JsArgs, JsData, JsError, JsResult, JsString,
builtins::iterable::create_iter_result_object,
context::intrinsics::Intrinsics,
error::JsNativeError,
js_string,
object::{CONSTRUCTOR, JsObject},
property::Attribute,
realm::Realm,
string::StaticJsStrings,
symbol::JsSymbol,
value::JsValue,
vm::{CallFrame, CallFrameFlags, CompletionRecord, GeneratorResumeKind, Stack},
};
use boa_gc::{Finalize, Trace, custom_trace};
use super::{BuiltInBuilder, IntrinsicObject};
#[derive(Debug, Finalize)]
pub(crate) enum GeneratorState {
SuspendedStart {
context: GeneratorContext,
},
SuspendedYield {
context: GeneratorContext,
},
Executing,
Completed,
}
unsafe impl Trace for GeneratorState {
custom_trace!(this, mark, {
match &this {
Self::SuspendedStart { context } | Self::SuspendedYield { context } => mark(context),
Self::Executing | Self::Completed => {}
}
});
}
#[derive(Debug, Trace, Finalize)]
pub(crate) struct GeneratorContext {
pub(crate) stack: Stack,
pub(crate) call_frame: Option<CallFrame>,
}
impl GeneratorContext {
pub(crate) fn from_current(context: &mut Context, async_generator: Option<JsObject>) -> Self {
let mut frame = context.vm.frame().clone();
frame.environments = context.vm.environments.clone();
frame.realm = context.realm().clone();
let mut stack = context.vm.stack.split_off_frame(&frame);
frame.rp = CallFrame::FUNCTION_PROLOGUE + frame.argument_count;
frame.flags |= CallFrameFlags::REGISTERS_ALREADY_PUSHED;
if let Some(async_generator) = async_generator {
stack.set_async_generator_object(&frame, async_generator);
}
Self {
call_frame: Some(frame),
stack,
}
}
pub(crate) fn resume(
&mut self,
value: Option<JsValue>,
resume_kind: GeneratorResumeKind,
context: &mut Context,
) -> CompletionRecord {
std::mem::swap(&mut context.vm.stack, &mut self.stack);
let frame = self.call_frame.take().expect("should have a call frame");
let rp = frame.rp;
context.vm.push_frame(frame);
let frame = context.vm.frame_mut();
frame.rp = rp;
frame.set_exit_early(true);
if let Some(value) = value {
context.vm.stack.push(value);
}
context.vm.stack.push(resume_kind);
let result = context.run();
std::mem::swap(&mut context.vm.stack, &mut self.stack);
self.call_frame = context.vm.pop_frame();
assert!(self.call_frame.is_some());
result
}
pub(crate) fn async_generator_object(&self) -> Option<JsObject> {
if let Some(frame) = &self.call_frame {
return self.stack.async_generator_object(frame);
}
None
}
}
#[derive(Debug, Finalize, Trace, JsData)]
pub struct Generator {
pub(crate) state: GeneratorState,
}
impl IntrinsicObject for Generator {
fn init(realm: &Realm) {
BuiltInBuilder::with_intrinsic::<Self>(realm)
.prototype(
realm
.intrinsics()
.objects()
.iterator_prototypes()
.iterator(),
)
.static_method(Self::next, js_string!("next"), 1)
.static_method(Self::r#return, js_string!("return"), 1)
.static_method(Self::throw, js_string!("throw"), 1)
.static_property(
JsSymbol::to_string_tag(),
Self::NAME,
Attribute::CONFIGURABLE,
)
.static_property(
CONSTRUCTOR,
realm
.intrinsics()
.constructors()
.generator_function()
.prototype(),
Attribute::CONFIGURABLE,
)
.build();
}
fn get(intrinsics: &Intrinsics) -> JsObject {
intrinsics.objects().generator()
}
}
impl Generator {
const NAME: JsString = StaticJsStrings::GENERATOR;
pub(crate) fn next(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
Self::generator_resume(this, args.get_or_undefined(0).clone(), context)
}
pub(crate) fn r#return(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
Self::generator_resume_abrupt(this, Ok(args.get_or_undefined(0).clone()), context)
}
pub(crate) fn throw(
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
Self::generator_resume_abrupt(
this,
Err(JsError::from_opaque(args.get_or_undefined(0).clone())),
context,
)
}
pub(crate) fn generator_resume(
r#gen: &JsValue,
value: JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
let Some(generator_obj) = r#gen.as_object() else {
return Err(JsNativeError::typ()
.with_message("Generator method called on non generator")
.into());
};
let mut r#gen = generator_obj.downcast_mut::<Self>().ok_or_else(|| {
JsNativeError::typ().with_message("generator resumed on non generator object")
})?;
let (mut generator_context, first_execution) =
match std::mem::replace(&mut r#gen.state, GeneratorState::Executing) {
GeneratorState::Executing => {
return Err(JsNativeError::typ()
.with_message("Generator should not be executing")
.into());
}
GeneratorState::Completed => {
r#gen.state = GeneratorState::Completed;
return Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
));
}
GeneratorState::SuspendedStart { context } => (context, true),
GeneratorState::SuspendedYield { context } => (context, false),
};
drop(r#gen);
let record = generator_context.resume(
(!first_execution).then_some(value),
GeneratorResumeKind::Normal,
context,
);
let mut r#gen = generator_obj
.downcast_mut::<Self>()
.expect("already checked this object type");
match record {
CompletionRecord::Return(value) => {
r#gen.state = GeneratorState::SuspendedYield {
context: generator_context,
};
Ok(value)
}
CompletionRecord::Normal(value) => {
r#gen.state = GeneratorState::Completed;
Ok(create_iter_result_object(value, true, context))
}
CompletionRecord::Throw(err) => {
r#gen.state = GeneratorState::Completed;
Err(err)
}
}
}
pub(crate) fn generator_resume_abrupt(
r#gen: &JsValue,
abrupt_completion: JsResult<JsValue>,
context: &mut Context,
) -> JsResult<JsValue> {
let Some(generator_obj) = r#gen.as_object() else {
return Err(JsNativeError::typ()
.with_message("Generator method called on non generator")
.into());
};
let mut r#gen = generator_obj.downcast_mut::<Self>().ok_or_else(|| {
JsNativeError::typ().with_message("generator resumed on non generator object")
})?;
let mut generator_context =
match std::mem::replace(&mut r#gen.state, GeneratorState::Executing) {
GeneratorState::Executing => {
return Err(JsNativeError::typ()
.with_message("Generator should not be executing")
.into());
}
GeneratorState::SuspendedStart { .. } | GeneratorState::Completed => {
r#gen.state = GeneratorState::Completed;
if let Ok(value) = abrupt_completion {
let value = create_iter_result_object(value, true, context);
return Ok(value);
}
return abrupt_completion;
}
GeneratorState::SuspendedYield { context } => context,
};
drop(r#gen);
let (value, resume_kind) = match abrupt_completion {
Ok(value) => (value, GeneratorResumeKind::Return),
Err(err) => (err.to_opaque(context), GeneratorResumeKind::Throw),
};
let record = generator_context.resume(Some(value), resume_kind, context);
let mut r#gen = generator_obj.downcast_mut::<Self>().ok_or_else(|| {
JsNativeError::typ().with_message("generator resumed on non generator object")
})?;
match record {
CompletionRecord::Return(value) => {
r#gen.state = GeneratorState::SuspendedYield {
context: generator_context,
};
Ok(value)
}
CompletionRecord::Normal(value) => {
r#gen.state = GeneratorState::Completed;
Ok(create_iter_result_object(value, true, context))
}
CompletionRecord::Throw(err) => {
r#gen.state = GeneratorState::Completed;
Err(err)
}
}
}
}