/**
* Copyright (c) 2012-2016 André Bargull
* Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms.
*
* <https://github.com/anba/es6draft>
*/
package com.github.anba.es6draft.compiler;
import static com.github.anba.es6draft.compiler.DefaultCodeGenerator.SetFunctionName;
import static com.github.anba.es6draft.compiler.DefaultCodeGenerator.ToPropertyKey;
import static com.github.anba.es6draft.semantics.StaticSemantics.IsAnonymousFunctionDefinition;
import static com.github.anba.es6draft.semantics.StaticSemantics.IsIdentifierRef;
import static com.github.anba.es6draft.semantics.StaticSemantics.PropName;
import java.util.HashSet;
import java.util.List;
import com.github.anba.es6draft.ast.*;
import com.github.anba.es6draft.ast.scope.Name;
import com.github.anba.es6draft.compiler.DefaultCodeGenerator.ValType;
import com.github.anba.es6draft.compiler.Labels.TempLabel;
import com.github.anba.es6draft.compiler.StatementGenerator.Completion;
import com.github.anba.es6draft.compiler.assembler.FieldName;
import com.github.anba.es6draft.compiler.assembler.Jump;
import com.github.anba.es6draft.compiler.assembler.MethodName;
import com.github.anba.es6draft.compiler.assembler.MutableValue;
import com.github.anba.es6draft.compiler.assembler.Type;
import com.github.anba.es6draft.compiler.assembler.Variable;
import com.github.anba.es6draft.runtime.internal.ScriptIterator;
/**
* <h1>12 ECMAScript Language: Expressions</h1><br>
* <h2>12.14 Assignment Operators</h2>
* <ul>
* <li>12.14.5 Destructuring Assignment
* </ul>
*/
final class DestructuringAssignmentGenerator {
private static final class Fields {
static final FieldName Collections_EMPTY_SET = FieldName.findStatic(Types.Collections, "EMPTY_SET", Types.Set);
}
private static final class Methods {
// class: AbstractOperations
static final MethodName AbstractOperations_GetV = MethodName.findStatic(Types.AbstractOperations, "GetV",
Type.methodType(Types.Object, Types.ExecutionContext, Types.Object, Types.Object));
static final MethodName AbstractOperations_GetV_String = MethodName.findStatic(Types.AbstractOperations, "GetV",
Type.methodType(Types.Object, Types.ExecutionContext, Types.Object, Types.String));
static final MethodName AbstractOperations_RequireObjectCoercible = MethodName.findStatic(
Types.AbstractOperations, "RequireObjectCoercible",
Type.methodType(Types.Object, Types.ExecutionContext, Types.Object));
// class: ScriptRuntime
static final MethodName ScriptRuntime_createRestArray = MethodName.findStatic(Types.ScriptRuntime,
"createRestArray", Type.methodType(Types.ArrayObject, Types.Iterator, Types.ExecutionContext));
static final MethodName ScriptRuntime_createRestObject = MethodName.findStatic(Types.ScriptRuntime,
"createRestObject",
Type.methodType(Types.OrdinaryObject, Types.Object, Types.Set, Types.ExecutionContext));
static final MethodName ScriptRuntime_iterate = MethodName.findStatic(Types.ScriptRuntime, "iterate",
Type.methodType(Types.ScriptIterator, Types.Object, Types.ExecutionContext));
static final MethodName ScriptRuntime_iteratorNextAndIgnore = MethodName.findStatic(Types.ScriptRuntime,
"iteratorNextAndIgnore", Type.methodType(Type.VOID_TYPE, Types.Iterator));
static final MethodName ScriptRuntime_iteratorNextOrUndefined = MethodName.findStatic(Types.ScriptRuntime,
"iteratorNextOrUndefined", Type.methodType(Types.Object, Types.Iterator));
// class: HashSet
static final MethodName HashSet_init = MethodName.findConstructor(Types.HashSet,
Type.methodType(Type.VOID_TYPE));
static final MethodName HashSet_add = MethodName.findVirtual(Types.HashSet, "add",
Type.methodType(Type.BOOLEAN_TYPE, Types.Object));
}
private DestructuringAssignmentGenerator() {
}
/**
* stack: [value] {@literal ->} []
*
* @param codegen
* the code generator
* @param node
* the assignment pattern node
* @param mv
* the code visitor
*/
static void DestructuringAssignment(CodeGenerator codegen, AssignmentPattern node, CodeVisitor mv) {
DestructuringAssignmentEvaluation init = new DestructuringAssignmentEvaluation(codegen, mv);
node.accept(init, null);
}
private static abstract class RuntimeSemantics<V> extends DefaultVoidNodeVisitor<V> {
protected final CodeGenerator codegen;
protected final CodeVisitor mv;
RuntimeSemantics(CodeGenerator codegen, CodeVisitor mv) {
this.codegen = codegen;
this.mv = mv;
}
protected final void DestructuringAssignmentEvaluation(AssignmentPattern node) {
node.accept(new DestructuringAssignmentEvaluation(codegen, mv), null);
}
protected final void IteratorDestructuringAssignmentEvaluation(AssignmentElementItem node,
Variable<ScriptIterator<?>> iterator) {
node.accept(new IteratorDestructuringAssignmentEvaluation(codegen, mv), iterator);
}
protected final void KeyedDestructuringAssignmentEvaluation(AssignmentProperty node, String key,
Variable<Object> value, Variable<HashSet<?>> propertyNames) {
node.accept(new LiteralKeyedDestructuringAssignmentEvaluation(codegen, mv, value, propertyNames), key);
}
protected final void KeyedDestructuringAssignmentEvaluation(AssignmentProperty node, ComputedPropertyName key,
Variable<Object> value, Variable<HashSet<?>> propertyNames) {
node.accept(new ComputedKeyedDestructuringAssignmentEvaluation(codegen, mv, value, propertyNames), key);
}
protected final ValType expression(Expression node, CodeVisitor mv) {
return codegen.expression(node, mv);
}
protected final ValType expressionBoxed(Expression node, CodeVisitor mv) {
return codegen.expressionBoxed(node, mv);
}
@Override
protected final void visit(Node node, V value) {
throw new IllegalStateException();
}
}
/**
* 12.14.5.2 Runtime Semantics: DestructuringAssignmentEvaluation
*/
private static final class DestructuringAssignmentEvaluation extends RuntimeSemantics<Void> {
DestructuringAssignmentEvaluation(CodeGenerator codegen, CodeVisitor mv) {
super(codegen, mv);
}
@Override
public void visit(ArrayAssignmentPattern node, Void value) {
// stack: [value] -> []
mv.enterVariableScope();
Variable<ScriptIterator<?>> iterator = mv.newVariable("iterator", ScriptIterator.class)
.uncheckedCast();
mv.loadExecutionContext();
mv.lineInfo(node);
mv.invoke(Methods.ScriptRuntime_iterate);
mv.store(iterator);
new IterationGenerator<ArrayAssignmentPattern>(codegen) {
@Override
protected Completion iterationBody(ArrayAssignmentPattern node, Variable<ScriptIterator<?>> iterator,
CodeVisitor mv) {
for (AssignmentElementItem element : node.getElements()) {
IteratorDestructuringAssignmentEvaluation(element, iterator);
}
return Completion.Normal;
}
@Override
protected void epilogue(ArrayAssignmentPattern node, Variable<ScriptIterator<?>> iterator,
CodeVisitor mv) {
IteratorClose(node, iterator, mv);
}
@Override
protected MutableValue<Object> enterIteration(ArrayAssignmentPattern node, CodeVisitor mv) {
return mv.enterIteration();
}
@Override
protected List<TempLabel> exitIteration(ArrayAssignmentPattern node, CodeVisitor mv) {
return mv.exitIteration();
}
}.generate(node, iterator, mv);
mv.exitVariableScope();
}
@Override
public void visit(ObjectAssignmentPattern node, Void value) {
// stack: [value] -> [value]
mv.loadExecutionContext();
mv.swap();
mv.lineInfo(node);
mv.invoke(Methods.AbstractOperations_RequireObjectCoercible);
// ObjectAssignmentPattern : { }
if (node.getProperties().isEmpty() && node.getRest() == null) {
// stack: [value] -> []
mv.pop();
return;
}
// stack: [value] -> []
mv.enterVariableScope();
Variable<Object> val = mv.newVariable("value", Object.class);
mv.store(val);
Variable<HashSet<?>> propertyNames = null;
if (!node.getProperties().isEmpty() && node.getRest() != null) {
propertyNames = mv.newVariable("propertyNames", HashSet.class).uncheckedCast();
mv.anew(Types.HashSet, Methods.HashSet_init);
mv.store(propertyNames);
}
// ObjectAssignmentPattern : { AssignmentPropertyList }
for (AssignmentProperty property : node.getProperties()) {
if (property.getPropertyName() == null) {
// AssignmentProperty : IdentifierReference Initializer{opt}
assert property.getTarget() instanceof IdentifierReference;
String name = ((IdentifierReference) property.getTarget()).getName();
KeyedDestructuringAssignmentEvaluation(property, name, val, propertyNames);
} else {
// AssignmentProperty : PropertyName : AssignmentElement
String name = PropName(property.getPropertyName());
if (name != null) {
KeyedDestructuringAssignmentEvaluation(property, name, val, propertyNames);
} else {
PropertyName propertyName = property.getPropertyName();
assert propertyName instanceof ComputedPropertyName;
KeyedDestructuringAssignmentEvaluation(property, (ComputedPropertyName) propertyName, val,
propertyNames);
}
}
}
// ObjectAssignmentPattern : { ... DestructuringAssignmentTarget }
// ObjectAssignmentPattern: { AssignmentPropertyList , ... DestructuringAssignmentTarget }
AssignmentRestProperty rest = node.getRest();
if (rest != null) {
LeftHandSideExpression target = rest.getTarget();
ReferenceOp<LeftHandSideExpression> op = null;
/* steps 1, 1-2 (not applicable) */
/* step 2, 3 */
ValType refType = null;
if (!(target instanceof AssignmentPattern)) {
// stack: [] -> [lref]
op = ReferenceOp.of(target);
refType = op.reference(target, mv, codegen);
}
/* steps 3-5, 4-6 */
// stack: [lref?] -> [lref?, restObj]
mv.load(val);
if (propertyNames != null) {
mv.load(propertyNames);
} else {
mv.get(Fields.Collections_EMPTY_SET);
}
mv.loadExecutionContext();
mv.lineInfo(rest);
mv.invoke(Methods.ScriptRuntime_createRestObject);
// Exit the variable scope early to avoid restoring the value/propertyNames slots after a yield.
mv.exitVariableScope();
/* steps 6-8, 7-9 */
if (!(target instanceof AssignmentPattern)) {
/* step 6, 7 */
// stack: [lref, restObj] -> []
op.putValue(target, refType, ValType.Object, mv);
} else {
/* steps 7-8, 8-9 */
// stack: [restObj] -> []
DestructuringAssignmentEvaluation((AssignmentPattern) target);
}
} else {
mv.exitVariableScope();
}
}
}
/**
* 12.14.5.3 Runtime Semantics: IteratorDestructuringAssignmentEvaluation
*/
private static final class IteratorDestructuringAssignmentEvaluation extends
RuntimeSemantics<Variable<ScriptIterator<?>>> {
IteratorDestructuringAssignmentEvaluation(CodeGenerator codegen, CodeVisitor mv) {
super(codegen, mv);
}
@Override
public void visit(Elision node, Variable<ScriptIterator<?>> iterator) {
// stack: [] -> []
mv.load(iterator);
mv.lineInfo(node);
mv.invoke(Methods.ScriptRuntime_iteratorNextAndIgnore);
}
@Override
public void visit(AssignmentElement node, Variable<ScriptIterator<?>> iterator) {
LeftHandSideExpression target = node.getTarget();
Expression initializer = node.getInitializer();
ReferenceOp<LeftHandSideExpression> op = null;
/* step 1 */
ValType refType = null;
if (!(target instanceof AssignmentPattern)) {
// stack: [] -> [lref]
op = ReferenceOp.of(target);
refType = op.reference(target, mv, codegen);
}
/* steps 2-3 */
// stack: [(lref)] -> [(lref), v]
mv.load(iterator);
mv.lineInfo(node);
mv.invoke(Methods.ScriptRuntime_iteratorNextOrUndefined);
/* steps 4-5 */
// stack: [(lref), v] -> [(lref), v']
if (initializer != null) {
Jump undef = new Jump();
mv.dup();
mv.loadUndefined();
mv.ifacmpne(undef);
{
mv.pop();
expressionBoxed(initializer, mv);
/* step 7 (moved) */
if (IsAnonymousFunctionDefinition(initializer) && IsIdentifierRef(target)) {
SetFunctionName(initializer, ((IdentifierReference) target).getName(), mv);
}
}
mv.mark(undef);
}
/* steps 6-8 */
if (target instanceof AssignmentPattern) {
// stack: [v'] -> []
DestructuringAssignmentEvaluation((AssignmentPattern) target);
} else {
// stack: [lref, 'v] -> []
op.putValue(target, refType, ValType.Any, mv);
}
}
@Override
public void visit(AssignmentRestElement node, Variable<ScriptIterator<?>> iterator) {
LeftHandSideExpression target = node.getTarget();
ReferenceOp<LeftHandSideExpression> op = null;
/* step 1 */
ValType refType = null;
if (!(target instanceof AssignmentPattern)) {
// stack: [] -> [lref]
op = ReferenceOp.of(target);
refType = op.reference(target, mv, codegen);
}
/* steps 2-4 */
// stack: [(lref)] -> [(lref), rest]
mv.load(iterator);
mv.loadExecutionContext();
mv.lineInfo(node);
mv.invoke(Methods.ScriptRuntime_createRestArray);
/* steps 5-7 */
if (!(target instanceof AssignmentPattern)) {
// stack: [lref, rest] -> []
op.putValue(target, refType, ValType.Object, mv);
} else {
// stack: [rest] -> []
DestructuringAssignmentEvaluation((AssignmentPattern) target);
}
}
}
/**
* 12.14.5.4 Runtime Semantics: KeyedDestructuringAssignmentEvaluation
*/
private static abstract class KeyedDestructuringAssignmentEvaluation<PROPERTYNAME> extends
RuntimeSemantics<PROPERTYNAME> {
private final Variable<Object> value;
KeyedDestructuringAssignmentEvaluation(CodeGenerator codegen, CodeVisitor mv, Variable<Object> value) {
super(codegen, mv);
this.value = value;
}
abstract ValType evaluatePropertyName(PROPERTYNAME propertyName);
abstract boolean isSimplePropertyName(PROPERTYNAME propertyName);
final boolean isSimplePropertyNameOrTarget(LeftHandSideExpression target,
PROPERTYNAME propertyName) {
if (isSimplePropertyName(propertyName)) {
return true;
}
if (target instanceof IdentifierReference) {
Name resolvedName = ((IdentifierReference) target).getResolvedName();
return resolvedName != null && resolvedName.isLocal();
}
return false;
}
@Override
public void visit(AssignmentProperty node, PROPERTYNAME propertyName) {
LeftHandSideExpression target = node.getTarget();
Expression initializer = node.getInitializer();
ReferenceOp<LeftHandSideExpression> op;
ValType type, refType;
if (target instanceof AssignmentPattern) {
/* step 1 (not applicable) */
op = null;
refType = null;
// stack: [] -> [cx, value]
mv.loadExecutionContext();
mv.load(value);
// stack: [cx, value] -> [cx, value, propertyName]
type = evaluatePropertyName(propertyName);
} else if (isSimplePropertyNameOrTarget(target, propertyName)) {
/* step 1 */
// stack: [] -> [lref]
op = ReferenceOp.of(target);
refType = op.reference(target, mv, codegen);
// stack: [lref] -> [lref, cx, value]
mv.loadExecutionContext();
mv.load(value);
// stack: [lref, cx, value] -> [lref, cx, value, propertyName]
type = evaluatePropertyName(propertyName);
} else {
// stack: [] -> []
type = evaluatePropertyName(propertyName);
Variable<?> propertyNameVar = mv.newScratchVariable(type.toClass());
mv.store(propertyNameVar);
/* step 1 */
// stack: [] -> [lref]
op = ReferenceOp.of(target);
refType = op.reference(target, mv, codegen);
// stack: [lref] -> [lref, cx, value, propertyName]
mv.loadExecutionContext();
mv.load(value);
mv.load(propertyNameVar);
mv.freeVariable(propertyNameVar);
}
/* steps 2-3 */
// stack: [(lref), cx, value, propertyName] -> [(lref), v]
mv.lineInfo(node);
if (type == ValType.String) {
mv.invoke(Methods.AbstractOperations_GetV_String);
} else {
mv.invoke(Methods.AbstractOperations_GetV);
}
/* steps 4-5 */
// stack: [(lref), v] -> [(lref), v']
if (initializer != null) {
Jump undef = new Jump();
mv.dup();
mv.loadUndefined();
mv.ifacmpne(undef);
{
mv.pop();
expressionBoxed(initializer, mv);
/* step 7 (moved) */
if (IsAnonymousFunctionDefinition(initializer) && IsIdentifierRef(target)) {
SetFunctionName(initializer, ((IdentifierReference) target).getName(), mv);
}
}
mv.mark(undef);
}
/* steps 6-8 */
if (target instanceof AssignmentPattern) {
// stack: [v'] -> []
DestructuringAssignmentEvaluation((AssignmentPattern) target);
} else {
// stack: [lref, 'v] -> []
op.putValue(target, refType, ValType.Any, mv);
}
}
}
/**
* 12.14.5.4 Runtime Semantics: KeyedDestructuringAssignmentEvaluation
*/
private static final class LiteralKeyedDestructuringAssignmentEvaluation extends
KeyedDestructuringAssignmentEvaluation<String> {
private final Variable<HashSet<?>> propertyNames;
LiteralKeyedDestructuringAssignmentEvaluation(CodeGenerator codegen, CodeVisitor mv, Variable<Object> value,
Variable<HashSet<?>> propertyNames) {
super(codegen, mv, value);
this.propertyNames = propertyNames;
}
@Override
ValType evaluatePropertyName(String propertyName) {
if (propertyNames != null) {
mv.load(propertyNames);
mv.aconst(propertyName);
mv.invoke(Methods.HashSet_add);
mv.pop();
}
mv.aconst(propertyName);
return ValType.String;
}
@Override
boolean isSimplePropertyName(String propertyName) {
return true;
}
}
/**
* 12.14.5.4 Runtime Semantics: KeyedDestructuringAssignmentEvaluation
*/
private static final class ComputedKeyedDestructuringAssignmentEvaluation extends
KeyedDestructuringAssignmentEvaluation<ComputedPropertyName> {
private final Variable<HashSet<?>> propertyNames;
ComputedKeyedDestructuringAssignmentEvaluation(CodeGenerator codegen, CodeVisitor mv, Variable<Object> value,
Variable<HashSet<?>> propertyNames) {
super(codegen, mv, value);
this.propertyNames = propertyNames;
}
@Override
ValType evaluatePropertyName(ComputedPropertyName propertyName) {
if (propertyNames != null) {
mv.load(propertyNames);
}
// Runtime Semantics: Evaluation
// ComputedPropertyName : [ AssignmentExpression ]
ValType propType = expression(propertyName.getExpression(), mv);
ValType keyType = ToPropertyKey(propType, mv);
if (propertyNames != null) {
mv.dupX1();
mv.invoke(Methods.HashSet_add);
mv.pop();
}
return keyType;
}
@Override
boolean isSimplePropertyName(ComputedPropertyName propertyName) {
return propertyName.getExpression() instanceof Literal;
}
}
}