private void tryReduceOperandsForOp(Node n) { switch (n.getType()) { case ADD: Node left = n.getFirstChild(); Node right = n.getLastChild(); if (!NodeUtil.mayBeString(left, shouldUseTypes) && !NodeUtil.mayBeString(right, shouldUseTypes)) { tryConvertOperandsToNumber(n); } break; case ASSIGN_BITOR: case ASSIGN_BITXOR: case ASSIGN_BITAND: // TODO(johnlenz): convert these to integers. case ASSIGN_LSH: case ASSIGN_RSH: case ASSIGN_URSH: case ASSIGN_SUB: case ASSIGN_MUL: case ASSIGN_MOD: case ASSIGN_DIV: tryConvertToNumber(n.getLastChild()); break; case BITNOT: case BITOR: case BITXOR: case BITAND: case LSH: case RSH: case URSH: case SUB: case MUL: case MOD: case DIV: case POS: case NEG: tryConvertOperandsToNumber(n); break; } }
private Node tryFoldAdd(Node node, Node left, Node right) { Preconditions.checkArgument(node.isAdd()); if (NodeUtil.mayBeString(node, shouldUseTypes)) { if (NodeUtil.isLiteralValue(left, false) && NodeUtil.isLiteralValue(right, false)) { // '6' + 7 return tryFoldAddConstantString(node, left, right); } else { // a + 7 or 6 + a return tryFoldChildAddString(node, left, right); } } else { // Try arithmetic add Node result = tryFoldArithmeticOp(node, left, right); if (result != node) { return result; } return tryFoldLeftChildOp(node, left, right); } }
/** * Expressions such as [foo() * 10 * 20] generate parse trees where no node has two const children * ((foo() * 10) * 20), so performArithmeticOp() won't fold it -- tryFoldLeftChildOp() will. * Specifically, it folds associative expressions where: - The left child is also an associative * expression of the same time. - The right child is a constant NUMBER constant. - The left * child's right child is a NUMBER constant. */ private Node tryFoldLeftChildOp(Node n, Node left, Node right) { Token opType = n.getType(); Preconditions.checkState( (NodeUtil.isAssociative(opType) && NodeUtil.isCommutative(opType)) || n.isAdd()); Preconditions.checkState(!n.isAdd() || !NodeUtil.mayBeString(n, shouldUseTypes)); // Use getNumberValue to handle constants like "NaN" and "Infinity" // other values are converted to numbers elsewhere. Double rightValObj = NodeUtil.getNumberValue(right, shouldUseTypes); if (rightValObj != null && left.getType() == opType) { Preconditions.checkState(left.getChildCount() == 2); Node ll = left.getFirstChild(); Node lr = ll.getNext(); Node valueToCombine = ll; Node replacement = performArithmeticOp(opType, valueToCombine, right); if (replacement == null) { valueToCombine = lr; replacement = performArithmeticOp(opType, valueToCombine, right); } if (replacement != null) { // Remove the child that has been combined left.removeChild(valueToCombine); // Replace the left op with the remaining child. n.replaceChild(left, left.removeFirstChild()); // New "-Infinity" node need location info explicitly // added. replacement.useSourceInfoIfMissingFromForTree(right); n.replaceChild(right, replacement); reportCodeChange(); } } return n; }
/** Try to fold arithmetic binary operators */ private Node performArithmeticOp(Token opType, Node left, Node right) { // Unlike other operations, ADD operands are not always converted // to Number. if (opType == Token.ADD && (NodeUtil.mayBeString(left, shouldUseTypes) || NodeUtil.mayBeString(right, shouldUseTypes))) { return null; } double result; // TODO(johnlenz): Handle NaN with unknown value. BIT ops convert NaN // to zero so this is a little awkward here. Double lValObj = NodeUtil.getNumberValue(left, shouldUseTypes); if (lValObj == null) { return null; } Double rValObj = NodeUtil.getNumberValue(right, shouldUseTypes); if (rValObj == null) { return null; } double lval = lValObj; double rval = rValObj; switch (opType) { case BITAND: result = NodeUtil.toInt32(lval) & NodeUtil.toInt32(rval); break; case BITOR: result = NodeUtil.toInt32(lval) | NodeUtil.toInt32(rval); break; case BITXOR: result = NodeUtil.toInt32(lval) ^ NodeUtil.toInt32(rval); break; case ADD: result = lval + rval; break; case SUB: result = lval - rval; break; case MUL: result = lval * rval; break; case MOD: if (rval == 0) { return null; } result = lval % rval; break; case DIV: if (rval == 0) { return null; } result = lval / rval; break; default: throw new Error("Unexpected arithmetic operator"); } // TODO(johnlenz): consider removing the result length check. // length of the left and right value plus 1 byte for the operator. if ((String.valueOf(result).length() <= String.valueOf(lval).length() + String.valueOf(rval).length() + 1 // Do not try to fold arithmetic for numbers > 2^53. After that // point, fixed-point math starts to break down and become inaccurate. && Math.abs(result) <= MAX_FOLD_NUMBER) || Double.isNaN(result) || result == Double.POSITIVE_INFINITY || result == Double.NEGATIVE_INFINITY) { return NodeUtil.numberNode(result, null); } return null; }