use crate::{
builtins::iterable::create_iter_result_object,
context::intrinsics::Intrinsics,
environments::EnvironmentStack,
error::JsNativeError,
object::{JsObject, CONSTRUCTOR},
property::Attribute,
realm::Realm,
symbol::JsSymbol,
value::JsValue,
vm::{CallFrame, CompletionRecord, GeneratorResumeKind},
Context, JsArgs, JsError, JsResult,
};
use boa_gc::{custom_trace, Finalize, Trace};
use boa_profiler::Profiler;
use super::{BuiltInBuilder, IntrinsicObject};
#[derive(Debug, Clone, Finalize)]
pub(crate) enum GeneratorState {
SuspendedStart {
context: GeneratorContext,
},
SuspendedYield {
context: GeneratorContext,
},
Executing,
Completed,
}
unsafe impl Trace for GeneratorState {
custom_trace!(this, {
match &this {
Self::SuspendedStart { context } | Self::SuspendedYield { context } => mark(context),
Self::Executing | Self::Completed => {}
}
});
}
#[derive(Debug, Clone, Trace, Finalize)]
pub(crate) struct GeneratorContext {
pub(crate) environments: EnvironmentStack,
pub(crate) stack: Vec<JsValue>,
pub(crate) active_function: Option<JsObject>,
pub(crate) call_frame: Option<CallFrame>,
pub(crate) realm: Realm,
}
impl GeneratorContext {
pub(crate) fn new(
environments: EnvironmentStack,
stack: Vec<JsValue>,
active_function: Option<JsObject>,
call_frame: CallFrame,
realm: Realm,
) -> Self {
Self {
environments,
stack,
active_function,
call_frame: Some(call_frame),
realm,
}
}
pub(crate) fn from_current(context: &mut Context<'_>) -> Self {
Self {
environments: context.vm.environments.clone(),
call_frame: Some(context.vm.frame().clone()),
stack: context.vm.stack.clone(),
active_function: context.vm.active_function.clone(),
realm: context.realm().clone(),
}
}
pub(crate) fn resume(
&mut self,
value: Option<JsValue>,
resume_kind: GeneratorResumeKind,
context: &mut Context<'_>,
) -> CompletionRecord {
std::mem::swap(&mut context.vm.environments, &mut self.environments);
std::mem::swap(&mut context.vm.stack, &mut self.stack);
std::mem::swap(&mut context.vm.active_function, &mut self.active_function);
context.swap_realm(&mut self.realm);
context
.vm
.push_frame(self.call_frame.take().expect("should have a call frame"));
context.vm.frame_mut().generator_resume_kind = resume_kind;
if let Some(value) = value {
context.vm.push(value);
}
let result = context.run();
std::mem::swap(&mut context.vm.environments, &mut self.environments);
std::mem::swap(&mut context.vm.stack, &mut self.stack);
std::mem::swap(&mut context.vm.active_function, &mut self.active_function);
context.swap_realm(&mut self.realm);
self.call_frame = context.vm.pop_frame();
assert!(self.call_frame.is_some());
result
}
}
#[derive(Debug, Finalize, Trace)]
pub struct Generator {
pub(crate) state: GeneratorState,
}
impl IntrinsicObject for Generator {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(Self::NAME, "init");
BuiltInBuilder::with_intrinsic::<Self>(realm)
.prototype(
realm
.intrinsics()
.objects()
.iterator_prototypes()
.iterator(),
)
.static_method(Self::next, "next", 1)
.static_method(Self::r#return, "return", 1)
.static_method(Self::throw, "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: &str = "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(
gen: &JsValue,
value: JsValue,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
let Some(generator_obj) = gen.as_object() else {
return Err(
JsNativeError::typ()
.with_message("Generator method called on non generator")
.into()
);
};
let mut generator_obj_mut = generator_obj.borrow_mut();
let Some(generator) = generator_obj_mut.as_generator_mut() else {
return Err(
JsNativeError::typ()
.with_message("generator resumed on non generator object")
.into()
);
};
let (mut generator_context, first_execution) =
match std::mem::replace(&mut generator.state, GeneratorState::Executing) {
GeneratorState::Executing => {
return Err(JsNativeError::typ()
.with_message("Generator should not be executing")
.into());
}
GeneratorState::Completed => {
generator.state = GeneratorState::Completed;
return Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
));
}
GeneratorState::SuspendedStart { context } => (context, true),
GeneratorState::SuspendedYield { context } => (context, false),
};
drop(generator_obj_mut);
let record = generator_context.resume(
(!first_execution).then_some(value),
GeneratorResumeKind::Normal,
context,
);
let mut generator_obj_mut = generator_obj.borrow_mut();
let generator = generator_obj_mut
.as_generator_mut()
.expect("already checked this object type");
match record {
CompletionRecord::Return(value) => {
generator.state = GeneratorState::SuspendedYield {
context: generator_context,
};
Ok(value)
}
CompletionRecord::Normal(value) => {
generator.state = GeneratorState::Completed;
Ok(create_iter_result_object(value, true, context))
}
CompletionRecord::Throw(err) => {
generator.state = GeneratorState::Completed;
Err(err)
}
}
}
pub(crate) fn generator_resume_abrupt(
gen: &JsValue,
abrupt_completion: JsResult<JsValue>,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
let Some(generator_obj) = gen.as_object() else {
return Err(
JsNativeError::typ()
.with_message("Generator method called on non generator")
.into()
);
};
let mut generator_obj_mut = generator_obj.borrow_mut();
let Some(generator) = generator_obj_mut.as_generator_mut() else {
return Err(
JsNativeError::typ()
.with_message("generator resumed on non generator object")
.into()
);
};
let mut generator_context =
match std::mem::replace(&mut generator.state, GeneratorState::Executing) {
GeneratorState::Executing => {
return Err(JsNativeError::typ()
.with_message("Generator should not be executing")
.into());
}
GeneratorState::SuspendedStart { .. } | GeneratorState::Completed => {
generator.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(generator_obj_mut);
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 generator_obj_mut = generator_obj.borrow_mut();
let generator = generator_obj_mut
.as_generator_mut()
.expect("already checked this object type");
match record {
CompletionRecord::Return(value) => {
generator.state = GeneratorState::SuspendedYield {
context: generator_context,
};
Ok(value)
}
CompletionRecord::Normal(value) => {
generator.state = GeneratorState::Completed;
Ok(create_iter_result_object(value, true, context))
}
CompletionRecord::Throw(err) => {
generator.state = GeneratorState::Completed;
Err(err)
}
}
}
}