///|
// Array.prototype.forEach is the first callback-bearing Array method whose
// loop is owned by the runtime activation coordinator. The frame keeps the
// observable algorithm state here; the executor only supplies the ordinary
// call request that selected this exact intrinsic.
///|
priv struct ExecutorArrayForEachRequest {
this_value : Value
args : Array[Value]
loc : @token.Loc
}
///|
fn ExecutorArrayForEachRequest::ExecutorArrayForEachRequest(
this_value~ : Value,
args~ : Array[Value],
loc~ : @token.Loc,
) -> ExecutorArrayForEachRequest {
{ this_value, args: args.copy(), loc, }
}
///|
// The setup-time realm capability is used only as an identity witness. No
// property lookup or name admission is performed on the call operand, so a
// replacement, sibling, or user-created "forEach" is excluded.
fn executor_array_for_each_identity_matches(
interp : Interpreter,
callee : Value,
) -> Bool {
let intrinsic = interp.realm_state.canonical_array_for_each.val
match (intrinsic, callee) {
(Some(Object(expected)), Object(actual)) => physical_equal(expected, actual)
_ => false
}
}
///|
fn executor_array_for_each_request(
interp : Interpreter,
callee : Value,
this_value : Value,
args : Array[Value],
loc : @token.Loc,
) -> ExecutorArrayForEachRequest? {
guard executor_array_for_each_identity_matches(interp, callee) else {
return None
}
Some(ExecutorArrayForEachRequest(this_value~, args~, loc~))
}
///|
priv enum ExecutorArrayForEachPhase {
ExecutorArrayForEachNeedObject
ExecutorArrayForEachAwaitLength(Value)
ExecutorArrayForEachHaveLengthValue(Value, Value)
ExecutorArrayForEachAwaitLengthPrimitive(Value, ExecutorToPrimitiveOperation)
ExecutorArrayForEachNeedCallback(Value, Int64)
ExecutorArrayForEachNeedIndex(Value, Int64, Value, Int64)
ExecutorArrayForEachAwaitHasProperty(
Value,
Int64,
Value,
Int64,
ExecutorDeleteHasPropertyOperation
)
ExecutorArrayForEachAwaitGet(Value, Int64, Value, Int64)
ExecutorArrayForEachHaveGetValue(Value, Int64, Value, Int64, Value)
ExecutorArrayForEachAwaitCallback(Value, Int64, Value, Int64)
ExecutorArrayForEachComplete
}
///|
priv struct ExecutorArrayForEachFrame {
request : ExecutorArrayForEachRequest
mut phase : ExecutorArrayForEachPhase
}
///|
fn ExecutorArrayForEachFrame::ExecutorArrayForEachFrame(
request : ExecutorArrayForEachRequest,
) -> ExecutorArrayForEachFrame {
{ request, phase: ExecutorArrayForEachNeedObject, }
}
///|
fn executor_array_for_each_length(
value : Value,
interp : Interpreter,
) -> Int64 raise Error {
to_length_from_value(Some(value), interp=Some(interp))
}
///|
fn ExecutorArrayForEachFrame::begin_length_value(
self : ExecutorArrayForEachFrame,
object : Value,
value : Value,
interp : Interpreter,
) -> ExecutorActivationStep raise Error {
if is_js_object(value) {
let operation = ExecutorToPrimitiveOperation(
value,
ExecutorToPrimitiveNumber,
self.request.loc,
)
match operation.step(interp) {
ExecutorActivationNormal(primitive) => {
self.phase = ExecutorArrayForEachNeedCallback(
object,
executor_array_for_each_length(primitive, interp),
)
self.step_frame(interp)
}
step => {
self.phase = ExecutorArrayForEachAwaitLengthPrimitive(object, operation)
step
}
}
} else {
self.phase = ExecutorArrayForEachNeedCallback(
object,
executor_array_for_each_length(value, interp),
)
self.step_frame(interp)
}
}
///|
fn ExecutorArrayForEachFrame::finish_has_property(
self : ExecutorArrayForEachFrame,
object : Value,
length : Int64,
callback : Value,
index : Int64,
has_property : Bool,
) -> ExecutorActivationStep {
if has_property {
self.phase = ExecutorArrayForEachAwaitGet(object, length, callback, index)
executor_activation_managed_property_get(
object,
index.to_string(),
self.request.loc,
)
} else {
self.phase = ExecutorArrayForEachNeedIndex(
object,
length,
callback,
index + 1L,
)
// Yield the next k to the activation coordinator. In particular, a
// sparse hole must not recurse through step_frame on the host stack.
ExecutorActivationContinue
}
}
///|
fn ExecutorArrayForEachFrame::step_frame(
self : ExecutorArrayForEachFrame,
interp : Interpreter,
) -> ExecutorActivationStep raise Error {
match self.phase {
ExecutorArrayForEachNeedObject => {
match self.request.this_value {
Null | Undefined =>
raise @errors.TypeError(
message="Cannot convert undefined or null to object",
)
_ => ()
}
let object = box_primitive_call_this(
self.request.this_value,
interp.realm_state,
)
self.phase = ExecutorArrayForEachAwaitLength(object)
executor_activation_managed_property_get(
object,
"length",
self.request.loc,
)
}
ExecutorArrayForEachAwaitLength(_) =>
raise @errors.InternalError(
message="forEach frame stepped while awaiting length",
)
ExecutorArrayForEachHaveLengthValue(object, value) =>
self.begin_length_value(object, value, interp)
ExecutorArrayForEachAwaitLengthPrimitive(object, operation) =>
match operation.step(interp) {
ExecutorActivationNormal(primitive) => {
self.phase = ExecutorArrayForEachNeedCallback(
object,
executor_array_for_each_length(primitive, interp),
)
self.step_frame(interp)
}
step => {
self.phase = ExecutorArrayForEachAwaitLengthPrimitive(
object, operation,
)
step
}
}
ExecutorArrayForEachNeedCallback(object, length) => {
let callback = if self.request.args.length() > 0 {
self.request.args[0]
} else {
Undefined
}
guard is_callable(callback) else {
raise @errors.TypeError(message="callback is not a function")
}
self.phase = ExecutorArrayForEachNeedIndex(object, length, callback, 0L)
self.step_frame(interp)
}
ExecutorArrayForEachNeedIndex(object, length, callback, index) =>
if index >= length {
self.phase = ExecutorArrayForEachComplete
ExecutorActivationNormal(Undefined)
} else {
// This is the sole loop-progress observation for this k. It happens
// before HasProperty and is not repeated when a child suspends.
interp.observe_execution_step()
let operation = ExecutorDeleteHasPropertyOperation(
target=object,
key=String_(index.to_string()),
loc=self.request.loc,
)
match operation.step(interp) {
ExecutorActivationNormal(Bool(has_property)) =>
self.finish_has_property(
object, length, callback, index, has_property,
)
step => {
self.phase = ExecutorArrayForEachAwaitHasProperty(
object, length, callback, index, operation,
)
step
}
}
}
ExecutorArrayForEachAwaitHasProperty(
object,
length,
callback,
index,
operation
) =>
match operation.step(interp) {
ExecutorActivationNormal(Bool(has_property)) =>
self.finish_has_property(
object, length, callback, index, has_property,
)
step => {
self.phase = ExecutorArrayForEachAwaitHasProperty(
object, length, callback, index, operation,
)
step
}
}
ExecutorArrayForEachAwaitGet(_, _, _, _) =>
raise @errors.InternalError(
message="forEach frame stepped while awaiting Get",
)
ExecutorArrayForEachHaveGetValue(object, length, callback, index, value) => {
let this_arg = if self.request.args.length() > 1 {
self.request.args[1]
} else {
Undefined
}
self.phase = ExecutorArrayForEachAwaitCallback(
object, length, callback, index,
)
ExecutorActivationCall(
ExecutorCallRequest(
callee=callback,
this_value=this_arg,
args=[value, Number(index.to_double()), object],
loc=self.request.loc,
),
)
}
ExecutorArrayForEachAwaitCallback(_, _, _, _) =>
raise @errors.InternalError(
message="forEach frame stepped while awaiting callback",
)
ExecutorArrayForEachComplete =>
raise @errors.InternalError(
message="forEach frame stepped after completion",
)
}
}
///|
impl ExecutorActivationFrame for ExecutorArrayForEachFrame with fn step(
self,
interp,
) {
self.step_frame(interp)
}
///|
impl ExecutorActivationFrame for ExecutorArrayForEachFrame with fn deliver_activation_completion(
self,
completion,
) {
match completion {
ExecutorActivationCompletionAbrupt(error) => raise error
ExecutorActivationCompletionReference(_) =>
raise @errors.InternalError(
message="array forEach received an unexpected binding reference completion",
)
ExecutorActivationCompletionNormal(value) =>
match self.phase {
ExecutorArrayForEachAwaitLength(object) =>
self.phase = ExecutorArrayForEachHaveLengthValue(object, value)
ExecutorArrayForEachAwaitLengthPrimitive(_, operation) =>
operation.deliver_activation_completion(
ExecutorActivationCompletionNormal(value),
)
ExecutorArrayForEachAwaitHasProperty(_, _, _, _, operation) =>
operation.deliver_activation_completion(
ExecutorActivationCompletionNormal(value),
)
ExecutorArrayForEachAwaitGet(object, length, callback, index) =>
self.phase = ExecutorArrayForEachHaveGetValue(
object, length, callback, index, value,
)
ExecutorArrayForEachAwaitCallback(object, length, callback, index) =>
// The callback result is intentionally discarded exactly once.
self.phase = ExecutorArrayForEachNeedIndex(
object,
length,
callback,
index + 1L,
)
ExecutorArrayForEachNeedObject
| ExecutorArrayForEachHaveLengthValue(_, _)
| ExecutorArrayForEachNeedCallback(_, _)
| ExecutorArrayForEachNeedIndex(_, _, _, _)
| ExecutorArrayForEachHaveGetValue(_, _, _, _, _)
| ExecutorArrayForEachComplete =>
raise @errors.InternalError(
message="forEach frame received an unexpected child completion",
)
}
}
}