/**
* 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.completion;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import com.github.anba.es6draft.ast.*;
import com.github.anba.es6draft.ast.synthetic.StatementListMethod;
import com.github.anba.es6draft.compiler.completion.CompletionValueVisitor.Completion;
import com.github.anba.es6draft.compiler.completion.CompletionValueVisitor.State;
/**
* Compute statement nodes which require a completion value.
*/
public final class CompletionValueVisitor extends DefaultNodeVisitor<Completion, State> {
// TODO: This visitor class performs (more or less) live variable analysis. Or rather, with
// proper live variable analysis the visitor shouldn't be necessary at all.
enum Completion {
Empty, Value;
Completion then(Completion c) {
return (this == Value || c == Value) ? Value : Empty;
}
}
private static final class Context {
// unlabelled breaks and continues
private final ArrayDeque<BreakableStatement> breakTargets = new ArrayDeque<>();
private final ArrayDeque<IterationStatement> continueTargets = new ArrayDeque<>();
// labelled breaks and continues
private final HashMap<String, Statement> namedBreakLabels = new HashMap<>();
private final HashMap<String, IterationStatement> namedContinueLabels = new HashMap<>();
void enterIteration(IterationStatement node) {
boolean hasBreak = node.hasBreak();
boolean hasContinue = node.hasContinue();
if (hasBreak || hasContinue) {
if (hasBreak)
breakTargets.push(node);
if (hasContinue)
continueTargets.push(node);
for (String label : node.getLabelSet()) {
if (hasBreak)
namedBreakLabels.put(label, node);
if (hasContinue)
namedContinueLabels.put(label, node);
}
}
}
void exitIteration(IterationStatement node) {
boolean hasBreak = node.hasBreak();
boolean hasContinue = node.hasContinue();
if (hasBreak || hasContinue) {
if (hasBreak)
breakTargets.pop();
if (hasContinue)
continueTargets.pop();
for (String label : node.getLabelSet()) {
if (hasBreak)
namedBreakLabels.remove(label);
if (hasContinue)
namedContinueLabels.remove(label);
}
}
}
void enterBreakable(BreakableStatement node) {
if (node.hasBreak()) {
breakTargets.push(node);
for (String label : node.getLabelSet()) {
namedBreakLabels.put(label, node);
}
}
}
void exitBreakable(BreakableStatement node) {
if (node.hasBreak()) {
breakTargets.pop();
for (String label : node.getLabelSet()) {
namedBreakLabels.remove(label);
}
}
}
void enterLabelled(LabelledStatement node) {
if (node.hasBreak()) {
for (String label : node.getLabelSet()) {
namedBreakLabels.put(label, node);
}
}
}
void exitLabelled(LabelledStatement node) {
if (node.hasBreak()) {
for (String label : node.getLabelSet()) {
namedBreakLabels.remove(label);
}
}
}
Statement breakTarget(BreakStatement node) {
String name = node.getLabel();
return name == null ? breakTargets.peek() : namedBreakLabels.get(name);
}
IterationStatement continueTarget(ContinueStatement node) {
String name = node.getLabel();
return name == null ? continueTargets.peek() : namedContinueLabels.get(name);
}
}
static final class State {
final Context context;
final boolean defaultComputeValue;
boolean computeValue;
boolean writeValue;
State(boolean defaultValue) {
context = new Context();
defaultComputeValue = defaultValue;
computeValue = defaultValue;
}
State(State state) {
context = state.context;
defaultComputeValue = state.defaultComputeValue;
computeValue = state.computeValue;
}
void implicitValue() {
computeValue = false;
}
void requestValue() {
computeValue = defaultComputeValue;
}
void apply(State otherState) {
computeValue = otherState.computeValue;
writeValue |= otherState.writeValue;
}
void enter(Statement node) {
node.setCompletionValue(computeValue);
}
void exit(Statement node) {
node.setCompletionValue(computeValue);
}
void exit(Statement node, boolean value) {
node.setCompletionValue(value);
}
void enterValue(Statement node) {
node.setCompletionValue(computeValue);
}
void exitValue(Statement node) {
node.setCompletionValue(computeValue);
writeValue |= computeValue;
computeValue = false;
}
void exitValue(Statement node, boolean value) {
node.setCompletionValue(value);
writeValue |= value;
computeValue = false;
}
}
private static <T> Iterable<T> reverse(List<T> list) {
return () -> new Iterator<T>() {
final ListIterator<T> iter = list.listIterator(list.size());
@Override
public boolean hasNext() {
return iter.hasPrevious();
}
@Override
public T next() {
return iter.previous();
}
};
}
private static final CompletionValueVisitor INSTANCE = new CompletionValueVisitor();
public static void performCompletion(Script script) {
script.accept(INSTANCE, new State(true));
}
public static void performCompletion(DoExpression expression) {
expression.getStatement().accept(INSTANCE, new State(expression.hasCompletion()));
}
@Override
protected Completion visit(Node node, State state) {
throw new IllegalStateException();
}
private <STATEMENT extends ModuleItem> Completion statements(List<STATEMENT> statements, State state) {
Completion result = Completion.Empty;
for (STATEMENT statement : reverse(statements)) {
result = statement.accept(this, state).then(result);
}
return result;
}
@Override
public Completion visit(Script node, State state) {
return statements(node.getStatements(), state);
}
@Override
public Completion visit(Module node, State state) {
return statements(node.getStatements(), state);
}
@Override
public Completion visit(BlockStatement node, State state) {
state.enter(node);
Completion result = statements(node.getStatements(), state);
state.exit(node);
return result;
}
@Override
public Completion visit(ExpressionStatement node, State state) {
state.enterValue(node);
state.exitValue(node);
return Completion.Value;
}
@Override
public Completion visit(IfStatement node, State state) {
final boolean computeValue = state.computeValue;
boolean innerComputeValue = false;
state.enterValue(node);
node.getThen().accept(this, state);
innerComputeValue |= state.computeValue;
if (node.getOtherwise() != null) {
state.computeValue = computeValue;
node.getOtherwise().accept(this, state);
innerComputeValue |= state.computeValue;
}
state.exitValue(node, innerComputeValue);
return Completion.Value;
}
@Override
public Completion visit(BreakStatement node, State state) {
state.enter(node);
Statement target = state.context.breakTarget(node);
state.exit(node);
if (target == null || target.hasCompletionValue()) {
state.requestValue();
}
return Completion.Value;
}
@Override
public Completion visit(ContinueStatement node, State state) {
state.enter(node);
IterationStatement target = state.context.continueTarget(node);
state.exit(node);
if (target == null || target.hasCompletionValue()) {
state.requestValue();
}
return Completion.Value;
}
@Override
public Completion visit(LabelledStatement node, State state) {
state.enter(node);
state.context.enterLabelled(node);
Completion result = node.getStatement().accept(this, state);
state.context.exitLabelled(node);
state.exit(node);
return result;
}
@Override
protected Completion visit(IterationStatement node, State state) {
state.enterValue(node);
state.context.enterIteration(node);
node.getStatement().accept(this, state);
state.context.exitIteration(node);
state.exitValue(node);
return Completion.Value;
}
@Override
public Completion visit(WithStatement node, State state) {
state.enterValue(node);
node.getStatement().accept(this, state);
state.exitValue(node);
return Completion.Value;
}
@Override
public Completion visit(ReturnStatement node, State state) {
state.implicitValue();
return Completion.Value;
}
@Override
public Completion visit(ThrowStatement node, State state) {
state.implicitValue();
return Completion.Value;
}
@Override
public Completion visit(TryStatement node, State state) {
final boolean computeValue = state.computeValue;
boolean innerComputeValue = false;
state.enterValue(node);
node.getTryBlock().accept(this, state);
innerComputeValue |= state.computeValue;
if (node.getCatchNode() != null) {
state.computeValue = computeValue;
node.getCatchNode().getCatchBlock().accept(this, state);
innerComputeValue |= state.computeValue;
}
for (GuardedCatchNode guardedCatchNode : node.getGuardedCatchNodes()) {
state.computeValue = computeValue;
guardedCatchNode.getCatchBlock().accept(this, state);
innerComputeValue |= state.computeValue;
}
if (node.getFinallyBlock() != null) {
state.computeValue = computeValue;
node.getFinallyBlock().accept(this, state);
innerComputeValue |= state.computeValue;
}
// Request completion value when 'computeValue' is true and the finally block is present.
state.exitValue(node, innerComputeValue || (computeValue && node.getFinallyBlock() != null));
return Completion.Value;
}
@Override
public Completion visit(SwitchStatement node, State state) {
final boolean computeValue = state.computeValue;
boolean innerComputeValue = false;
state.enterValue(node);
state.context.enterBreakable(node);
for (SwitchClause clause : node.getClauses()) {
state.computeValue = computeValue;
statements(clause.getStatements(), state);
innerComputeValue |= state.computeValue;
}
state.context.exitBreakable(node);
// Request completion value when 'computeValue' is true and no catch clauses are present.
state.exitValue(node, innerComputeValue || (computeValue && node.getClauses().isEmpty()));
return Completion.Value;
}
@Override
public Completion visit(LetStatement node, State state) {
return node.getStatement().accept(this, state);
}
@Override
public Completion visit(StatementListMethod node, State state) {
state.enter(node);
State newState = new State(state);
Completion result = statements(node.getStatements(), newState);
state.apply(newState);
// Request completion value when write access to completion value slot was observed.
state.exit(node, newState.writeValue);
return result;
}
@Override
public Completion visit(VariableStatement node, State state) {
return Completion.Empty;
}
@Override
protected Completion visit(Declaration node, State state) {
return Completion.Empty;
}
@Override
public Completion visit(ExportDeclaration node, State value) {
return Completion.Empty;
}
@Override
public Completion visit(ImportDeclaration node, State value) {
return Completion.Empty;
}
@Override
public Completion visit(EmptyStatement node, State state) {
return Completion.Empty;
}
@Override
public Completion visit(DebuggerStatement node, State state) {
return Completion.Empty;
}
@Override
public Completion visit(LabelledFunctionStatement node, State value) {
return Completion.Empty;
}
}