/*
* Copyright 2011 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devtools.j2objc.translate;
import com.google.devtools.j2objc.ast.Block;
import com.google.devtools.j2objc.ast.BreakStatement;
import com.google.devtools.j2objc.ast.ContinueStatement;
import com.google.devtools.j2objc.ast.DoStatement;
import com.google.devtools.j2objc.ast.EmptyStatement;
import com.google.devtools.j2objc.ast.EnhancedForStatement;
import com.google.devtools.j2objc.ast.ForStatement;
import com.google.devtools.j2objc.ast.LabeledStatement;
import com.google.devtools.j2objc.ast.MethodDeclaration;
import com.google.devtools.j2objc.ast.SimpleName;
import com.google.devtools.j2objc.ast.Statement;
import com.google.devtools.j2objc.ast.TreeUtil;
import com.google.devtools.j2objc.ast.TreeVisitor;
import com.google.devtools.j2objc.ast.WhileStatement;
import java.util.HashMap;
import java.util.Map;
/**
* Rewrites multiple labels that have the same name.
* Rewrites labeled break and continue statements, converting them to goto for
* the correct control flow.
*
* @author Tom Ball, Keith Stanger
*/
public class LabelRewriter extends TreeVisitor {
@Override
public boolean visit(MethodDeclaration node) {
// Rename any labels that have the same names; legal in Java but not C.
final Map<String, Integer> labelCounts = new HashMap<>();
node.accept(new TreeVisitor() {
@Override
public void endVisit(LabeledStatement labeledStatement) {
final String name = labeledStatement.getLabel().getIdentifier();
int value = labelCounts.containsKey(name) ? labelCounts.get(name) + 1 : 1;
labelCounts.put(name, value);
if (value > 1) {
final String newName = name + '_' + value;
labeledStatement.setLabel(new SimpleName(newName));
// Update references to this label.
labeledStatement.accept(new TreeVisitor() {
@Override
public void endVisit(ContinueStatement node) {
if (node.getLabel() != null && node.getLabel().getIdentifier().equals(name)) {
node.setLabel(new SimpleName(newName));
}
}
@Override
public void endVisit(BreakStatement node) {
if (node.getLabel() != null && node.getLabel().getIdentifier().equals(name)) {
node.setLabel(new SimpleName(newName));
}
}
});
}
}
});
return true;
}
private static Statement getLoopBody(Statement s) {
if (s instanceof DoStatement) {
return ((DoStatement) s).getBody();
} else if (s instanceof EnhancedForStatement) {
return ((EnhancedForStatement) s).getBody();
} else if (s instanceof ForStatement) {
return ((ForStatement) s).getBody();
} else if (s instanceof WhileStatement) {
return ((WhileStatement) s).getBody();
}
return null;
}
@Override
public void endVisit(LabeledStatement node) {
Statement loopBody = getLoopBody(node.getBody());
final String labelIdentifier = node.getLabel().getIdentifier();
final boolean[] hasContinue = new boolean[1];
final boolean[] hasBreak = new boolean[1];
node.accept(new TreeVisitor() {
@Override
public void endVisit(ContinueStatement node) {
if (node.getLabel() != null && node.getLabel().getIdentifier().equals(labelIdentifier)) {
hasContinue[0] = true;
node.setLabel(new SimpleName("continue_" + labelIdentifier));
}
}
@Override
public void endVisit(BreakStatement node) {
if (node.getLabel() != null && node.getLabel().getIdentifier().equals(labelIdentifier)) {
hasBreak[0] = true;
node.setLabel(new SimpleName("break_" + labelIdentifier));
}
}
});
if (hasContinue[0]) {
assert loopBody != null : "Continue statements must be inside a loop.";
LabeledStatement newLabelStmt = new LabeledStatement("continue_" + labelIdentifier);
newLabelStmt.setBody(new EmptyStatement());
// Put the loop body into an inner block so the continue label is outside
// the scope of any variable initializations.
Block newBlock = new Block();
loopBody.replaceWith(newBlock);
newBlock.addStatement(loopBody);
newBlock.addStatement(newLabelStmt);
}
if (hasBreak[0]) {
LabeledStatement newLabelStmt = new LabeledStatement("break_" + labelIdentifier);
newLabelStmt.setBody(new EmptyStatement());
TreeUtil.insertAfter(node, newLabelStmt);
}
if (hasContinue[0] || hasBreak[0]) {
// Replace this node with its statement, thus deleting the label.
node.replaceWith(TreeUtil.remove(node.getBody()));
}
}
}