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

307 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { surroundingAgent } from '../host-defined/engine.mts';
import {
JSStringValue, ReferenceRecord, Value, type PropertyKeyValue,
} from '../value.mts';
import {
IsAnonymousFunctionDefinition,
IsIdentifierRef,
StringValue,
type FunctionDeclaration,
} from '../static-semantics/all.mts';
import {
Evaluate, type PlainEvaluator, type StatementEvaluator,
} from '../evaluator.mts';
import {
Q, X,
Completion,
AbruptCompletion,
NormalCompletion,
EnsureCompletion,
} from '../completion.mts';
import { OutOfRange } from '../helpers.mts';
import type { ParseNode } from '../parser/ParseNode.mts';
import {
Evaluate_PropertyName,
NamedEvaluation,
refineLeftHandSideExpression,
} from './all.mts';
import {
ArrayCreate,
CopyDataProperties,
CreateDataPropertyOrThrow,
GetIterator,
GetV,
GetValue,
IteratorClose,
IteratorStep,
OrdinaryObjectCreate,
PutValue,
ResolveBinding,
RequireObjectCoercible,
ToString,
F,
Assert,
type IteratorRecord,
IteratorStepValue,
} from '#self';
// ObjectAssignmentPattern :
// `{` `}`
// `{` AssignmentPropertyList `}`
// `{` AssignmentPropertyList `,` `}`
// `{` AssignmentPropertyList `,` AssignmentRestProperty? `}`
function* DestructuringAssignmentEvaluation_ObjectAssignmentPattern({ AssignmentPropertyList, AssignmentRestProperty }: ParseNode.ObjectAssignmentPattern, value: Value): PlainEvaluator {
// 1. Perform ? RequireObjectCoercible(value).
Q(RequireObjectCoercible(value));
// 2. Perform ? PropertyDestructuringAssignmentEvaluation for AssignmentPropertyList using value as the argument.
const excludedNames: readonly PropertyKeyValue[] = Q(yield* PropertyDestructuringAssignmentEvaluation(AssignmentPropertyList, value));
if (AssignmentRestProperty) {
Q(yield* RestDestructuringAssignmentEvaluation(AssignmentRestProperty, value, excludedNames));
}
// 3. Return NormalCompletion(empty).
return NormalCompletion(undefined);
}
/** https://tc39.es/ecma262/#sec-runtime-semantics-restdestructuringassignmentevaluation */
// AssignmentRestProperty : `...` DestructuringAssignmentTarget
function* RestDestructuringAssignmentEvaluation({ DestructuringAssignmentTarget }: ParseNode.AssignmentRestProperty, value: Value, excludedNames: readonly PropertyKeyValue[]): StatementEvaluator {
// 1. Let lref be the result of evaluating DestructuringAssignmentTarget.
const lref = Q(yield* Evaluate(DestructuringAssignmentTarget));
Q(lref);
// 3. Let restObj be OrdinaryObjectCreate(%Object.prototype%).
const restObj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%'));
// 4. Perform ? CopyDataProperties(restObj, value, excludedNames).
Q(yield* CopyDataProperties(restObj, value, excludedNames));
// 5. Return PutValue(lref, restObj).
return yield* PutValue(lref, restObj);
}
function* PropertyDestructuringAssignmentEvaluation(AssignmentPropertyList: ParseNode.ObjectAssignmentPattern['AssignmentPropertyList'], value: Value): PlainEvaluator<PropertyKeyValue[]> {
const propertyNames: PropertyKeyValue[] = [];
for (const AssignmentProperty of AssignmentPropertyList) {
if ('IdentifierReference' in AssignmentProperty) {
// 1. Let P be StringValue of IdentifierReference.
const P = StringValue(AssignmentProperty.IdentifierReference);
// 2. Let lref be ? ResolveBinding(P).
const lref = Q(yield* ResolveBinding(P, undefined, AssignmentProperty.IdentifierReference.strict));
// 3. Let v be ? GetV(value, P).
let v = Q(yield* GetV(value, P));
// 4. If Initializer? is present and v is undefined, then
if (AssignmentProperty.Initializer && v === Value.undefined) {
// a. If IsAnonymousFunctionDefinition(Initializer) is true, then
if (IsAnonymousFunctionDefinition(AssignmentProperty.Initializer)) {
// i. Set v to the result of performing NamedEvaluation for Initializer with argument P.
v = Q(yield* NamedEvaluation(AssignmentProperty.Initializer as FunctionDeclaration, P));
} else { // b. Else,
// i. Let defaultValue be the result of evaluating Initializer.
const defaultValue = Q(yield* Evaluate(AssignmentProperty.Initializer));
// ii. Set v to ? GetValue(defaultValue)
v = Q(yield* GetValue(defaultValue));
}
}
// 5. Perform ? PutValue(lref, v).
Q(yield* PutValue(lref, v));
// 6. Return a new List containing P.
propertyNames.push(P);
} else {
Assert('PropertyName' in AssignmentProperty);
// 1. Let name be the result of evaluating PropertyName.
const name = yield* Evaluate_PropertyName(AssignmentProperty.PropertyName!);
Q(name);
// 3. Perform ? KeyedDestructuringAssignmentEvaluation of AssignmentElement with value and name as the arguments.
Q(yield* KeyedDestructuringAssignmentEvaluation(AssignmentProperty.AssignmentElement, value, name as PropertyKeyValue));
// 4. Return a new List containing name.
propertyNames.push(name as PropertyKeyValue);
}
}
return propertyNames;
}
// AssignmentElement : DestructuringAssignmentTarget Initializer?
function* KeyedDestructuringAssignmentEvaluation({
DestructuringAssignmentTarget,
Initializer,
}: ParseNode.AssignmentElement, value: Value, propertyName: PropertyKeyValue) {
// 1. If DestructuringAssignmentTarget is neither an ObjectLiteral nor an ArrayLiteral, then
let lref;
if (DestructuringAssignmentTarget.type !== 'ObjectLiteral'
&& DestructuringAssignmentTarget.type !== 'ArrayLiteral') {
// a. Let lref be the result of evaluating DestructuringAssignmentTarget.
lref = Q(yield* Evaluate(DestructuringAssignmentTarget));
}
// 2. Let v be ? GetV(value, propertyName).
const v = Q(yield* GetV(value, propertyName));
// 3. If Initializer is present and v is undefined, then
let rhsValue: Value;
if (Initializer && v === Value.undefined) {
// a. If IsAnonymousFunctionDefinition(Initializer) and IsIdentifierRef of DestructuringAssignmentTarget are both true, then
if (IsAnonymousFunctionDefinition(Initializer) && IsIdentifierRef(DestructuringAssignmentTarget)) {
// i. Let rhsValue be NamedEvaluation of Initializer with argument GetReferencedName(lref).
rhsValue = Q(yield* NamedEvaluation(Initializer as FunctionDeclaration, (lref as ReferenceRecord).ReferencedName as JSStringValue));
} else {
// i. Let defaultValue be the result of evaluating Initializer.
const defaultValue = Q(yield* Evaluate(Initializer));
// ii. Let rhsValue be ? GetValue(defaultValue).
rhsValue = Q(yield* GetValue(defaultValue));
}
} else { // 4. Else, let rhsValue be v.
rhsValue = v;
}
// 5. If DestructuringAssignmentTarget is an ObjectLiteral or an ArrayLiteral, then
if (DestructuringAssignmentTarget.type === 'ObjectLiteral'
|| DestructuringAssignmentTarget.type === 'ArrayLiteral') {
// a. Let assignmentPattern be the AssignmentPattern that is covered by DestructuringAssignmentTarget.
const assignmentPattern = refineLeftHandSideExpression(DestructuringAssignmentTarget) as ParseNode.ObjectAssignmentPattern | ParseNode.ArrayAssignmentPattern;
// b. Return the result of performing DestructuringAssignmentEvaluation of assignmentPattern with rhsValue as the argument.
return yield* DestructuringAssignmentEvaluation(assignmentPattern, X(rhsValue));
}
// 6. Return ? PutValue(lref, rhsValue).
return Q(yield* PutValue(X(lref)!, rhsValue));
}
// ArrayAssignmentPattern :
// `[` `]`
// `[` AssignmentElementList `]`
// `[` AssignmentElementList `,` AssignmentRestElement? `]`
function* DestructuringAssignmentEvaluation_ArrayAssignmentPattern({ AssignmentElementList, AssignmentRestElement }: ParseNode.ArrayAssignmentPattern, value: Value) {
// 1. Let iteratorRecord be ? GetIterator(value).
const iteratorRecord = Q(yield* GetIterator(value, 'sync'));
// 2. Let status be IteratorDestructuringAssignmentEvaluation of AssignmentElementList with argument iteratorRecord.
let status = EnsureCompletion(yield* IteratorDestructuringAssignmentEvaluation(AssignmentElementList, iteratorRecord));
// 3. If status is an abrupt completion, then
if (status instanceof AbruptCompletion) {
// a. If iteratorRecord.[[Done]] is false, return ? IteratorClose(iteratorRecord, status).
if (iteratorRecord.Done === Value.false) {
return Q(yield* IteratorClose(iteratorRecord, status));
}
// b. Return Completion(status).
return status;
}
// 4. If Elision is present, then
// ...
// 5. If AssignmentRestElement is present, then
if (AssignmentRestElement) {
// a. Set status to the result of performing IteratorDestructuringAssignmentEvaluation of AssignmentRestElement with iteratorRecord as the argument.
status = EnsureCompletion(yield* IteratorDestructuringAssignmentEvaluation(AssignmentRestElement, iteratorRecord));
}
// 6. If iteratorRecord.[[Done]] is false, return ? IteratorClose(iteratorRecord, status).
if (iteratorRecord.Done === Value.false) {
return Q(yield* IteratorClose(iteratorRecord, status));
}
return Completion(status);
}
function* IteratorDestructuringAssignmentEvaluation(node: ParseNode.AssignmentElisionElement[] | ParseNode.AssignmentElisionElement | ParseNode.AssignmentRestElement, iteratorRecord: IteratorRecord): StatementEvaluator {
if (Array.isArray(node)) {
for (const n of node) {
Q(yield* IteratorDestructuringAssignmentEvaluation(n, iteratorRecord));
}
return NormalCompletion(undefined);
}
switch (node.type) {
case 'Elision':
// 1. If iteratorRecord.[[Done]] is false, then
if (iteratorRecord.Done === Value.false) {
// a. Perform ? IteratorStep(iteratorRecord).
Q(yield* IteratorStep(iteratorRecord));
}
// 2. Return NormalCompletion(empty).
return NormalCompletion(undefined);
case 'AssignmentElement': {
const { DestructuringAssignmentTarget, Initializer } = node;
let lref;
// 1. If DestructuringAssignmentTarget is neither an ObjectLiteral nor an ArrayLiteral, then
if (DestructuringAssignmentTarget.type !== 'ObjectLiteral'
&& DestructuringAssignmentTarget.type !== 'ArrayLiteral') {
lref = Q(yield* Evaluate(DestructuringAssignmentTarget));
}
let value: Value = Value.undefined;
// 2. If iteratorRecord.[[Done]] is false, then
if (iteratorRecord.Done === Value.false) {
// a. Let next be ? IteratorStepValue(iteratorRecord).
const next = Q(yield* IteratorStepValue(iteratorRecord));
// d. If next is not done, set value to next.
if (next !== 'done') {
value = next;
}
}
let v: Value;
// 4. If Initializer is present and value is undefined, then
if (Initializer && value === Value.undefined) {
// a. If IsAnonymousFunctionDefinition(AssignmentExpression) is true and IsIdentifierRef of LeftHandSideExpression is true, then
if (IsAnonymousFunctionDefinition(Initializer) && IsIdentifierRef(DestructuringAssignmentTarget)) {
// i. Let target be the StringValue of DestructuringAssignmentTarget.
const target = (lref as ReferenceRecord).ReferencedName as JSStringValue;
// i. ii. Let v be ? NamedEvaluation of Initializer with argument target.
v = Q(yield* NamedEvaluation(Initializer as FunctionDeclaration, target));
} else { // b. Else,
// i. Let defaultValue be the result of evaluating Initializer.
const defaultValue = Q(yield* Evaluate(Initializer));
// ii. Let v be ? GetValue(defaultValue).
v = Q(yield* GetValue(defaultValue));
}
} else { // 5. Else, let v be value.
v = Q(value);
}
// 6. If DestructuringAssignmentTarget is an ObjectLiteral or an ArrayLiteral, then
if (DestructuringAssignmentTarget.type === 'ObjectLiteral'
|| DestructuringAssignmentTarget.type === 'ArrayLiteral') {
// a. Let nestedAssignmentPattern be the AssignmentPattern that is covered by DestructuringAssignmentTarget.
const nestedAssignmentPattern = refineLeftHandSideExpression(DestructuringAssignmentTarget) as ParseNode.ObjectAssignmentPattern | ParseNode.ArrayAssignmentPattern;
// b. Return the result of performing DestructuringAssignmentEvaluation of nestedAssignmentPattern with v as the argument.
return yield* DestructuringAssignmentEvaluation(nestedAssignmentPattern, X(v));
}
// 7. Return ? PutValue(lref, v).
return Q(yield* PutValue(Q(lref) as ReferenceRecord, v));
}
case 'AssignmentRestElement': {
const { AssignmentExpression: DestructuringAssignmentTarget } = node;
let lref;
// 1. If DestructuringAssignmentTarget is neither an ObjectLiteral nor an ArrayLiteral, then
if (DestructuringAssignmentTarget.type !== 'ObjectLiteral'
&& DestructuringAssignmentTarget.type !== 'ArrayLiteral') {
lref = yield* Evaluate(DestructuringAssignmentTarget);
Q(lref);
}
// 2. Let A be ! ArrayCreate(0).
const A = X(ArrayCreate(0));
// 3. Let n be 0.
let n = 0;
// 4. Repeat, while iteratorRecord.[[Done]] is false,
while (iteratorRecord.Done === Value.false) {
// a. Let next be IteratorStep(iteratorRecord).
const next = Q(yield* IteratorStepValue(iteratorRecord));
// d. If next is not done, then
if (next !== 'done') {
// i. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), next).
X(CreateDataPropertyOrThrow(A, X(ToString(F(n))), X(next)));
// v. Set n to n + 1.
n += 1;
}
}
// 5. If DestructuringAssignmentTarget is neither an ObjectLiteral nor an ArrayLiteral, then
if (DestructuringAssignmentTarget.type !== 'ObjectLiteral'
&& DestructuringAssignmentTarget.type !== 'ArrayLiteral') {
return Q(yield* PutValue(Q(lref) as ReferenceRecord, A));
}
// 6. Let nestedAssignmentPattern be the AssignmentPattern that is covered by DestructuringAssignmentTarget.
const nestedAssignmentPattern = refineLeftHandSideExpression(DestructuringAssignmentTarget) as ParseNode.ObjectAssignmentPattern | ParseNode.ArrayAssignmentPattern;
// 7. Return the result of performing DestructuringAssignmentEvaluation of nestedAssignmentPattern with A as the argument.
return yield* DestructuringAssignmentEvaluation(nestedAssignmentPattern, A);
}
default:
throw new OutOfRange('IteratorDestructuringAssignmentEvaluation', node);
}
}
export function DestructuringAssignmentEvaluation(node: ParseNode.ObjectAssignmentPattern | ParseNode.ArrayAssignmentPattern, value: Value): StatementEvaluator {
switch (node.type) {
case 'ObjectAssignmentPattern':
return DestructuringAssignmentEvaluation_ObjectAssignmentPattern(node, value);
case 'ArrayAssignmentPattern':
return DestructuringAssignmentEvaluation_ArrayAssignmentPattern(node, value);
default:
throw new OutOfRange('DestructuringAssignmentEvaluation', node);
}
}