Package com.google.javascript.jscomp

Source Code of com.google.javascript.jscomp.PeepholeReplaceKnownMethods

/*
* Copyright 2011 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.Lists;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;

import java.util.List;
import java.util.Locale;

/**
* Just to fold known methods when they are called with constants.
*
*/
class PeepholeReplaceKnownMethods extends AbstractPeepholeOptimization{

  // The LOCALE independent "locale"
  private static final Locale ROOT_LOCALE = new Locale("");

  @Override
  Node optimizeSubtree(Node subtree) {
    if (NodeUtil.isCall(subtree) ){
      return tryFoldKnownMethods(subtree);
    }
    return subtree;
  }

  private Node tryFoldKnownMethods(Node subtree) {
    // For now we only support string methods .join(),
    // .indexOf(), .substring() and .substr()
    // and numeric methods parseInt() and parseFloat().

    subtree = tryFoldArrayJoin(subtree);

    if (subtree.getType() == Token.CALL) {
      Node callTarget = subtree.getFirstChild();
      if (callTarget == null) {
        return subtree;
      }

      if (NodeUtil.isGet(callTarget)) {
        subtree = tryFoldKnownStringMethods(subtree);
      } else {
        subtree = tryFoldKnownNumericMethods(subtree);
      }
    }

    return subtree;
  }

  /**
   * Try to evaluate known String methods
   *    .indexOf(), .substr(), .substring()
   */
  private Node tryFoldKnownStringMethods(Node subtree) {
    Preconditions.checkArgument(subtree.getType() == Token.CALL);

    // check if this is a call on a string method
    // then dispatch to specific folding method.
    Node callTarget = subtree.getFirstChild();
    if (callTarget == null) {
      return subtree;
    }

    if (!NodeUtil.isGet(callTarget)) {
      return subtree;
    }

    Node stringNode = callTarget.getFirstChild();
    Node functionName = stringNode.getNext();

    if ((stringNode.getType() != Token.STRING) ||
        (functionName.getType() != Token.STRING)) {
      return subtree;
    }

    String functionNameString = functionName.getString();
    Node firstArg = callTarget.getNext();
    if (firstArg == null) {
      if (functionNameString.equals("toLowerCase")) {
        subtree = tryFoldStringToLowerCase(subtree, stringNode);
      } else if (functionNameString.equals("toUpperCase")) {
        subtree = tryFoldStringToUpperCase(subtree, stringNode);
      }
      return subtree;
    } else if (NodeUtil.isImmutableValue(firstArg)) {
      if (functionNameString.equals("indexOf") ||
          functionNameString.equals("lastIndexOf")) {
        subtree = tryFoldStringIndexOf(subtree, functionNameString,
            stringNode, firstArg);
      } else if (functionNameString.equals("substr")) {
        subtree = tryFoldStringSubstr(subtree, stringNode, firstArg);
      } else if (functionNameString.equals("substring")) {
        subtree = tryFoldStringSubstring(subtree, stringNode, firstArg);
      }
    }

    return subtree;
  }

  /**
   * Try to evaluate known Numeric methods
   *    .parseInt(), parseFloat()
   */
  private Node tryFoldKnownNumericMethods(Node subtree) {
    Preconditions.checkArgument(subtree.getType() == Token.CALL);

    if (isASTNormalized()) {
      // check if this is a call on a string method
      // then dispatch to specific folding method.
      Node callTarget = subtree.getFirstChild();

      if (!NodeUtil.isName(callTarget)) {
        return subtree;
      }

      String functionNameString = callTarget.getString();
      Node firstArgument = callTarget.getNext();
      if ((firstArgument != null) &&
          (firstArgument.getType() == Token.STRING ||
           firstArgument.getType() == Token.NUMBER)) {
        if (functionNameString.equals("parseInt") ||
            functionNameString.equals("parseFloat")) {
          subtree = tryFoldParseNumber(subtree, functionNameString,
              firstArgument);
        }
      }
    }
    return subtree;
  }

  /**
   * @return The lowered string Node.
   */
  private Node tryFoldStringToLowerCase(Node subtree, Node stringNode) {
    // From Rhino, NativeString.java. See ECMA 15.5.4.11
    String lowered = stringNode.getString().toLowerCase(ROOT_LOCALE);
    Node replacement = Node.newString(lowered);
    subtree.getParent().replaceChild(subtree, replacement);
    reportCodeChange();
    return replacement;
  }

