| /* |
| * Copyright 2008 Google Inc. |
| * |
| * 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.gwt.dev.jjs.impl; |
| |
| import com.google.gwt.dev.jjs.SourceInfo; |
| import com.google.gwt.dev.jjs.ast.JBinaryOperation; |
| import com.google.gwt.dev.jjs.ast.JBinaryOperator; |
| import com.google.gwt.dev.jjs.ast.JBlock; |
| import com.google.gwt.dev.jjs.ast.JBooleanLiteral; |
| import com.google.gwt.dev.jjs.ast.JCastOperation; |
| import com.google.gwt.dev.jjs.ast.JConditional; |
| import com.google.gwt.dev.jjs.ast.JExpression; |
| import com.google.gwt.dev.jjs.ast.JExpressionStatement; |
| import com.google.gwt.dev.jjs.ast.JIfStatement; |
| import com.google.gwt.dev.jjs.ast.JMethod; |
| import com.google.gwt.dev.jjs.ast.JNode; |
| import com.google.gwt.dev.jjs.ast.JPrefixOperation; |
| import com.google.gwt.dev.jjs.ast.JPrimitiveType; |
| import com.google.gwt.dev.jjs.ast.JProgram; |
| import com.google.gwt.dev.jjs.ast.JReturnStatement; |
| import com.google.gwt.dev.jjs.ast.JStatement; |
| import com.google.gwt.dev.jjs.ast.JType; |
| import com.google.gwt.dev.jjs.ast.JUnaryOperation; |
| import com.google.gwt.dev.jjs.ast.JUnaryOperator; |
| import com.google.gwt.dev.jjs.ast.JValueLiteral; |
| import com.google.gwt.dev.jjs.ast.js.JMultiExpression; |
| |
| import java.util.List; |
| |
| /** |
| * Methods that both construct and try to simplify AST nodes. If simplification |
| * fails, then the methods will return an original, unmodified version of the |
| * node if one is supplied. The routines do not recurse into their arguments; |
| * the arguments are assumed to already be simplified as much as possible. |
| */ |
| public class Simplifier { |
| /** |
| * TODO: if the AST were normalized, we wouldn't need this. |
| */ |
| public static boolean isEmpty(JStatement stmt) { |
| if (stmt == null) { |
| return true; |
| } |
| return (stmt instanceof JBlock && ((JBlock) stmt).getStatements().isEmpty()); |
| } |
| |
| /** |
| * Negate the supplied expression if negating it makes the expression shorter. |
| * Otherwise, return null. |
| */ |
| static JExpression maybeUnflipBoolean(JExpression expr) { |
| if (expr instanceof JUnaryOperation) { |
| JUnaryOperation unop = (JUnaryOperation) expr; |
| if (unop.getOp() == JUnaryOperator.NOT) { |
| return unop.getArg(); |
| } |
| } |
| return null; |
| } |
| |
| private static <T> List<T> allButLast(List<T> list) { |
| return list.subList(0, list.size() - 1); |
| } |
| |
| private static <T> T last(List<T> list) { |
| return list.get(list.size() - 1); |
| } |
| |
| private final JProgram program; |
| |
| public Simplifier(JProgram program) { |
| this.program = program; |
| } |
| |
| public JExpression cast(JExpression original, SourceInfo info, JType type, JExpression exp) { |
| info = getBestSourceInfo(original, info, exp); |
| if (type == exp.getType()) { |
| return exp; |
| } |
| if ((type instanceof JPrimitiveType) && (exp instanceof JValueLiteral)) { |
| // Statically evaluate casting literals. |
| JPrimitiveType typePrim = (JPrimitiveType) type; |
| JValueLiteral expLit = (JValueLiteral) exp; |
| JValueLiteral casted = typePrim.coerceLiteral(expLit); |
| if (casted != null) { |
| return casted; |
| } |
| } |
| |
| /* |
| * Discard casts from byte or short to int, because such casts are always |
| * implicit anyway. Cannot coerce char since that would change the semantics |
| * of concat. |
| */ |
| if (type == program.getTypePrimitiveInt()) { |
| JType expType = exp.getType(); |
| if ((expType == program.getTypePrimitiveShort()) |
| || (expType == program.getTypePrimitiveByte())) { |
| return exp; |
| } |
| } |
| |
| // no simplification made |
| if (original != null) { |
| return original; |
| } |
| return new JCastOperation(info, type, exp); |
| } |
| |
| public JExpression cast(JType type, JExpression exp) { |
| return cast(null, exp.getSourceInfo(), type, exp); |
| } |
| |
| public JExpression conditional(JConditional original, SourceInfo info, JType type, |
| JExpression condExpr, JExpression thenExpr, JExpression elseExpr) { |
| info = getBestSourceInfo(original, info, condExpr); |
| if (condExpr instanceof JMultiExpression) { |
| // (a,b,c)?d:e -> a,b,(c?d:e) |
| // TODO(spoon): do this outward multi movement for all AST nodes |
| JMultiExpression condMulti = (JMultiExpression) condExpr; |
| JMultiExpression newMulti = new JMultiExpression(info); |
| newMulti.exprs.addAll(allButLast(condMulti.exprs)); |
| newMulti.exprs.add(conditional(null, info, type, last(condMulti.exprs), thenExpr, elseExpr)); |
| // TODO(spoon): immediately simplify the resulting multi |
| return newMulti; |
| } |
| if (condExpr instanceof JBooleanLiteral) { |
| if (((JBooleanLiteral) condExpr).getValue()) { |
| // e.g. (true ? then : else) -> then |
| return thenExpr; |
| } else { |
| // e.g. (false ? then : else) -> else |
| return elseExpr; |
| } |
| } else if (thenExpr instanceof JBooleanLiteral) { |
| if (((JBooleanLiteral) thenExpr).getValue()) { |
| // e.g. (cond ? true : else) -> cond || else |
| return shortCircuitOr(null, info, condExpr, elseExpr); |
| } else { |
| // e.g. (cond ? false : else) -> !cond && else |
| JExpression notCondExpr = not(null, condExpr.getSourceInfo(), condExpr); |
| return shortCircuitAnd(null, info, notCondExpr, elseExpr); |
| } |
| } else if (elseExpr instanceof JBooleanLiteral) { |
| if (((JBooleanLiteral) elseExpr).getValue()) { |
| // e.g. (cond ? then : true) -> !cond || then |
| JExpression notCondExpr = not(null, condExpr.getSourceInfo(), condExpr); |
| return shortCircuitOr(null, info, notCondExpr, thenExpr); |
| } else { |
| // e.g. (cond ? then : false) -> cond && then |
| return shortCircuitAnd(null, info, condExpr, thenExpr); |
| } |
| } else { |
| // e.g. (!cond ? then : else) -> (cond ? else : then) |
| JExpression unflipped = maybeUnflipBoolean(condExpr); |
| if (unflipped != null) { |
| return new JConditional(info, type, unflipped, elseExpr, thenExpr); |
| } |
| } |
| |
| // no simplification made |
| if (original != null) { |
| return original; |
| } |
| return new JConditional(info, type, condExpr, thenExpr, elseExpr); |
| } |
| |
| public JStatement ifStatement(JIfStatement original, SourceInfo info, JExpression condExpr, |
| JStatement thenStmt, JStatement elseStmt, JMethod currentMethod) { |
| info = getBestSourceInfo(original, info, condExpr); |
| if (condExpr instanceof JMultiExpression) { |
| // if(a,b,c) d else e -> {a; b; if(c) d else e; } |
| JMultiExpression condMulti = (JMultiExpression) condExpr; |
| JBlock newBlock = new JBlock(info); |
| for (JExpression expr : allButLast(condMulti.exprs)) { |
| newBlock.addStmt(expr.makeStatement()); |
| } |
| newBlock.addStmt(ifStatement(null, info, last(condMulti.exprs), thenStmt, elseStmt, |
| currentMethod)); |
| // TODO(spoon): immediately simplify the resulting block |
| return newBlock; |
| } |
| |
| if (condExpr instanceof JBooleanLiteral) { |
| JBooleanLiteral booleanLiteral = (JBooleanLiteral) condExpr; |
| boolean boolVal = booleanLiteral.getValue(); |
| if (boolVal && !isEmpty(thenStmt)) { |
| // If true, replace myself with then statement |
| return thenStmt; |
| } else if (!boolVal && !isEmpty(elseStmt)) { |
| // If false, replace myself with else statement |
| return elseStmt; |
| } else { |
| // just prune me |
| return condExpr.makeStatement(); |
| } |
| } |
| |
| if (isEmpty(thenStmt) && isEmpty(elseStmt)) { |
| return condExpr.makeStatement(); |
| } |
| |
| if (!isEmpty(elseStmt)) { |
| // if (!cond) foo else bar -> if (cond) bar else foo |
| JExpression unflipped = Simplifier.maybeUnflipBoolean(condExpr); |
| if (unflipped != null) { |
| // Force sub-parts to blocks, otherwise we break else-if chains. |
| // TODO: this goes away when we normalize the Java AST properly. |
| thenStmt = ensureBlock(thenStmt); |
| elseStmt = ensureBlock(elseStmt); |
| return ifStatement(null, info, unflipped, elseStmt, thenStmt, currentMethod); |
| } |
| } |
| |
| JStatement rewritenStatement = |
| rewriteIfIntoBoolean(info, condExpr, thenStmt, elseStmt, currentMethod); |
| if (rewritenStatement != null) { |
| return rewritenStatement; |
| } |
| |
| // no simplification made |
| if (original != null) { |
| return original; |
| } |
| return new JIfStatement(info, condExpr, thenStmt, elseStmt); |
| } |
| |
| public JExpression not(JPrefixOperation original, SourceInfo info, JExpression arg) { |
| info = getBestSourceInfo(original, info, arg); |
| if (arg instanceof JMultiExpression) { |
| // !(a,b,c) -> (a,b,!c) |
| JMultiExpression argMulti = (JMultiExpression) arg; |
| JMultiExpression newMulti = new JMultiExpression(info); |
| newMulti.exprs.addAll(allButLast(argMulti.exprs)); |
| newMulti.exprs.add(not(null, info, last(argMulti.exprs))); |
| // TODO(spoon): immediately simplify the newMulti |
| return newMulti; |
| } |
| if (arg instanceof JBinaryOperation) { |
| // try to invert the binary operator |
| JBinaryOperation argOp = (JBinaryOperation) arg; |
| JBinaryOperator op = argOp.getOp(); |
| JBinaryOperator newOp = null; |
| if (op == JBinaryOperator.EQ) { |
| // e.g. !(x == y) -> x != y |
| newOp = JBinaryOperator.NEQ; |
| } else if (op == JBinaryOperator.NEQ) { |
| // e.g. !(x != y) -> x == y |
| newOp = JBinaryOperator.EQ; |
| } else if (op == JBinaryOperator.GT) { |
| // e.g. !(x > y) -> x <= y |
| newOp = JBinaryOperator.LTE; |
| } else if (op == JBinaryOperator.LTE) { |
| // e.g. !(x <= y) -> x > y |
| newOp = JBinaryOperator.GT; |
| } else if (op == JBinaryOperator.GTE) { |
| // e.g. !(x >= y) -> x < y |
| newOp = JBinaryOperator.LT; |
| } else if (op == JBinaryOperator.LT) { |
| // e.g. !(x < y) -> x >= y |
| newOp = JBinaryOperator.GTE; |
| } |
| if (newOp != null) { |
| JBinaryOperation newBinOp = |
| new JBinaryOperation(info, argOp.getType(), newOp, argOp.getLhs(), argOp.getRhs()); |
| return newBinOp; |
| } |
| } else if (arg instanceof JPrefixOperation) { |
| // try to invert the unary operator |
| JPrefixOperation argOp = (JPrefixOperation) arg; |
| JUnaryOperator op = argOp.getOp(); |
| // e.g. !!x -> x |
| if (op == JUnaryOperator.NOT) { |
| return argOp.getArg(); |
| } |
| } else if (arg instanceof JBooleanLiteral) { |
| JBooleanLiteral booleanLit = (JBooleanLiteral) arg; |
| return JBooleanLiteral.get(!booleanLit.getValue()); |
| } |
| |
| // no simplification made |
| if (original != null) { |
| return original; |
| } |
| return new JPrefixOperation(info, JUnaryOperator.NOT, arg); |
| } |
| |
| /** |
| * Simplify short circuit AND expressions. |
| * |
| * <pre> |
| * if (true && isWhatever()) -> if (isWhatever()) |
| * if (false && isWhatever()) -> if (false) |
| * |
| * if (isWhatever() && true) -> if (isWhatever()) |
| * if (isWhatever() && false) -> if (false), unless side effects |
| * </pre> |
| */ |
| public JExpression shortCircuitAnd(JBinaryOperation original, SourceInfo info, JExpression lhs, |
| JExpression rhs) { |
| info = getBestSourceInfo(original, info, lhs); |
| if (lhs instanceof JBooleanLiteral) { |
| JBooleanLiteral booleanLiteral = (JBooleanLiteral) lhs; |
| if (booleanLiteral.getValue()) { |
| return rhs; |
| } else { |
| return lhs; |
| } |
| |
| } else if (rhs instanceof JBooleanLiteral) { |
| JBooleanLiteral booleanLiteral = (JBooleanLiteral) rhs; |
| if (booleanLiteral.getValue()) { |
| return lhs; |
| } else if (!lhs.hasSideEffects()) { |
| return rhs; |
| } |
| } |
| // no simplification made |
| if (original != null) { |
| return original; |
| } |
| return new JBinaryOperation(info, rhs.getType(), JBinaryOperator.AND, lhs, rhs); |
| } |
| |
| /** |
| * Simplify short circuit OR expressions. |
| * |
| * <pre> |
| * if (true || isWhatever()) -> if (true) |
| * if (false || isWhatever()) -> if (isWhatever()) |
| * |
| * if (isWhatever() || false) -> if (isWhatever()) |
| * if (isWhatever() || true) -> if (true), unless side effects |
| * </pre> |
| */ |
| public JExpression shortCircuitOr(JBinaryOperation original, SourceInfo info, JExpression lhs, |
| JExpression rhs) { |
| info = getBestSourceInfo(original, info, lhs); |
| if (lhs instanceof JBooleanLiteral) { |
| JBooleanLiteral booleanLiteral = (JBooleanLiteral) lhs; |
| if (booleanLiteral.getValue()) { |
| return lhs; |
| } else { |
| return rhs; |
| } |
| |
| } else if (rhs instanceof JBooleanLiteral) { |
| JBooleanLiteral booleanLiteral = (JBooleanLiteral) rhs; |
| if (!booleanLiteral.getValue()) { |
| return lhs; |
| } else if (!lhs.hasSideEffects()) { |
| return rhs; |
| } |
| } |
| // no simplification made |
| if (original != null) { |
| return original; |
| } |
| return new JBinaryOperation(info, rhs.getType(), JBinaryOperator.OR, lhs, rhs); |
| } |
| |
| private JStatement ensureBlock(JStatement stmt) { |
| if (stmt == null) { |
| return null; |
| } |
| if (!(stmt instanceof JBlock)) { |
| JBlock block = new JBlock(stmt.getSourceInfo()); |
| block.addStmt(stmt); |
| stmt = block; |
| } |
| return stmt; |
| } |
| |
| private JExpression extractExpression(JStatement stmt) { |
| if (stmt instanceof JExpressionStatement) { |
| JExpressionStatement statement = (JExpressionStatement) stmt; |
| return statement.getExpr(); |
| } |
| |
| return null; |
| } |
| |
| private JStatement extractSingleStatement(JStatement stmt) { |
| if (stmt instanceof JBlock) { |
| JBlock block = (JBlock) stmt; |
| if (block.getStatements().size() == 1) { |
| return extractSingleStatement(block.getStatements().get(0)); |
| } |
| } |
| |
| return stmt; |
| } |
| |
| private SourceInfo getBestSourceInfo(JNode original, SourceInfo info, JNode defaultNode) { |
| if (info == null) { |
| if (original == null) { |
| info = defaultNode.getSourceInfo(); |
| } else { |
| info = original.getSourceInfo(); |
| } |
| } |
| return info; |
| } |
| |
| private JStatement rewriteIfIntoBoolean(SourceInfo sourceInfo, JExpression condExpr, |
| JStatement thenStmt, JStatement elseStmt, JMethod currentMethod) { |
| thenStmt = extractSingleStatement(thenStmt); |
| elseStmt = extractSingleStatement(elseStmt); |
| |
| if (thenStmt instanceof JReturnStatement && elseStmt instanceof JReturnStatement |
| && currentMethod != null) { |
| // Special case |
| // if () { return ..; } else { return ..; } => |
| // return ... ? ... : ...; |
| JExpression thenExpression = ((JReturnStatement) thenStmt).getExpr(); |
| JExpression elseExpression = ((JReturnStatement) elseStmt).getExpr(); |
| if (thenExpression == null || elseExpression == null) { |
| // empty returns are not supported. |
| return null; |
| } |
| |
| JConditional conditional = |
| new JConditional(sourceInfo, currentMethod.getType(), condExpr, thenExpression, |
| elseExpression); |
| |
| JReturnStatement returnStatement = new JReturnStatement(sourceInfo, conditional); |
| return returnStatement; |
| } |
| |
| if (elseStmt != null) { |
| // if () { } else { } -> ... ? ... : ... ; |
| JExpression thenExpression = extractExpression(thenStmt); |
| JExpression elseExpression = extractExpression(elseStmt); |
| |
| if (thenExpression != null && elseExpression != null) { |
| JConditional conditional = |
| new JConditional(sourceInfo, JPrimitiveType.VOID, condExpr, thenExpression, |
| elseExpression); |
| |
| return conditional.makeStatement(); |
| } |
| } else { |
| // if () { } -> ... && ...; |
| JExpression thenExpression = extractExpression(thenStmt); |
| |
| if (thenExpression != null) { |
| JBinaryOperator binaryOperator = JBinaryOperator.AND; |
| |
| JExpression unflipExpression = maybeUnflipBoolean(condExpr); |
| if (unflipExpression != null) { |
| condExpr = unflipExpression; |
| binaryOperator = JBinaryOperator.OR; |
| } |
| |
| JBinaryOperation binaryOperation = |
| new JBinaryOperation(sourceInfo, program.getTypeVoid(), binaryOperator, condExpr, |
| thenExpression); |
| |
| return binaryOperation.makeStatement(); |
| } |
| } |
| |
| return null; |
| } |
| } |