use crate::fuel::Fuel;
use crate::heap::Heap;
use crate::outcome::{EvalResult, Outcome};
use crate::value::{PromiseId, Value};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PromiseState {
Pending(Vec<PromiseHandler>),
Resolved(Value),
Rejected(Value),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PromiseHandler {
on_resolve: Option<Value>,
on_reject: Option<Value>,
chained: PromiseId,
}
impl PromiseHandler {
#[must_use]
pub fn new(on_resolve: Option<Value>, on_reject: Option<Value>, chained: PromiseId) -> Self {
Self {
on_resolve,
on_reject,
chained,
}
}
#[must_use]
pub fn on_resolve(&self) -> Option<&Value> {
self.on_resolve.as_ref()
}
#[must_use]
pub fn on_reject(&self) -> Option<&Value> {
self.on_reject.as_ref()
}
#[must_use]
pub fn chained(&self) -> PromiseId {
self.chained
}
}
#[allow(clippy::needless_pass_by_value)]
pub fn then_impl(args: Vec<Value>, this: Value, heap: Heap, fuel: Fuel) -> EvalResult {
match promise_id_of(&this) {
Some(promise_id) => {
let on_resolve = args.first().cloned();
let on_reject = args.get(1).cloned();
settle(promise_id, on_resolve, on_reject, heap, fuel)
}
None => Ok((
Outcome::Throw(type_error("Promise.prototype.then called on non-promise")),
heap,
fuel,
)),
}
}
#[allow(clippy::needless_pass_by_value)]
pub fn catch_impl(args: Vec<Value>, this: Value, heap: Heap, fuel: Fuel) -> EvalResult {
match promise_id_of(&this) {
Some(promise_id) => {
let on_reject = args.first().cloned();
settle(promise_id, None, on_reject, heap, fuel)
}
None => Ok((
Outcome::Throw(type_error("Promise.prototype.catch called on non-promise")),
heap,
fuel,
)),
}
}
fn promise_id_of(value: &Value) -> Option<PromiseId> {
match value {
Value::Promise(id) => Some(*id),
Value::Undefined
| Value::Null
| Value::Boolean(_)
| Value::Number(_)
| Value::String(_)
| Value::Object(_)
| Value::Function(_)
| Value::Native(_) => None,
}
}
fn settle(
promise_id: PromiseId,
on_resolve: Option<Value>,
on_reject: Option<Value>,
heap: Heap,
fuel: Fuel,
) -> EvalResult {
match heap.promise(promise_id).cloned() {
Some(PromiseState::Resolved(value)) => fire_resolve_handler(value, on_resolve, heap, fuel),
Some(PromiseState::Rejected(value)) => fire_reject_handler(value, on_reject, heap, fuel),
Some(PromiseState::Pending(handlers)) => {
queue_handler(promise_id, handlers, on_resolve, on_reject, heap, fuel)
}
None => Ok((
Outcome::Throw(type_error("promise missing from heap")),
heap,
fuel,
)),
}
}
fn fire_resolve_handler(
value: Value,
on_resolve: Option<Value>,
heap: Heap,
fuel: Fuel,
) -> EvalResult {
match on_resolve {
Some(callback) if is_callable(&callback) => {
crate::expression::call_function(&callback, &Value::Undefined, vec![value], heap, fuel)
.and_then(|(outcome, heap, fuel)| chain_outcome(outcome, heap, fuel))
}
Some(_) | None => alloc_resolved_value(value, heap, fuel),
}
}
fn fire_reject_handler(
value: Value,
on_reject: Option<Value>,
heap: Heap,
fuel: Fuel,
) -> EvalResult {
match on_reject {
Some(callback) if is_callable(&callback) => {
crate::expression::call_function(&callback, &Value::Undefined, vec![value], heap, fuel)
.and_then(|(outcome, heap, fuel)| chain_outcome(outcome, heap, fuel))
}
Some(_) | None => alloc_rejected_value(value, heap, fuel),
}
}
#[allow(clippy::unnecessary_wraps)] fn queue_handler(
source: PromiseId,
existing_handlers: Vec<PromiseHandler>,
on_resolve: Option<Value>,
on_reject: Option<Value>,
heap: Heap,
fuel: Fuel,
) -> EvalResult {
let (chained, heap) = heap.alloc_promise(PromiseState::Pending(Vec::new()));
let handler = PromiseHandler::new(on_resolve, on_reject, chained);
let extended_handlers: Vec<PromiseHandler> = existing_handlers
.into_iter()
.chain(std::iter::once(handler))
.collect();
let heap = heap
.store_promise(source, PromiseState::Pending(extended_handlers))
.unwrap_or_else(|h| h);
Ok((Outcome::Normal(Value::Promise(chained)), heap, fuel))
}
fn chain_outcome(outcome: Outcome, heap: Heap, fuel: Fuel) -> EvalResult {
match outcome {
Outcome::Normal(value) => alloc_resolved_value(value, heap, fuel),
Outcome::Throw(value) => alloc_rejected_value(value, heap, fuel),
}
}
#[allow(clippy::unnecessary_wraps)] fn alloc_resolved_value(value: Value, heap: Heap, fuel: Fuel) -> EvalResult {
let (id, heap) = heap.alloc_promise(PromiseState::Resolved(value));
Ok((Outcome::Normal(Value::Promise(id)), heap, fuel))
}
#[allow(clippy::unnecessary_wraps)] fn alloc_rejected_value(value: Value, heap: Heap, fuel: Fuel) -> EvalResult {
let (id, heap) = heap.alloc_promise(PromiseState::Rejected(value));
Ok((Outcome::Normal(Value::Promise(id)), heap, fuel))
}
fn is_callable(value: &Value) -> bool {
matches!(value, Value::Function(_) | Value::Native(_))
}
fn type_error(message: &str) -> Value {
Value::String(format!("TypeError: {message}"))
}