  /**
   * @return The uppered string Node.
   */
  private Node tryFoldStringToUpperCase(Node subtree, Node stringNode) {
    // From Rhino, NativeString.java. See ECMA 15.5.4.12
    String uppered = stringNode.getString().toUpperCase(ROOT_LOCALE);
    Node replacement = Node.newString(uppered);
    subtree.getParent().replaceChild(subtree, replacement);
    reportCodeChange();
    return replacement;
  }

  /**
   * @param input string representation of a number
   * @return string with leading and trailing zeros removed
   */
  private String normalizeNumericString(String input) {
    if (input == null || input.length() == 0) {
      return input;
    }

    int startIndex = 0, endIndex = input.length() - 1;
    while (startIndex < input.length() && input.charAt(startIndex) == '0') {
      startIndex++;
    }
    while (endIndex >= 0 && input.charAt(endIndex) == '0') {
      endIndex--;
    }
    if (startIndex >= endIndex) {
      return input;
    }

    return input.substring(startIndex, endIndex + 1);
  }

  /**
   * Try to evaluate parseInt, parseFloat:
   *     parseInt("1") -> 1
   *     parseInt("1", 10) -> 1
   *     parseFloat("1.11") -> 1.11
   */
  private Node tryFoldParseNumber(
      Node n, String functionName, Node firstArg) {
    Preconditions.checkArgument(n.getType() == Token.CALL);

    boolean isParseInt = functionName.equals("parseInt");
    Node secondArg = firstArg.getNext();

    // Second argument is only used as the radix for parseInt
    int radix = 0;
    if (secondArg != null) {
      if (!isParseInt) {
        return n;
      }

      // Third-argument and non-numeric second arg are problematic. Discard.
      if ((secondArg.getNext() != null) ||
          (secondArg.getType() != Token.NUMBER)) {
        return n;
      } else {
        double tmpRadix = secondArg.getDouble();
        if (tmpRadix != (int)tmpRadix)
          return n;
        radix = (int)tmpRadix;
        if (radix < 0 || radix == 1 || radix > 36) {
          return n;
        }
      }
    }

    // stringVal must be a valid string.
    String stringVal = null;
    Double checkVal;
    if (firstArg.getType() == Token.NUMBER) {
      checkVal = NodeUtil.getNumberValue(firstArg);
      if (!(radix == 0 || radix == 10) && isParseInt) {
        //Convert a numeric first argument to a different base
        stringVal = String.valueOf(checkVal.intValue());
      } else {
        // If parseFloat is called with a numeric argument,
        // replace it with just the number.
        // If parseInt is called with a numeric first argument and the radix
        // is 10 or omitted, just replace it with the number
        Node numericNode;
        if (isParseInt) {
          numericNode = Node.newNumber(checkVal.intValue());
        } else {
          numericNode = Node.newNumber(checkVal);
        }
        n.getParent().replaceChild(n, numericNode);
        reportCodeChange();
        return numericNode;
      }
    } else {
      stringVal = NodeUtil.getStringValue(firstArg);
      if (stringVal == null) {
        return n;
      }

      //Check that the string is in a format we can recognize
      checkVal = NodeUtil.getStringNumberValue(stringVal);
      if (checkVal == null || checkVal == Double.NaN) {
        return n;
      }

      stringVal = NodeUtil.trimJsWhiteSpace(stringVal);
    }

    Node newNode;
    if (isParseInt) {
      if (radix == 0 || radix == 16) {
        if (stringVal.length() > 1 &&
            stringVal.substring(0, 2).equalsIgnoreCase("0x")) {
          radix = 16;
          stringVal = stringVal.substring(2);
        } else if (radix == 0) {
          // if a radix is not specified or is 0 and the most
          // significant digit is "0", the string will parse
          // with a radix of 8 on some browsers, so leave
          // this case alone. This check does not apply in
          // script mode ECMA5 or greater
          if (!isEcmaScript5OrGreater() &&
              stringVal.substring(0, 1).equals("0")) {
            return n;
          }

          radix = 10;
        }
      }
      int newVal = 0;
      try {
        newVal = Integer.parseInt(stringVal, radix);
      } catch (NumberFormatException e) {
        return n;
      }

      newNode = Node.newNumber(newVal);
    } else {
      String normalizedNewVal = "0";
      try {
        double newVal = Double.parseDouble(stringVal);
        newNode = Node.newNumber(newVal);
        normalizedNewVal = normalizeNumericString(String.valueOf(newVal));
      }
      catch(NumberFormatException e) {
        return n;
      }
      // Make sure that the parsed number matches the original string
      // This prevents rounding differences between the java implementation
      // and native script.
      if (!normalizeNumericString(stringVal).equals(normalizedNewVal)) {
        return n;
      }
    }

    n.getParent().replaceChild(n, newNode);

    reportCodeChange();

    return newNode;
  }

