use std::cell::Cell;
use boa_gc::Gc;
use crate::{
builtins::{
async_generator::AsyncGenerator, generator::GeneratorContext, promise::PromiseCapability,
Promise,
},
js_string,
native_function::NativeFunction,
object::FunctionObjectBuilder,
vm::{opcode::Operation, CompletionType, GeneratorResumeKind},
Context, JsArgs, JsResult, JsValue,
};
#[derive(Debug, Clone, Copy)]
pub(crate) struct Await;
impl Operation for Await {
const NAME: &'static str = "Await";
const INSTRUCTION: &'static str = "INST - Await";
const COST: u8 = 5;
fn execute(context: &mut Context) -> JsResult<CompletionType> {
let value = context.vm.pop();
let promise = Promise::promise_resolve(
&context.intrinsics().constructors().promise().constructor(),
value,
context,
)?;
let return_value = context
.vm
.frame()
.promise_capability(&context.vm.stack)
.as_ref()
.map(PromiseCapability::promise)
.cloned()
.map(JsValue::from)
.unwrap_or_default();
let gen = GeneratorContext::from_current(context);
let captures = Gc::new(Cell::new(Some(gen)));
let on_fulfilled = FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_this, args, captures, context| {
let mut gen = captures.take().expect("should only run once");
let async_generator = gen.async_generator_object();
gen.resume(
Some(args.get_or_undefined(0).clone()),
GeneratorResumeKind::Normal,
context,
);
if let Some(async_generator) = async_generator {
async_generator
.downcast_mut::<AsyncGenerator>()
.expect("must be async generator")
.context = Some(gen);
}
Ok(JsValue::undefined())
},
captures.clone(),
),
)
.name(js_string!())
.length(1)
.build();
let on_rejected = FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_this, args, captures, context| {
let mut gen = captures.take().expect("should only run once");
let async_generator = gen.async_generator_object();
gen.resume(
Some(args.get_or_undefined(0).clone()),
GeneratorResumeKind::Throw,
context,
);
if let Some(async_generator) = async_generator {
async_generator
.downcast_mut::<AsyncGenerator>()
.expect("must be async generator")
.context = Some(gen);
}
Ok(JsValue::undefined())
},
captures,
),
)
.name(js_string!())
.length(1)
.build();
Promise::perform_promise_then(
&promise,
Some(on_fulfilled),
Some(on_rejected),
None,
context,
);
context.vm.set_return_value(return_value);
Ok(CompletionType::Yield)
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct CreatePromiseCapability;
impl Operation for CreatePromiseCapability {
const NAME: &'static str = "CreatePromiseCapability";
const INSTRUCTION: &'static str = "INST - CreatePromiseCapability";
const COST: u8 = 8;
fn execute(context: &mut Context) -> JsResult<CompletionType> {
if context
.vm
.frame()
.promise_capability(&context.vm.stack)
.is_some()
{
return Ok(CompletionType::Normal);
}
let promise_capability = PromiseCapability::new(
&context.intrinsics().constructors().promise().constructor(),
context,
)
.expect("cannot fail per spec");
context
.vm
.frame
.set_promise_capability(&mut context.vm.stack, Some(&promise_capability));
Ok(CompletionType::Normal)
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct CompletePromiseCapability;
impl Operation for CompletePromiseCapability {
const NAME: &'static str = "CompletePromiseCapability";
const INSTRUCTION: &'static str = "INST - CompletePromiseCapability";
const COST: u8 = 8;
fn execute(context: &mut Context) -> JsResult<CompletionType> {
let Some(promise_capability) = context.vm.frame().promise_capability(&context.vm.stack)
else {
return if context.vm.pending_exception.is_some() {
Ok(CompletionType::Throw)
} else {
Ok(CompletionType::Normal)
};
};
if let Some(error) = context.vm.pending_exception.take() {
promise_capability
.reject()
.call(&JsValue::undefined(), &[error.to_opaque(context)], context)
.expect("cannot fail per spec");
} else {
let return_value = context.vm.get_return_value();
promise_capability
.resolve()
.call(&JsValue::undefined(), &[return_value], context)
.expect("cannot fail per spec");
};
context
.vm
.set_return_value(promise_capability.promise().clone().into());
Ok(CompletionType::Normal)
}
}