use super::VaryingOperand;
use crate::{
Context, JsArgs, JsValue,
builtins::{
Promise, async_generator::AsyncGenerator, generator::GeneratorContext,
promise::PromiseCapability,
},
js_string,
native_function::NativeFunction,
object::FunctionObjectBuilder,
vm::{CompletionRecord, GeneratorResumeKind, opcode::Operation},
};
use boa_gc::Gc;
use std::{cell::Cell, ops::ControlFlow};
#[derive(Debug, Clone, Copy)]
pub(crate) struct Await;
impl Await {
#[inline(always)]
pub(super) fn operation(
value: VaryingOperand,
context: &mut Context,
) -> ControlFlow<CompletionRecord> {
let value = context.vm.get_register(value.into());
let promise = match Promise::promise_resolve(
&context.intrinsics().constructors().promise().constructor(),
value.clone(),
context,
) {
Ok(promise) => promise,
Err(err) => return context.handle_error(err),
};
let return_value = context
.vm
.stack
.get_promise_capability(&context.vm.frame)
.as_ref()
.map(PromiseCapability::promise)
.cloned()
.map(JsValue::from)
.unwrap_or_default();
let r#gen = GeneratorContext::from_current(context, None);
let captures = Gc::new(Cell::new(Some(r#gen)));
let on_fulfilled = FunctionObjectBuilder::new(
context.realm(),
NativeFunction::from_copy_closure_with_captures(
|_this, args, captures, context| {
let mut r#gen = captures.take().expect("should only run once");
let async_generator = r#gen.async_generator_object();
r#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(r#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 r#gen = captures.take().expect("should only run once");
let async_generator = r#gen.async_generator_object();
r#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(r#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);
context.handle_yield()
}
}
impl Operation for Await {
const NAME: &'static str = "Await";
const INSTRUCTION: &'static str = "INST - Await";
const COST: u8 = 5;
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct CreatePromiseCapability;
impl CreatePromiseCapability {
#[inline(always)]
pub(super) fn operation((): (), context: &mut Context) {
if context
.vm
.stack
.get_promise_capability(&context.vm.frame)
.is_some()
{
return;
}
let promise_capability = PromiseCapability::new(
&context.intrinsics().constructors().promise().constructor(),
context,
)
.expect("cannot fail per spec");
context
.vm
.stack
.set_promise_capability(&context.vm.frame, Some(&promise_capability));
}
}
impl Operation for CreatePromiseCapability {
const NAME: &'static str = "CreatePromiseCapability";
const INSTRUCTION: &'static str = "INST - CreatePromiseCapability";
const COST: u8 = 8;
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct CompletePromiseCapability;
impl CompletePromiseCapability {
#[inline(always)]
pub(super) fn operation((): (), context: &mut Context) -> ControlFlow<CompletionRecord> {
let Some(promise_capability) = context.vm.stack.get_promise_capability(&context.vm.frame)
else {
return if context.vm.pending_exception.is_some() {
context.handle_throw()
} else {
ControlFlow::Continue(())
};
};
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());
ControlFlow::Continue(())
}
}
impl Operation for CompletePromiseCapability {
const NAME: &'static str = "CompletePromiseCapability";
const INSTRUCTION: &'static str = "INST - CompletePromiseCapability";
const COST: u8 = 8;
}