Files
ask262/engine262/src/runtime-semantics/LabelledEvaluation.mts
T

754 lines
36 KiB
TypeScript

import { surroundingAgent } from '../host-defined/engine.mts';
import {
JSStringValue, ObjectValue, ReferenceRecord, Value,
} from '../value.mts';
import {
Evaluate, type Evaluator, type PlainEvaluator, type StatementEvaluator,
} from '../evaluator.mts';
import {
BoundNames,
IsConstantDeclaration,
IsDestructuring,
StringValue,
type DestructuringParseNode,
} from '../static-semantics/all.mts';
import { CreateForInIterator, type ForInIteratorInstance } from '../intrinsics/ForInIteratorPrototype.mts';
import {
Completion,
NormalCompletion,
AbruptCompletion,
UpdateEmpty,
EnsureCompletion,
Await,
Q, X,
type PlainCompletion,
BreakCompletion,
} from '../completion.mts';
import { JSStringSet, OutOfRange } from '../helpers.mts';
import type { ParseNode } from '../parser/ParseNode.mts';
import {
Evaluate_SwitchStatement,
Evaluate_VariableDeclarationList,
BindingInitialization,
DestructuringAssignmentEvaluation,
refineLeftHandSideExpression,
} from './all.mts';
import {
Assert,
Call,
GetIterator,
GetValue,
PutValue,
GetV,
ResolveBinding,
InitializeReferencedBinding,
IteratorComplete,
IteratorValue,
IteratorClose,
AsyncIteratorClose,
ToBoolean,
ToObject,
SameValue,
type IteratorRecord,
} from '#self';
import { DeclarativeEnvironmentRecord } from '#self';
/** https://tc39.es/ecma262/#sec-loopcontinues */
function LoopContinues(completion: Completion<Value | void>, labelSet: JSStringSet) {
// 1. If completion.[[Type]] is normal, return true.
if (completion.Type === 'normal') {
return Value.true;
}
// 2. If completion.[[Type]] is not continue, return false.
if (completion.Type !== 'continue') {
return Value.false;
}
// 3. If completion.[[Target]] is empty, return true.
if (completion.Target === undefined) {
return Value.true;
}
// 4. If completion.[[Target]] is an element of labelSet, return true.
if (labelSet.has(completion.Target)) {
return Value.true;
}
// 5. Return false.
return Value.false;
}
export function LabelledEvaluation(node: ParseNode.LabelledStatement | ParseNode.BreakableStatement, labelSet: JSStringSet): StatementEvaluator {
switch (node.type) {
case 'DoWhileStatement':
case 'WhileStatement':
case 'ForStatement':
case 'ForInStatement':
case 'ForOfStatement':
case 'ForAwaitStatement':
case 'SwitchStatement':
return LabelledEvaluation_BreakableStatement(node, labelSet);
case 'LabelledStatement':
return LabelledEvaluation_LabelledStatement(node, labelSet);
default:
throw new OutOfRange('LabelledEvaluation', node);
}
}
/** https://tc39.es/ecma262/#sec-labelled-statements-runtime-semantics-labelledevaluation */
// LabelledStatement : LabelIdentifier `:` LabelledItem
function* LabelledEvaluation_LabelledStatement({ LabelIdentifier, LabelledItem }: ParseNode.LabelledStatement, labelSet: JSStringSet) {
// 1. Let label be the StringValue of LabelIdentifier.
const label = StringValue(LabelIdentifier);
// 2. Append label as an element of labelSet.
labelSet.add(label);
// 3. Let stmtResult be LabelledEvaluation of LabelledItem with argument labelSet.
let stmtResult = EnsureCompletion(yield* LabelledEvaluation_LabelledItem(LabelledItem, labelSet)) as Completion<Value | void>;
// 4. If stmtResult.[[Type]] is break and SameValue(stmtResult.[[Target]], label) is true, then
if (stmtResult.Type === 'break' && SameValue(stmtResult.Target!, label) === Value.true) {
// a. Set stmtResult to NormalCompletion(stmtResult.[[Value]]).
stmtResult = NormalCompletion(stmtResult.Value);
}
// 5. Return Completion(stmtResult).
return Completion(stmtResult);
}
// LabelledItem :
// Statement
// FunctionDeclaration
function LabelledEvaluation_LabelledItem(LabelledItem: ParseNode.LabelledItem, labelSet: JSStringSet) {
switch (LabelledItem.type) {
case 'DoWhileStatement':
case 'WhileStatement':
case 'ForStatement':
case 'ForInStatement':
case 'ForOfStatement':
case 'SwitchStatement':
case 'LabelledStatement':
return LabelledEvaluation(LabelledItem, labelSet);
default:
return Evaluate(LabelledItem);
}
}
/** https://tc39.es/ecma262/#sec-statement-semantics-runtime-semantics-labelledevaluation */
// BreakableStatement :
// IterationStatement
// SwitchStatement
//
// IterationStatement :
// (DoWhileStatement)
// (WhileStatement)
function* LabelledEvaluation_BreakableStatement(BreakableStatement: ParseNode.BreakableStatement, labelSet: JSStringSet): StatementEvaluator {
switch (BreakableStatement.type) {
case 'DoWhileStatement':
case 'WhileStatement':
case 'ForStatement':
case 'ForInStatement':
case 'ForOfStatement':
case 'ForAwaitStatement': {
// 1. Let stmtResult be LabelledEvaluation of IterationStatement with argument labelSet.
let stmtResult = EnsureCompletion(yield* LabelledEvaluation_IterationStatement(BreakableStatement, labelSet));
// 2. If stmtResult.[[Type]] is break, then
if (stmtResult.Type === 'break') {
// a. If stmtResult.[[Target]] is empty, then
if (stmtResult.Target === undefined) {
// i. If stmtResult.[[Value]] is empty, set stmtResult to NormalCompletion(undefined).
if (stmtResult.Value === undefined) {
stmtResult = NormalCompletion(Value.undefined);
} else { // ii. Else, set stmtResult to NormalCompletion(stmtResult.[[Value]]).
stmtResult = NormalCompletion(stmtResult.Value);
}
}
}
// 3. Return Completion(stmtResult).
return Completion(stmtResult);
}
case 'SwitchStatement': {
// 1. Let stmtResult be LabelledEvaluation of SwitchStatement.
let stmtResult = EnsureCompletion(yield* Evaluate_SwitchStatement(BreakableStatement));
// 2. If stmtResult.[[Type]] is break, then
if (stmtResult.Type === 'break') {
// a. If stmtResult.[[Target]] is empty, then
if (stmtResult.Target === undefined) {
// i. If stmtResult.[[Value]] is empty, set stmtResult to NormalCompletion(undefined).
if (stmtResult.Value === undefined) {
stmtResult = NormalCompletion(Value.undefined);
} else { // ii. Else, set stmtResult to NormalCompletion(stmtResult.[[Value]]).
stmtResult = NormalCompletion(stmtResult.Value);
}
}
}
// 3. Return Completion(stmtResult).
return Completion(stmtResult) as Completion<Value | void>;
}
default:
throw new OutOfRange('LabelledEvaluation_BreakableStatement', BreakableStatement);
}
}
function LabelledEvaluation_IterationStatement(IterationStatement: ParseNode.IterationStatement, labelSet: JSStringSet): StatementEvaluator {
switch (IterationStatement.type) {
case 'DoWhileStatement':
return LabelledEvaluation_IterationStatement_DoWhileStatement(IterationStatement, labelSet);
case 'WhileStatement':
return LabelledEvaluation_IterationStatement_WhileStatement(IterationStatement, labelSet);
case 'ForStatement':
return LabelledEvaluation_BreakableStatement_ForStatement(IterationStatement, labelSet);
case 'ForInStatement':
return LabelledEvaluation_IterationStatement_ForInStatement(IterationStatement, labelSet);
case 'ForOfStatement':
return LabelledEvaluation_IterationStatement_ForOfStatement(IterationStatement, labelSet);
case 'ForAwaitStatement':
return LabelledEvaluation_IterationStatement_ForAwaitStatement(IterationStatement, labelSet);
default:
throw new OutOfRange('LabelledEvaluation_IterationStatement', IterationStatement);
}
}
/** https://tc39.es/ecma262/#sec-do-while-statement-runtime-semantics-labelledevaluation */
// IterationStatement :
// `do` Statement `while` `(` Expression `)` `;`
function* LabelledEvaluation_IterationStatement_DoWhileStatement({ Statement, Expression }: ParseNode.DoWhileStatement, labelSet: JSStringSet) {
// 1. Let V be undefined.
let V: Value = Value.undefined;
// 2. Repeat,
while (true) {
// a. Let stmtResult be the result of evaluating Statement.
const stmtResult = EnsureCompletion(yield* Evaluate(Statement)) as Completion<Value | void>;
// b. If LoopContinues(stmtResult, labelSet) is false, return Completion(UpdateEmpty(stmtResult, V)).
if (LoopContinues(stmtResult, labelSet) === Value.false) {
return Completion(UpdateEmpty(stmtResult, V));
}
// c. If stmtResult.[[Value]] is not empty, set V to stmtResult.[[Value]].
if (stmtResult.Value !== undefined) {
V = stmtResult.Value;
}
// d. Let exprRef be the result of evaluating Expression.
const exprRef = Q(yield* Evaluate(Expression));
// e. Let exprValue be ? GetValue(exprRef).
const exprValue = Q(yield* GetValue(exprRef));
// f. If ! ToBoolean(exprValue) is false, return NormalCompletion(V).
if (X(ToBoolean(exprValue)) === Value.false) {
return NormalCompletion(V);
}
}
}
/** https://tc39.es/ecma262/#sec-while-statement-runtime-semantics-labelledevaluation */
// IterationStatement :
// `while` `(` Expression `)` Statement
function* LabelledEvaluation_IterationStatement_WhileStatement({ Expression, Statement }: ParseNode.WhileStatement, labelSet: JSStringSet) {
// 1. Let V be undefined.
let V: Value = Value.undefined;
// 2. Repeat,
while (true) {
// a. Let exprRef be the result of evaluating Expression.
const exprRef = Q(yield* Evaluate(Expression));
// b. Let exprValue be ? GetValue(exprRef).
const exprValue = Q(yield* GetValue(exprRef));
// c. If ! ToBoolean(exprValue) is false, return NormalCompletion(V).
if (X(ToBoolean(exprValue)) === Value.false) {
return NormalCompletion(V);
}
// d. Let stmtResult be the result of evaluating Statement.
const stmtResult = EnsureCompletion(yield* Evaluate(Statement));
// e. If LoopContinues(stmtResult, labelSet) is false, return Completion(UpdateEmpty(stmtResult, V)).
if (LoopContinues(stmtResult, labelSet) === Value.false) {
return Completion(UpdateEmpty(stmtResult, V));
}
// f. If stmtResult.[[Value]] is not empty, set V to stmtResult.[[Value]].
if (stmtResult.Value !== undefined) {
V = stmtResult.Value;
}
}
}
/** https://tc39.es/ecma262/#sec-for-statement-runtime-semantics-labelledevaluation */
// IterationStatement :
// `for` `(` Expression? `;` Expression? `;` Expresssion? `)` Statement
// `for` `(` `var` VariableDeclarationList `;` Expression? `;` Expression? `)` Statement
// `for` `(` LexicalDeclaration Expression? `;` Expression? `)` Statement
function* LabelledEvaluation_BreakableStatement_ForStatement(ForStatement: ParseNode.ForStatement, labelSet: JSStringSet) {
const {
VariableDeclarationList, LexicalDeclaration,
Expression_a, Expression_b, Expression_c,
Statement,
} = ForStatement;
switch (true) {
case !!LexicalDeclaration: {
// 1. Let oldEnv be the running execution context's LexicalEnvironment.
const oldEnv = surroundingAgent.runningExecutionContext.LexicalEnvironment;
// 2. Let loopEnv be NewDeclarativeEnvironment(oldEnv).
const loopEnv = new DeclarativeEnvironmentRecord(oldEnv);
// 3. Let isConst be IsConstantDeclaration of LexicalDeclaration.
const isConst = IsConstantDeclaration(LexicalDeclaration);
// 4. Let boundNames be the BoundNames of LexicalDeclaration.
const boundNames = BoundNames(LexicalDeclaration);
// 5. For each element dn of boundNames, do
for (const dn of boundNames) {
// a. If isConst is true, then
if (isConst) {
// i. Perform ! loopEnv.CreateImmutableBinding(dn, true).
X(loopEnv.CreateImmutableBinding(dn, Value.true));
} else { // b. Else,
// i. Perform ! loopEnv.CreateMutableBinding(dn, false).
X(loopEnv.CreateMutableBinding(dn, Value.false));
}
}
// 6. Set the running execution context's LexicalEnvironment to loopEnv.
surroundingAgent.runningExecutionContext.LexicalEnvironment = loopEnv;
// 7. Let forDcl be the result of evaluating LexicalDeclaration.
const forDcl = yield* Evaluate(LexicalDeclaration);
// 8. If forDcl is an abrupt completion, then
if (forDcl instanceof AbruptCompletion) {
// a. Set the running execution context's LexicalEnvironment to oldEnv.
surroundingAgent.runningExecutionContext.LexicalEnvironment = oldEnv;
// b. Return Completion(forDcl).
return Completion(forDcl);
}
// 9. If isConst is false, let perIterationLets be boundNames; otherwise let perIterationLets be « ».
let perIterationLets: JSStringValue[];
if (isConst === false) {
perIterationLets = boundNames;
} else {
perIterationLets = [];
}
// 10. Let bodyResult be ForBodyEvaluation(the first Expression, the second Expression, Statement, perIterationLets, labelSet).
const bodyResult = yield* ForBodyEvaluation(Expression_a, Expression_b, Statement, perIterationLets, labelSet);
// 11. Set the running execution context's LexicalEnvironment to oldEnv.
surroundingAgent.runningExecutionContext.LexicalEnvironment = oldEnv;
// 12. Return Completion(bodyResult).
return Completion(bodyResult);
}
case !!VariableDeclarationList: {
// 1. Let varDcl be the result of evaluating VariableDeclarationList.
const varDcl = yield* Evaluate_VariableDeclarationList(VariableDeclarationList);
Q(varDcl);
// 3. Return ? ForBodyEvaluation(the first Expression, the second Expression, Statement, « », labelSet).
return Q(yield* ForBodyEvaluation(Expression_a, Expression_b, Statement, [], labelSet));
}
default: {
// 1. If the first Expression is present, then
if (Expression_a) {
// a. Let exprRef be the result of evaluating the first Expression.
const exprRef = Q(yield* Evaluate(Expression_a));
// b. Perform ? GetValue(exprRef).
Q(yield* GetValue(exprRef));
}
// 2. Return ? ForBodyEvaluation(the second Expression, the third Expression, Statement, « », labelSet).
return Q(yield* ForBodyEvaluation(Expression_b, Expression_c, Statement, [], labelSet));
}
}
}
function* LabelledEvaluation_IterationStatement_ForInStatement(ForInStatement: ParseNode.ForInStatement, labelSet: JSStringSet): StatementEvaluator {
const {
LeftHandSideExpression,
ForBinding,
ForDeclaration,
Expression,
Statement,
} = ForInStatement;
switch (true) {
case !!LeftHandSideExpression && !!Expression: {
// IterationStatement : `for` `(` LeftHandSideExpression `in` Expression `)` Statement
// 1. Let keyResult be ? ForIn/OfHeadEvaluation(« », Expression, enumerate).
const keyResult = Q(yield* ForInOfHeadEvaluation([], Expression, 'enumerate'));
// 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement, keyResult, enumerate, assignment, labelSet).
return Q(yield* ForInOfBodyEvaluation(LeftHandSideExpression, Statement, keyResult as IteratorRecord, 'enumerate', 'assignment', labelSet));
}
case !!ForBinding && !!Expression: {
// IterationStatement :`for` `(` `var` ForBinding `in` Expression `)` Statement
// 1. Let keyResult be ? ForIn/OfHeadEvaluation(« », Expression, enumerate).
const keyResult = Q(yield* ForInOfHeadEvaluation([], Expression, 'enumerate'));
// 2. Return ? ForIn/OfBodyEvaluation(ForBinding, Statement, keyResult, enumerate, varBinding, labelSet).
return Q(yield* ForInOfBodyEvaluation(ForBinding, Statement, keyResult as IteratorRecord, 'enumerate', 'varBinding', labelSet));
}
case !!ForDeclaration && !!Expression: {
// IterationStatement : `for` `(` ForDeclaration `in` Expression `)` Statement
// 1. Let keyResult be ? ForIn/OfHeadEvaluation(BoundNames of ForDeclaration, Expression, enumerate).
const keyResult = Q(yield* ForInOfHeadEvaluation(BoundNames(ForDeclaration), Expression, 'enumerate'));
// 2. Return ? ForIn/OfBodyEvaluation(ForDeclaration, Statement, keyResult, enumerate, lexicalBinding, labelSet).
return Q(yield* ForInOfBodyEvaluation(ForDeclaration, Statement, keyResult as IteratorRecord, 'enumerate', 'lexicalBinding', labelSet));
}
default:
throw new OutOfRange('LabelledEvaluation_IterationStatement_ForInStatement', ForInStatement);
}
}
// IterationStatement :
// `for` `await` `(` LeftHandSideExpression `of` AssignmentExpression `)` Statement
// `for` `await` `(` `var` ForBinding `of` AssignmentExpression `)` Statement
// `for` `await` `(` ForDeclaration`of` AssignmentExpression `)` Statement
function* LabelledEvaluation_IterationStatement_ForAwaitStatement(ForAwaitStatement: ParseNode.ForAwaitStatement, labelSet: JSStringSet): StatementEvaluator {
const {
LeftHandSideExpression,
ForBinding,
ForDeclaration,
AssignmentExpression,
Statement,
} = ForAwaitStatement;
switch (true) {
case !!LeftHandSideExpression: {
// 1. Let keyResult be ? ForIn/OfHeadEvaluation(« », AssignmentExpression, async-iterate).
const keyResult = Q(yield* ForInOfHeadEvaluation([], AssignmentExpression, 'async-iterate'));
// 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement, keyResult, iterate, assignment, labelSet, async).
return Q(yield* ForInOfBodyEvaluation(LeftHandSideExpression, Statement, keyResult as IteratorRecord, 'iterate', 'assignment', labelSet, 'async'));
}
case !!ForBinding: {
// 1. Let keyResult be ? ForIn/OfHeadEvaluation(« », AssignmentExpression, async-iterate).
const keyResult = Q(yield* ForInOfHeadEvaluation([], AssignmentExpression, 'async-iterate'));
// 2. Return ? ForIn/OfBodyEvaluation(ForBinding, Statement, keyResult, iterate, varBinding, labelSet, async).
return Q(yield* ForInOfBodyEvaluation(ForBinding, Statement, keyResult as IteratorRecord, 'iterate', 'varBinding', labelSet, 'async'));
}
case !!ForDeclaration: {
// 1. Let keyResult be ? ForIn/OfHeadEvaluation(BoundNames of ForDeclaration, AssignmentExpression, async-iterate).
const keyResult = Q(yield* ForInOfHeadEvaluation(BoundNames(ForDeclaration), AssignmentExpression, 'async-iterate'));
// 2. Return ? ForIn/OfBodyEvaluation(ForDeclaration, Statement, keyResult, iterate, lexicalBinding, labelSet, async).
return Q(yield* ForInOfBodyEvaluation(ForDeclaration, Statement, keyResult as IteratorRecord, 'iterate', 'lexicalBinding', labelSet, 'async'));
}
default:
throw new OutOfRange('LabelledEvaluation_IterationStatement_ForAwaitStatement', ForAwaitStatement);
}
}
/** https://tc39.es/ecma262/#sec-for-in-and-for-of-statements-runtime-semantics-labelledevaluation */
// IterationStatement :
// `for` `(` LeftHandSideExpression `of` AssignmentExpression `)` Statement
// `for` `(` `var` ForBinding `of` AssignmentExpression `)` Statement
// `for` `(` ForDeclaration `of` AssignmentExpression `)` Statement
function* LabelledEvaluation_IterationStatement_ForOfStatement(ForOfStatement: ParseNode.ForOfStatement, labelSet: JSStringSet): StatementEvaluator {
const {
LeftHandSideExpression,
ForBinding,
ForDeclaration,
AssignmentExpression,
Statement,
} = ForOfStatement;
switch (true) {
case !!LeftHandSideExpression: {
// 1. Let keyResult be ? ForIn/OfHeadEvaluation(« », AssignmentExpression, iterate).
const keyResult = Q(yield* ForInOfHeadEvaluation([], AssignmentExpression, 'iterate'));
// 2. Return ? ForIn/OfBodyEvaluation(LeftHandSideExpression, Statement, keyResult, iterate, assignment, labelSet).
return Q(yield* ForInOfBodyEvaluation(LeftHandSideExpression, Statement, keyResult as IteratorRecord, 'iterate', 'assignment', labelSet));
}
case !!ForBinding: {
// 1. Let keyResult be ? ForIn/OfHeadEvaluation(« », AssignmentExpression, iterate).
const keyResult = Q(yield* ForInOfHeadEvaluation([], AssignmentExpression, 'iterate'));
// 2. Return ? ForIn/OfBodyEvaluation(ForBinding, Statement, keyResult, iterate, varBinding, labelSet).
return Q(yield* ForInOfBodyEvaluation(ForBinding, Statement, keyResult as IteratorRecord, 'iterate', 'varBinding', labelSet));
}
case !!ForDeclaration: {
// 1. Let keyResult be ? ForIn/OfHeadEvaluation(BoundNames of ForDeclaration, AssignmentExpression, iterate).
const keyResult = Q(yield* ForInOfHeadEvaluation(BoundNames(ForDeclaration), AssignmentExpression, 'iterate'));
// 2. Return ? ForIn/OfBodyEvaluation(ForDeclaration, Statement, keyResult, iterate, lexicalBinding, labelSet).
return Q(yield* ForInOfBodyEvaluation(ForDeclaration, Statement, keyResult as IteratorRecord, 'iterate', 'lexicalBinding', labelSet));
}
default:
throw new OutOfRange('LabelledEvaluation_BreakableStatement_ForOfStatement', ForOfStatement);
}
}
/** https://tc39.es/ecma262/#sec-forbodyevaluation */
function* ForBodyEvaluation(test: ParseNode.Expression | undefined, increment: ParseNode.Expression | undefined, stmt: ParseNode.Statement, perIterationBindings: readonly JSStringValue[], labelSet: JSStringSet) {
// 1. Let V be undefined.
let V: Value = Value.undefined;
// 2. Perform ? CreatePerIterationEnvironment(perIterationBindings).
Q(yield* CreatePerIterationEnvironment(perIterationBindings));
// 3. Repeat,
while (true) {
// a. If test is not [empty], then
if (test) {
// i. Let testRef be the result of evaluating test.
const testRef = Q(yield* Evaluate(test));
// ii. Let testValue be ? GetValue(testRef).
const testValue = Q(yield* GetValue(testRef));
// iii. If ! ToBoolean(testValue) is false, return NormalCompletion(V).
if (X(ToBoolean(testValue)) === Value.false) {
return NormalCompletion(V);
}
}
// b. Let result be the result of evaluating stmt.
const result = EnsureCompletion(yield* Evaluate(stmt));
// c. If LoopContinues(result, labelSet) is false, return Completion(UpdateEmpty(result, V)).
if (LoopContinues(result, labelSet) === Value.false) {
return Completion(UpdateEmpty(result, V));
}
// d. If result.[[Value]] is not empty, set V to result.[[Value]].
if (result.Value !== undefined) {
V = result.Value;
}
// e. Perform ? CreatePerIterationEnvironment(perIterationBindings).
Q(yield* CreatePerIterationEnvironment(perIterationBindings));
// f. If increment is not [empty], then
if (increment) {
// i. Let incRef be the result of evaluating increment.
const incRef = Q(yield* Evaluate(increment));
// ii. Perform ? GetValue(incRef).
Q(yield* GetValue(incRef));
}
}
}
/** https://tc39.es/ecma262/#sec-createperiterationenvironment */
function* CreatePerIterationEnvironment(perIterationBindings: readonly JSStringValue[]): PlainEvaluator {
// 1. If perIterationBindings has any elements, then
if (perIterationBindings.length > 0) {
// a. Let lastIterationEnv be the running execution context's LexicalEnvironment.
const lastIterationEnv = surroundingAgent.runningExecutionContext.LexicalEnvironment;
// b. Let outer be lastIterationEnv.[[OuterEnv]].
const outer = lastIterationEnv.OuterEnv;
// c. Assert: outer is not null.
Assert(outer !== Value.null);
// d. Let thisIterationEnv be NewDeclarativeEnvironment(outer).
const thisIterationEnv = new DeclarativeEnvironmentRecord(outer);
// e. For each element bn of perIterationBindings, do
for (const bn of perIterationBindings) {
// i. Perform ! thisIterationEnv.CreateMutableBinding(bn, false).
X(thisIterationEnv.CreateMutableBinding(bn, Value.false));
// ii. Let lastValue be ? lastIterationEnv.GetBindingValue(bn, true).
const lastValue = Q(yield* lastIterationEnv.GetBindingValue(bn, Value.true));
// iii. Perform thisIterationEnv.InitializeBinding(bn, lastValue).
yield* thisIterationEnv.InitializeBinding(bn, lastValue);
}
// f. Set the running execution context's LexicalEnvironment to thisIterationEnv.
surroundingAgent.runningExecutionContext.LexicalEnvironment = thisIterationEnv;
}
// 2. Return undefined.
return undefined;
}
/** https://tc39.es/ecma262/#sec-runtime-semantics-forinofheadevaluation */
function* ForInOfHeadEvaluation(uninitializedBoundNames: readonly JSStringValue[], expr: ParseNode.Expression | ParseNode.AssignmentExpression, iterationKind: 'enumerate' | 'iterate' | 'async-iterate'): Evaluator<PlainCompletion<Value | ForInOfHeadEvaluationResult | IteratorRecord> | BreakCompletion> {
// 1. Let oldEnv be the running execution context's LexicalEnvironment.
const oldEnv = surroundingAgent.runningExecutionContext.LexicalEnvironment;
// 2. If uninitializedBoundNames is not an empty List, then
if (uninitializedBoundNames.length > 0) {
// a. Assert: uninitializedBoundNames has no duplicate entries.
// b. Let newEnv be NewDeclarativeEnvironment(oldEnv).
const newEnv = new DeclarativeEnvironmentRecord(oldEnv);
// c. For each string name in uninitializedBoundNames, do
for (const name of uninitializedBoundNames) {
// i. Perform ! newEnv.CreateMutableBinding(name, false).
X(newEnv.CreateMutableBinding(name, Value.false));
}
// d. Set the running execution context's LexicalEnvironment to newEnv.
surroundingAgent.runningExecutionContext.LexicalEnvironment = newEnv;
}
// 3. Let exprRef be the result of evaluating expr.
const exprRef = Q(yield* Evaluate(expr));
// 4. Set the running execution context's LexicalEnvironment to oldEnv.
surroundingAgent.runningExecutionContext.LexicalEnvironment = oldEnv;
// 5. Let exprValue be ? GetValue(exprRef).
const exprValue = Q(yield* GetValue(exprRef));
// 6. If iterationKind is enumerate, then
if (iterationKind === 'enumerate') {
// a. If exprValue is undefined or null, then
if (exprValue === Value.undefined || exprValue === Value.null) {
// i. Return Completion { [[Type]]: break, [[Value]]: empty, [[Target]]: empty }.
return new Completion({ Type: 'break', Value: undefined, Target: undefined });
}
// b. Let obj be ! ToObject(exprValue).
const obj = X(ToObject(exprValue));
// c. Let iterator be ? EnumerateObjectProperties(obj).
const iterator = Q(EnumerateObjectProperties(obj));
// d. Let nextMethod be ! GetV(iterator, "next").
const nextMethod = X(GetV(iterator, Value('next')));
// e. Return the Record { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }.
return { Iterator: iterator, NextMethod: nextMethod, Done: Value.false };
} else { // 7. Else,
// a. Assert: iterationKind is iterate or async-iterate.
Assert(iterationKind === 'iterate' || iterationKind === 'async-iterate');
// b. If iterationKind is async-iterate, let iteratorHint be async.
// c. Else, let iteratorHint be sync.
const iteratorHint = iterationKind === 'async-iterate' ? 'async' : 'sync';
// d. Return ? GetIterator(exprValue, iteratorHint).
return Q(yield* GetIterator(exprValue, iteratorHint));
}
}
interface ForInOfHeadEvaluationResult {
readonly Iterator: ForInIteratorInstance;
readonly NextMethod: Value;
readonly Done: Value;
}
/** https://tc39.es/ecma262/#sec-enumerate-object-properties */
function EnumerateObjectProperties(O: ObjectValue) {
return CreateForInIterator(O);
}
/** https://tc39.es/ecma262/#sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset */
function* ForInOfBodyEvaluation(lhs: ParseNode, stmt: ParseNode.Statement, iteratorRecord: IteratorRecord, iterationKind: 'enumerate' | 'iterate', lhsKind: 'assignment' | 'lexicalBinding' | 'varBinding', labelSet: JSStringSet, iteratorKind?: 'sync' | 'async'): StatementEvaluator {
// 1. If iteratorKind is not present, set iteratorKind to sync.
if (iteratorKind === undefined) {
iteratorKind = 'sync';
}
// 2. Let oldEnv be the running execution context's LexicalEnvironment.
const oldEnv = surroundingAgent.runningExecutionContext.LexicalEnvironment;
// 3. Let V be undefined.
let V: Value = Value.undefined;
// 4. Let destructuring be IsDestructuring of lhs.
const destructuring = IsDestructuring(lhs);
// 5. If destructuring is true and if lhsKind is assignment, then
let assignmentPattern;
if (destructuring && lhsKind === 'assignment') {
// a. Assert: lhs is a LeftHandSideExpression.
// b. Let assignmentPattern be the AssignmentPattern that is covered by lhs.
assignmentPattern = refineLeftHandSideExpression(lhs as DestructuringParseNode);
}
// 6. Repeat,
while (true) {
// a. Let nextResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]).
let nextResult = Q(yield* Call(iteratorRecord.NextMethod, iteratorRecord.Iterator));
// b. If iteratorKind is async, then set nextResult to ? Await(nextResult).
if (iteratorKind === 'async') {
nextResult = Q(yield* Await(nextResult));
}
// c. If Type(nextResult) is not Object, throw a TypeError exception.
if (!(nextResult instanceof ObjectValue)) {
return surroundingAgent.Throw('TypeError', 'NotAnObject', nextResult);
}
// d. Let done be ? IteratorComplete(nextResult).
const done = Q(yield* IteratorComplete(nextResult));
// e. If done is true, return NormalCompletion(V).
if (done === Value.true) {
return NormalCompletion(V);
}
// f. Let nextValue be ? IteratorValue(nextResult).
const nextValue = Q(yield* IteratorValue(nextResult));
// g. If lhsKind is either assignment or varBinding, then
let lhsRef;
let iterationEnv;
if (lhsKind === 'assignment' || lhsKind === 'varBinding') {
// i. If destructuring is false, then
if (destructuring === false) {
// 1. Let lhsRef be the result of evaluating lhs. (It may be evaluated repeatedly.)
lhsRef = yield* Evaluate(lhs);
}
} else { // h. Else,
// i. Assert: lhsKind is lexicalBinding.
Assert(lhsKind === 'lexicalBinding');
// ii. Assert: lhs is a ForDeclaration.
Assert(lhs.type === 'ForDeclaration');
// iii. Let iterationEnv be NewDeclarativeEnvironment(oldEnv).
iterationEnv = new DeclarativeEnvironmentRecord(oldEnv);
// iv. Perform BindingInstantiation for lhs passing iterationEnv as the argument.
BindingInstantiation(lhs, iterationEnv);
// v. Set the running execution context's LexicalEnvironment to iterationEnv.
surroundingAgent.runningExecutionContext.LexicalEnvironment = iterationEnv;
// vi. If destructuring is false, then
if (destructuring === false) {
// 1. Assert: lhs binds a single name.
// 2. Let lhsName be the sole element of BoundNames of lhs.
const lhsName = BoundNames(lhs)[0];
// 3. Let lhsRef be ! ResolveBinding(lhsName).
lhsRef = X(ResolveBinding(lhsName, undefined, lhs.strict));
}
}
let status: PlainCompletion<unknown>;
// i. If destructuring is false, then
if (destructuring === false) {
// i. If lhsRef is an abrupt completion, then
if (lhsRef instanceof AbruptCompletion) {
// 1. Let status be lhsRef.
status = lhsRef;
} else if (lhsKind === 'lexicalBinding') { // ii. Else is lhsKind is lexicalBinding, then
// 1. Let status be InitializeReferencedBinding(lhsRef, nextValue).
status = yield* InitializeReferencedBinding(Q(lhsRef) as ReferenceRecord, nextValue);
} else { // iii. Else,
status = yield* PutValue(Q(lhsRef) as ReferenceRecord, nextValue);
}
} else { // j. Else,
// i. If lhsKind is assignment, then
if (lhsKind === 'assignment') {
// 1. Let status be DestructuringAssignmentEvaluation of assignmentPattern with argument nextValue.
status = yield* DestructuringAssignmentEvaluation(assignmentPattern as ParseNode.ObjectAssignmentPattern | ParseNode.ArrayAssignmentPattern, nextValue);
} else if (lhsKind === 'varBinding') { // ii. Else if lhsKind is varBinding, then
// 1. Assert: lhs is a ForBinding.
Assert(lhs.type === 'ForBinding');
// 2. Let status be BindingInitialization of lhs with arguments nextValue and undefined.
status = yield* BindingInitialization(lhs, nextValue, Value.undefined);
} else { // iii. Else,
// 1. Assert: lhsKind is lexicalBinding.
Assert(lhsKind === 'lexicalBinding');
// 2. Assert: lhs is a ForDeclaration.
Assert(lhs.type === 'ForDeclaration');
// 3. Let status be BindingInitialization of lhs with arguments nextValue and iterationEnv.
status = yield* BindingInitialization(lhs, nextValue, iterationEnv!);
}
}
// k. If status is an abrupt completion, then
if (status instanceof AbruptCompletion) {
// i. Set the running execution context's LexicalEnvironment to oldEnv.
surroundingAgent.runningExecutionContext.LexicalEnvironment = oldEnv;
// ii. if iterationKind is enumerate, then
if (iterationKind === 'enumerate') {
// 1. Return status.
return status as Completion<Value | void>;
} else { // iv. Else,
// 1. Assert: iterationKind is iterate.
Assert(iterationKind === 'iterate');
// 2. If iteratorKind is async, return ? AsyncIteratorClose(iteratorRecord, status).
if (iteratorKind === 'async') {
return Q(yield* AsyncIteratorClose(iteratorRecord, status)) as Completion<Value | void>;
}
// 3 .Return ? IteratorClose(iteratorRecord, status).
return Q(yield* IteratorClose(iteratorRecord, EnsureCompletion(status)));
}
}
// l. Let result be the result of evaluating stmt.
const result = EnsureCompletion(yield* Evaluate(stmt));
// m. Set the running execution context's LexicalEnvironment to oldEnv.
surroundingAgent.runningExecutionContext.LexicalEnvironment = oldEnv;
// n. If LoopContinues(result, labelSet) is false, then
if (LoopContinues(result, labelSet) === Value.false) {
// Set _status_ to Completion(UpdateEmpty(_result_, _V_)).
status = UpdateEmpty(result, V);
// i. If iterationKind is enumerate, then
if (iterationKind === 'enumerate') {
// 1. Return ? _status_.
return Q(status as Completion<Value | void>);
} else { // ii. Else,
// 1. Assert: iterationKind is iterate.
Assert(iterationKind === 'iterate');
// 2. If iteratorKind is async, return ? AsyncIteratorClose(iteratorRecord, status).
if (iteratorKind === 'async') {
return Q(yield* AsyncIteratorClose(iteratorRecord, status)) as Completion<Value | void>;
}
// 3. Return ? IteratorClose(iteratorRecord, status).
return Q(yield* IteratorClose(iteratorRecord, EnsureCompletion(status)));
}
}
// o. If result.[[Value]] is not empty, set V to result.[[Value]].
if (result.Value !== undefined) {
V = result.Value;
}
}
}
/** https://tc39.es/ecma262/#sec-runtime-semantics-bindinginstantiation */
// ForDeclaration : LetOrConst ForBinding
function BindingInstantiation({ LetOrConst, ForBinding }: ParseNode.ForDeclaration, environment: DeclarativeEnvironmentRecord) {
// 1. Assert: environment is a declarative Environment Record.
Assert(environment instanceof DeclarativeEnvironmentRecord);
// 2. For each element name of the BoundNames of ForBinding, do
for (const name of BoundNames(ForBinding)) {
// a. If IsConstantDeclaration of LetOrConst is true, then
if (IsConstantDeclaration(LetOrConst)) {
// i. Perform ! environment.CreateImmutableBinding(name, true).
X(environment.CreateImmutableBinding(name, Value.true));
} else { // b. Else,
// i. Perform ! environment.CreateMutableBinding(name, false).
X(environment.CreateMutableBinding(name, Value.false));
}
}
}
/** https://tc39.es/ecma262/#sec-for-in-and-for-of-statements-runtime-semantics-evaluation */
// ForBinding : BindingIdentifier
export function Evaluate_ForBinding({ BindingIdentifier, strict }: ParseNode.ForBinding) {
// 1. Let bindingId be StringValue of BindingIdentifier.
const bindingId = StringValue(BindingIdentifier!);
// 2. Return ? ResolveBinding(bindingId).
return ResolveBinding(bindingId, undefined, strict);
}