  /**
   * Try to evaluate String.indexOf/lastIndexOf:
   *     "abcdef".indexOf("bc") -> 1
   *     "abcdefbc".indexOf("bc", 3) -> 6
   */
  private Node tryFoldStringIndexOf(
      Node n, String functionName, Node lstringNode, Node firstArg) {
    Preconditions.checkArgument(n.getType() == Token.CALL);
    Preconditions.checkArgument(lstringNode.getType() == Token.STRING);

    String lstring = NodeUtil.getStringValue(lstringNode);
    boolean isIndexOf = functionName.equals("indexOf");
    Node secondArg = firstArg.getNext();
    String searchValue = NodeUtil.getStringValue(firstArg);
    // searchValue must be a valid string.
    if (searchValue == null) {
      return n;
    }
    int fromIndex = isIndexOf ? 0 : lstring.length();
    if (secondArg != null) {
      // Third-argument and non-numeric second arg are problematic. Discard.
      if ((secondArg.getNext() != null) ||
          (secondArg.getType() != Token.NUMBER)) {
        return n;
      } else {
        fromIndex = (int) secondArg.getDouble();
      }
    }
    int indexVal = isIndexOf ? lstring.indexOf(searchValue, fromIndex)
                             : lstring.lastIndexOf(searchValue, fromIndex);
    Node newNode = Node.newNumber(indexVal);
    n.getParent().replaceChild(n, newNode);

    reportCodeChange();

    return newNode;
  }

  /**
   * Try to fold an array join: ['a', 'b', 'c'].join('') -> 'abc';
   */
  private Node tryFoldArrayJoin(Node n) {
    Node callTarget = n.getFirstChild();

    if (callTarget == null || !NodeUtil.isGetProp(callTarget)) {
      return n;
    }

    Node right = callTarget.getNext();
    if (right != null && !NodeUtil.isImmutableValue(right)) {
      return n;
    }

    Node arrayNode = callTarget.getFirstChild();
    Node functionName = arrayNode.getNext();

    if ((arrayNode.getType() != Token.ARRAYLIT) ||
        !functionName.getString().equals("join")) {
      return n;
    }

    String joinString = (right == null) ? "," : NodeUtil.getStringValue(right);
    List<Node> arrayFoldedChildren = Lists.newLinkedList();
    StringBuilder sb = null;
    int foldedSize = 0;
    Node prev = null;
    Node elem = arrayNode.getFirstChild();
    // Merges adjacent String nodes.
    while (elem != null) {
      if (NodeUtil.isImmutableValue(elem) || elem.getType() == Token.EMPTY) {
        if (sb == null) {
          sb = new StringBuilder();
        } else {
          sb.append(joinString);
        }
        sb.append(NodeUtil.getArrayElementStringValue(elem));
      } else {
        if (sb != null) {
          Preconditions.checkNotNull(prev);
          // + 2 for the quotes.
          foldedSize += sb.length() + 2;
          arrayFoldedChildren.add(
              Node.newString(sb.toString()).copyInformationFrom(prev));
          sb = null;
        }
        foldedSize += InlineCostEstimator.getCost(elem);
        arrayFoldedChildren.add(elem);
      }
      prev = elem;
      elem = elem.getNext();
    }

    if (sb != null) {
      Preconditions.checkNotNull(prev);
      // + 2 for the quotes.
      foldedSize += sb.length() + 2;
      arrayFoldedChildren.add(
          Node.newString(sb.toString()).copyInformationFrom(prev));
    }
    // one for each comma.
    foldedSize += arrayFoldedChildren.size() - 1;

    int originalSize = InlineCostEstimator.getCost(n);
    switch (arrayFoldedChildren.size()) {
      case 0:
        Node emptyStringNode = Node.newString("");
        n.getParent().replaceChild(n, emptyStringNode);
        reportCodeChange();
        return emptyStringNode;
      case 1:
        Node foldedStringNode = arrayFoldedChildren.remove(0);
        if (foldedSize > originalSize) {
          return n;
        }
        arrayNode.detachChildren();
        if (foldedStringNode.getType() != Token.STRING) {
          // If the Node is not a string literal, ensure that
          // it is coerced to a string.
          Node replacement = new Node(Token.ADD,
              Node.newString("").copyInformationFrom(n),
              foldedStringNode);
          foldedStringNode = replacement;
        }
        n.getParent().replaceChild(n, foldedStringNode);
        reportCodeChange();
        return foldedStringNode;
      default:
        // No folding could actually be performed.
        if (arrayFoldedChildren.size() == arrayNode.getChildCount()) {
          return n;
        }
        int kJoinOverhead = "[].join()".length();
        foldedSize += kJoinOverhead;
        foldedSize += (right != null) ? InlineCostEstimator.getCost(right) : 0;
        if (foldedSize > originalSize) {
          return n;
        }
        arrayNode.detachChildren();
        for (Node node : arrayFoldedChildren) {
          arrayNode.addChildToBack(node);
        }
        reportCodeChange();
        break;
    }

    return n;
  }

