All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.MinimizedCondition Maven / Gradle / Ivy

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs.

There is a newer version: v20240317
Show newest version
/*
 * Copyright 2013 The Closure Compiler Authors.
 *
 * 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.javascript.jscomp;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Collections;
import java.util.Comparator;

/**
 * A class that represents a minimized conditional expression.
 * This is a conditional expression that has been massaged according to
 * DeMorgan's laws in order to minimize the length of the source
 * representation.
 * 

* Depending on the context, a leading NOT node in front of the conditional * may or may not be counted as a cost, so this class provides ways to * access minimized versions of both of those abstract syntax trees (ASTs). * * @author [email protected] (Ben Lickly) */ class MinimizedCondition { /** Definitions of the style of minimization preferred. */ enum MinimizationStyle { /** Compute the length of the minimized condition as including * any leading NOT node, if present. */ PREFER_UNNEGATED, /** Compute the length of the minimized condition without penalizing * a leading NOT node, if present. */ ALLOW_LEADING_NOT } /** A representation equivalent to the original condition. */ private final MeasuredNode positive; /** A representation equivalent to the negation of the original condition. */ private final MeasuredNode negative; /** A placeholder at the same AST location as the original condition */ private Node placeholder; private MinimizedCondition(MeasuredNode p, MeasuredNode n) { Preconditions.checkArgument(p.node.getParent() == null); Preconditions.checkArgument(n.node.getParent() == null); positive = p; negative = n.change(); } Node getPlaceholder() { return placeholder; } MinimizedCondition setPlaceholder(Node placeholder) { this.placeholder = placeholder; return this; } /** * Remove the passed condition node from the AST, and then return a * MinimizedCondition that represents the condition node after * minimization. */ static MinimizedCondition fromConditionNode(Node n) { switch (n.getToken()) { case NOT: case AND: case OR: case HOOK: case COMMA: Node placeholder = swapWithPlaceholderNode(n); return computeMinimizedCondition(n).setPlaceholder(placeholder); default: return unoptimized(n); } } /** * Return the shorter representation of the original condition node. *

* Depending on the context, this may require to either penalize or * not the existence of a leading NOT node. *

  • When {@code style} is {@code PREFER_UNNEGATED}, simply try to * minimize the total length of the conditional.
  • *
  • When {@code style} is {@code ALLOW_LEADING_NOT}, prefer the right side * in cases such as: *
    * !x || !y || z ==> !(x && y && !z) *
    * This is useful in contexts such as IFs or HOOKs where subsequent * optimizations can efficiently deal with leading NOTs. *
* * @return the minimized condition MeasuredNode, with equivalent semantics * to that passed to {@link #fromConditionNode}. */ MeasuredNode getMinimized(MinimizationStyle style) { if (style == MinimizationStyle.PREFER_UNNEGATED || positive.node.isNot() || positive.length <= negative.length) { return positive; } else { return negative.addNot(); } } /** * Return a MeasuredNode of the given condition node, without minimizing * the result. *

* Since a MinimizedCondition necessarily must contain two trees, this * method sets the negative side to a {@link Token#SCRIPT} node (never valid * inside an expression) with an unreasonably high length so that it will * never be chosen by {@link #getMinimized}. * * @param n the conditional expression tree to minimize. * This must be connected to the AST, and will be swapped * with a placeholder node during minimization. * @return a MinimizedCondition object representing that tree. */ static MinimizedCondition unoptimized(Node n) { Preconditions.checkNotNull(n.getParent()); Node placeholder = swapWithPlaceholderNode(n); MeasuredNode pos = new MeasuredNode(n, 0, false); MeasuredNode neg = new MeasuredNode(IR.script(), Integer.MAX_VALUE, true); return new MinimizedCondition(pos, neg).setPlaceholder(placeholder); } /** * Remove the given node from the AST, and replace it with a placeholder * SCRIPT node. * @return the new placeholder node. */ private static Node swapWithPlaceholderNode(Node n) { Preconditions.checkNotNull(n.getParent()); Node placeholder = IR.script(); n.replaceWith(placeholder); return placeholder; } /** * Minimize the condition at the given node. * * @param n the conditional expression tree to minimize. * This must be connected to the AST, and will be swapped * with a placeholder node during minimization. * @return a MinimizedCondition object representing that tree. */ private static MinimizedCondition computeMinimizedCondition(Node n) { Preconditions.checkArgument(n.getParent() == null); switch (n.getToken()) { case NOT: { MinimizedCondition subtree = computeMinimizedCondition(n.getFirstChild().detach()); ImmutableList positiveAsts = ImmutableList.of( subtree.positive.cloneTree().addNot(), subtree.negative.cloneTree()); ImmutableList negativeAsts = ImmutableList.of( subtree.negative.negate(), subtree.positive); return new MinimizedCondition( Collections.min(positiveAsts, AST_LENGTH_COMPARATOR), Collections.min(negativeAsts, AST_LENGTH_COMPARATOR)); } case AND: case OR: { Token opType = n.getToken(); Token complementType = opType == Token.AND ? Token.OR : Token.AND; MinimizedCondition leftSubtree = computeMinimizedCondition(n.getFirstChild().detach()); MinimizedCondition rightSubtree = computeMinimizedCondition(n.getLastChild().detach()); ImmutableList positiveAsts = ImmutableList.of( MeasuredNode.addNode(new Node(opType).srcref(n), leftSubtree.positive.cloneTree(), rightSubtree.positive.cloneTree()), MeasuredNode.addNode(new Node(complementType).srcref(n), leftSubtree.negative.cloneTree(), rightSubtree.negative.cloneTree()).negate()); ImmutableList negativeAsts = ImmutableList.of( MeasuredNode.addNode(new Node(opType).srcref(n), leftSubtree.positive, rightSubtree.positive).negate(), MeasuredNode.addNode(new Node(complementType).srcref(n), leftSubtree.negative, rightSubtree.negative)); return new MinimizedCondition( Collections.min(positiveAsts, AST_LENGTH_COMPARATOR), Collections.min(negativeAsts, AST_LENGTH_COMPARATOR)); } case HOOK: { Node cond = n.getFirstChild(); Node thenNode = cond.getNext(); Node elseNode = thenNode.getNext(); MinimizedCondition thenSubtree = computeMinimizedCondition(thenNode.detach()); MinimizedCondition elseSubtree = computeMinimizedCondition(elseNode.detach()); MeasuredNode posTree = MeasuredNode.addNode( new Node(Token.HOOK, cond.cloneTree()).srcref(n), thenSubtree.positive, elseSubtree.positive); MeasuredNode negTree = MeasuredNode.addNode( new Node(Token.HOOK, cond.cloneTree()).srcref(n), thenSubtree.negative, elseSubtree.negative); return new MinimizedCondition(posTree, negTree); } case COMMA: { Node lhs = n.getFirstChild(); MinimizedCondition rhsSubtree = computeMinimizedCondition(lhs.getNext().detach()); MeasuredNode posTree = MeasuredNode.addNode( new Node(Token.COMMA, lhs.cloneTree()).srcref(n), rhsSubtree.positive); MeasuredNode negTree = MeasuredNode.addNode( new Node(Token.COMMA, lhs.cloneTree()).srcref(n), rhsSubtree.negative); return new MinimizedCondition(posTree, negTree); } default: { MeasuredNode pos = new MeasuredNode(n, 0, false); MeasuredNode neg = pos.cloneTree().negate(); return new MinimizedCondition(pos, neg); } } } private static final Comparator AST_LENGTH_COMPARATOR = new Comparator() { @Override public int compare(MeasuredNode o1, MeasuredNode o2) { return o1.length - o2.length; } }; /** An AST-node along with some additional metadata. */ static class MeasuredNode { private Node node; private int length; private boolean changed; Node getNode() { return node; } boolean isChanged() { return changed; } MeasuredNode(Node n, int len, boolean ch) { node = n; length = len; changed = ch; } private MeasuredNode negate() { this.change(); switch (node.getToken()) { case EQ: node.setToken(Token.NE); return this; case NE: node.setToken(Token.EQ); return this; case SHEQ: node.setToken(Token.SHNE); return this; case SHNE: node.setToken(Token.SHEQ); return this; default: return this.addNot(); } } private MeasuredNode change() { this.changed = true; return this; } private MeasuredNode addNot() { node = new Node(Token.NOT, node).srcref(node); length += estimateCostOneLevel(node); return this; } /** * Estimate the number of characters in the textual representation of * the given node and that will be devoted to negation or parentheses. * Since these are the only characters that flipping a condition * according to De Morgan's rule can affect, these are the only ones * we count. * Not nodes are counted by the NOT node itself, whereas * parentheses around an expression are counted by the parent node. * @param n the node to be checked. * @return the number of negations and parentheses in the node. */ private static int estimateCostOneLevel(Node n) { int cost = 0; if (n.isNot()) { cost++; // A negation is needed. } int parentPrecedence = NodeUtil.precedence(n.getToken()); for (Node child = n.getFirstChild(); child != null; child = child.getNext()) { if (PeepholeMinimizeConditions.isLowerPrecedence(child, parentPrecedence)) { cost += 2; // A pair of parenthesis is needed. } } return cost; } private MeasuredNode cloneTree() { return new MeasuredNode(node.cloneTree(), length, changed); } private static MeasuredNode addNode(Node parent, MeasuredNode... children) { int cost = 0; boolean changed = false; for (MeasuredNode child : children) { parent.addChildToBack(child.node); cost += child.length; changed = changed || child.changed; } cost += estimateCostOneLevel(parent); return new MeasuredNode(parent, cost, changed); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy