/*
* 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.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.devtools.j2objc.ast.Assignment;
import com.google.devtools.j2objc.ast.DoStatement;
import com.google.devtools.j2objc.ast.Expression;
import com.google.devtools.j2objc.ast.FunctionInvocation;
import com.google.devtools.j2objc.ast.IfStatement;
import com.google.devtools.j2objc.ast.InfixExpression;
import com.google.devtools.j2objc.ast.MethodDeclaration;
import com.google.devtools.j2objc.ast.MethodInvocation;
import com.google.devtools.j2objc.ast.ParenthesizedExpression;
import com.google.devtools.j2objc.ast.PrefixExpression;
import com.google.devtools.j2objc.ast.SimpleName;
import com.google.devtools.j2objc.ast.Statement;
import com.google.devtools.j2objc.ast.TreeNode;
import com.google.devtools.j2objc.ast.TreeUtil;
import com.google.devtools.j2objc.ast.TreeVisitor;
import com.google.devtools.j2objc.ast.VariableDeclarationStatement;
import com.google.devtools.j2objc.ast.WhileStatement;
import com.google.devtools.j2objc.types.GeneratedVariableElement;
import com.google.devtools.j2objc.util.TypeUtil;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
/**
* Detects deep expression trees and extracts them into separate statements.
*
* @author Keith Stanger
*/
public class ComplexExpressionExtractor extends TreeVisitor {
// The ObjC compiler tends to fail with roughly 100 chained method calls.
private static final int DEFAULT_MAX_DEPTH = 50;
private static int maxDepth = DEFAULT_MAX_DEPTH;
private Map<Expression, Integer> depths = Maps.newHashMap();
private ExecutableElement currentMethod;
private Statement currentStatement;
private int count = 1;
@VisibleForTesting
static void setMaxDepth(int newMaxDepth) {
maxDepth = newMaxDepth;
}
@VisibleForTesting
static void resetMaxDepth() {
maxDepth = DEFAULT_MAX_DEPTH;
}
private void handleNode(Expression node, Collection<Expression> children) {
if (node.getParent() instanceof Statement) {
return;
}
int depth = 0;
for (Expression child : children) {
Integer childDepth = depths.get(child);
depth = Math.max(depth, childDepth != null ? childDepth : 1);
}
if (depth >= maxDepth) {
VariableElement newVar = GeneratedVariableElement.newLocalVar(
"complex$" + count++, node.getTypeMirror(), currentMethod);
Statement newStmt = new VariableDeclarationStatement(newVar, node.copy());
assert currentStatement != null;
TreeUtil.insertBefore(currentStatement, newStmt);
node.replaceWith(new SimpleName(newVar));
} else {
depths.put(node, depth + 1);
}
}
@Override
public boolean preVisit(TreeNode node) {
super.preVisit(node);
if (node instanceof Statement) {
currentStatement = (Statement) node;
}
return true;
}
@Override
public boolean visit(MethodDeclaration node) {
currentMethod = node.getExecutableElement();
return true;
}
@Override
public void endVisit(MethodDeclaration node) {
currentMethod = null;
}
@Override
public void endVisit(InfixExpression node) {
handleNode(node, node.getOperands());
}
@Override
public void endVisit(MethodInvocation node) {
Expression receiver = node.getExpression();
List<Expression> args = node.getArguments();
List<Expression> children = Lists.newArrayListWithCapacity(args.size() + 1);
if (receiver != null) {
children.add(receiver);
}
children.addAll(args);
handleNode(node, children);
}
@Override
public void endVisit(FunctionInvocation node) {
handleNode(node, node.getArguments());
}
@Override
public void endVisit(PrefixExpression node) {
TreeNode parent = node.getParent();
if (parent == null) {
return;
}
// Check for balancing dereference and address-of operators.
if (parent instanceof PrefixExpression) {
PrefixExpression.Operator thisOp = node.getOperator();
PrefixExpression.Operator parentOp = ((PrefixExpression) parent).getOperator();
if ((thisOp == PrefixExpression.Operator.DEREFERENCE
&& parentOp == PrefixExpression.Operator.ADDRESS_OF)
|| (thisOp == PrefixExpression.Operator.ADDRESS_OF
&& parentOp == PrefixExpression.Operator.DEREFERENCE)) {
parent.replaceWith(TreeUtil.remove(node.getOperand()));
return;
}
}
// Some other translation passes may have inserted prefix expressions
// without checking if parentheses were necessary.
switch (parent.getKind()) {
case POSTFIX_EXPRESSION:
case PREFIX_EXPRESSION: // Parentheses not needed, but better for readability.
ParenthesizedExpression.parenthesizeAndReplace(node);
break;
default:
// Ignore.
}
}
@Override
public void endVisit(Assignment node) {
if (TypeUtil.isBoolean(node.getTypeMirror())) {
if (node.getRightHandSide() instanceof InfixExpression) {
// Avoid clang precedence warning by putting parentheses around expression.
ParenthesizedExpression.parenthesizeAndReplace(node.getRightHandSide());
}
// Avoid clang parentheses warning when assignments are used as conditional expressions
// in statements. ConditionalExpressions don't need to change, though, since it's a
// Java syntax error if an assignment-as-conditional use isn't parenthesized already.
TreeNode parent = node.getParent();
if ((parent instanceof DoStatement && node == ((DoStatement) parent).getExpression())
|| (parent instanceof IfStatement && node == ((IfStatement) parent).getExpression())
|| (parent instanceof WhileStatement
&& node == ((WhileStatement) parent).getExpression())) {
ParenthesizedExpression.parenthesizeAndReplace(node);
}
}
}
/**
* If an equality (==) expression has double-parentheses, remove one set.
* This avoids clang's -Wparentheses-equality warning.
*/
@Override
public void endVisit(ParenthesizedExpression node) {
Expression expr = node.getExpression();
if (expr instanceof ParenthesizedExpression) {
Expression inner = ((ParenthesizedExpression) expr).getExpression();
if (isEqualityExpression(inner)) {
node.replaceWith(TreeUtil.remove(expr));
}
} else if (!(node.getParent() instanceof Expression) && isEqualityExpression(expr)) {
node.replaceWith(TreeUtil.remove(expr));
}
}
private boolean isEqualityExpression(Expression expr) {
return expr instanceof InfixExpression
&& ((InfixExpression) expr).getOperator() == InfixExpression.Operator.EQUALS;
}
}