  /**
   * Try to fold .substr() calls on strings
   */
  private Node tryFoldStringSubstr(Node n, Node stringNode, Node arg1) {
    Preconditions.checkArgument(n.getType() == Token.CALL);
    Preconditions.checkArgument(stringNode.getType() == Token.STRING);

    int start, length;
    String stringAsString = stringNode.getString();

    // TODO(nicksantos): We really need a NodeUtil.getNumberValue
    // function.
    if (arg1 != null && arg1.getType() == Token.NUMBER) {
      start = (int) arg1.getDouble();
    } else {
      return n;
    }

    Node arg2 = arg1.getNext();
    if (arg2 != null) {
      if (arg2.getType() == Token.NUMBER) {
        length = (int) arg2.getDouble();
      } else {
        return n;
      }

      if (arg2.getNext() != null) {
        // If we got more args than we expected, bail out.
        return n;
      }
    } else {
      // parameter 2 not passed
      length = stringAsString.length() - start;
    }

    // Don't handle these cases. The specification actually does
    // specify the behavior in some of these cases, but we haven't
    // done a thorough investigation that it is correctly implemented
    // in all browsers.
    if ((start + length) > stringAsString.length() ||
        (length < 0) ||
        (start < 0)) {
      return n;
    }

    String result = stringAsString.substring(start, start + length);
    Node resultNode = Node.newString(result);

    Node parent = n.getParent();
    parent.replaceChild(n, resultNode);
    reportCodeChange();
    return resultNode;
  }

  /**
   * Try to fold .substring() calls on strings
   */
  private Node tryFoldStringSubstring(Node n, Node stringNode, Node arg1) {
    Preconditions.checkArgument(n.getType() == Token.CALL);
    Preconditions.checkArgument(stringNode.getType() == Token.STRING);

    int start, end;
    String stringAsString = stringNode.getString();

    if (arg1 != null && arg1.getType() == Token.NUMBER) {
      start = (int) arg1.getDouble();
    } else {
      return n;
    }

    Node arg2 = arg1.getNext();
    if (arg2 != null) {
      if (arg2.getType() == Token.NUMBER) {
        end = (int) arg2.getDouble();
      } else {
        return n;
      }

      if (arg2.getNext() != null) {
        // If we got more args than we expected, bail out.
        return n;
      }
    } else {
      // parameter 2 not passed
      end = stringAsString.length();
    }

    // Don't handle these cases. The specification actually does
    // specify the behavior in some of these cases, but we haven't
    // done a thorough investigation that it is correctly implemented
    // in all browsers.
    if ((end > stringAsString.length()) ||
        (start > stringAsString.length()) ||
        (end < 0) ||
        (start < 0)) {
      return n;
    }

    String result = stringAsString.substring(start, end);
    Node resultNode = Node.newString(result);

    Node parent = n.getParent();
    parent.replaceChild(n, resultNode);
    reportCodeChange();
    return resultNode;
  }

}
TOP

Related Classes of com.google.javascript.jscomp.PeepholeReplaceKnownMethods

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.