/* * Copyright (c) 2012, the Dart project authors. * * Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html * * 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.dart.engine.ast.visitor; import com.google.dart.engine.ast.AdjacentStrings; import com.google.dart.engine.ast.AstNode; import com.google.dart.engine.ast.BinaryExpression; import com.google.dart.engine.ast.BooleanLiteral; import com.google.dart.engine.ast.DoubleLiteral; import com.google.dart.engine.ast.Expression; import com.google.dart.engine.ast.IntegerLiteral; import com.google.dart.engine.ast.InterpolationElement; import com.google.dart.engine.ast.InterpolationExpression; import com.google.dart.engine.ast.InterpolationString; import com.google.dart.engine.ast.ListLiteral; import com.google.dart.engine.ast.MapLiteral; import com.google.dart.engine.ast.MapLiteralEntry; import com.google.dart.engine.ast.MethodInvocation; import com.google.dart.engine.ast.NullLiteral; import com.google.dart.engine.ast.ParenthesizedExpression; import com.google.dart.engine.ast.PrefixExpression; import com.google.dart.engine.ast.PrefixedIdentifier; import com.google.dart.engine.ast.PropertyAccess; import com.google.dart.engine.ast.SimpleIdentifier; import com.google.dart.engine.ast.SimpleStringLiteral; import com.google.dart.engine.ast.StringInterpolation; import com.google.dart.engine.ast.StringLiteral; import com.google.dart.engine.ast.SymbolLiteral; import com.google.dart.engine.element.Element; import com.google.dart.engine.element.FieldElement; import com.google.dart.engine.scanner.Token; import java.math.BigInteger; import java.util.ArrayList; import java.util.HashMap; /** * Instances of the class {@code ConstantEvaluator} evaluate constant expressions to produce their * compile-time value. According to the Dart Language Specification: <blockquote> A constant * expression is one of the following: * <ul> * <li>A literal number.</li> * <li>A literal boolean.</li> * <li>A literal string where any interpolated expression is a compile-time constant that evaluates * to a numeric, string or boolean value or to {@code null}.</li> * <li>{@code null}.</li> * <li>A reference to a static constant variable.</li> * <li>An identifier expression that denotes a constant variable, a class or a type parameter.</li> * <li>A constant constructor invocation.</li> * <li>A constant list literal.</li> * <li>A constant map literal.</li> * <li>A simple or qualified identifier denoting a top-level function or a static method.</li> * <li>A parenthesized expression {@code (e)} where {@code e} is a constant expression.</li> * <li>An expression of one of the forms {@code identical(e1, e2)}, {@code e1 == e2}, * {@code e1 != e2} where {@code e1} and {@code e2} are constant expressions that evaluate to a * numeric, string or boolean value or to {@code null}.</li> * <li>An expression of one of the forms {@code !e}, {@code e1 && e2} or {@code e1 || e2}, where * {@code e}, {@code e1} and {@code e2} are constant expressions that evaluate to a boolean value or * to {@code null}.</li> * <li>An expression of one of the forms {@code ~e}, {@code e1 ^ e2}, {@code e1 & e2}, * {@code e1 | e2}, {@code e1 >> e2} or {@code e1 << e2}, where {@code e}, {@code e1} and {@code e2} * are constant expressions that evaluate to an integer value or to {@code null}.</li> * <li>An expression of one of the forms {@code -e}, {@code e1 + e2}, {@code e1 - e2}, * {@code e1 * e2}, {@code e1 / e2}, {@code e1 ~/ e2}, {@code e1 > e2}, {@code e1 < e2}, * {@code e1 >= e2}, {@code e1 <= e2} or {@code e1 % e2}, where {@code e}, {@code e1} and {@code e2} * are constant expressions that evaluate to a numeric value or to {@code null}.</li> * </ul> * </blockquote> The values returned by instances of this class are therefore {@code null} and * instances of the classes {@code Boolean}, {@code BigInteger}, {@code Double}, {@code String}, and * {@code DartObject}. * <p> * In addition, this class defines several values that can be returned to indicate various * conditions encountered during evaluation. These are documented with the static field that define * those values. * * @coverage dart.engine.ast */ public class ConstantEvaluator extends GeneralizingAstVisitor<Object> { /** * The value returned for expressions (or non-expression nodes) that are not compile-time constant * expressions. */ public static final Object NOT_A_CONSTANT = new Object(); /** * Initialize a newly created constant evaluator. */ public ConstantEvaluator() { } @Override public Object visitAdjacentStrings(AdjacentStrings node) { StringBuilder builder = new StringBuilder(); for (StringLiteral string : node.getStrings()) { Object value = string.accept(this); if (value == NOT_A_CONSTANT) { return value; } builder.append(value); } return builder.toString(); } @Override public Object visitBinaryExpression(BinaryExpression node) { Object leftOperand = node.getLeftOperand().accept(this); if (leftOperand == NOT_A_CONSTANT) { return leftOperand; } Object rightOperand = node.getRightOperand().accept(this); if (rightOperand == NOT_A_CONSTANT) { return rightOperand; } switch (node.getOperator().getType()) { case AMPERSAND: // integer or {@code null} if (leftOperand instanceof BigInteger && rightOperand instanceof BigInteger) { return ((BigInteger) leftOperand).and((BigInteger) rightOperand); } break; case AMPERSAND_AMPERSAND: // boolean or {@code null} if (leftOperand instanceof Boolean && rightOperand instanceof Boolean) { return ((Boolean) leftOperand).booleanValue() && ((Boolean) rightOperand).booleanValue(); } break; case BANG_EQ: // numeric, string, boolean, or {@code null} if (leftOperand instanceof Boolean && rightOperand instanceof Boolean) { return ((Boolean) leftOperand).booleanValue() != ((Boolean) rightOperand).booleanValue(); } else if (leftOperand instanceof BigInteger && rightOperand instanceof BigInteger) { return !((BigInteger) leftOperand).equals(rightOperand); } else if (leftOperand instanceof Double && rightOperand instanceof Double) { return !((Double) leftOperand).equals(rightOperand); } else if (leftOperand instanceof String && rightOperand instanceof String) { return !((String) leftOperand).equals(rightOperand); } break; case BAR: // integer or {@code null} if (leftOperand instanceof BigInteger && rightOperand instanceof BigInteger) { return ((BigInteger) leftOperand).or((BigInteger) rightOperand); } break; case BAR_BAR: // boolean or {@code null} if (leftOperand instanceof Boolean && rightOperand instanceof Boolean) { return ((Boolean) leftOperand).booleanValue() || ((Boolean) rightOperand).booleanValue(); } break; case CARET: // integer or {@code null} if (leftOperand instanceof BigInteger && rightOperand instanceof BigInteger) { return ((BigInteger) leftOperand).xor((BigInteger) rightOperand); } break; case EQ_EQ: // numeric, string, boolean, or {@code null} if (leftOperand instanceof Boolean && rightOperand instanceof Boolean) { return ((Boolean) leftOperand).booleanValue() == ((Boolean) rightOperand).booleanValue(); } else if (leftOperand instanceof BigInteger && rightOperand instanceof BigInteger) { return ((BigInteger) leftOperand).equals(rightOperand); } else if (leftOperand instanceof Double && rightOperand instanceof Double) { return ((Double) leftOperand).equals(rightOperand); } else if (leftOperand instanceof String && rightOperand instanceof String) { return ((String) leftOperand).equals(rightOperand); } break; case GT: // numeric or {@code null} if (leftOperand instanceof BigInteger && rightOperand instanceof BigInteger) { return ((BigInteger) leftOperand).compareTo((BigInteger) rightOperand) > 0; } else if (leftOperand instanceof Double && rightOperand instanceof Double) { return ((Double) leftOperand).compareTo((Double) rightOperand) > 0; } break; case GT_EQ: // numeric or {@code null} if (leftOperand instanceof BigInteger && rightOperand instanceof BigInteger) { return ((BigInteger) leftOperand).compareTo((BigInteger) rightOperand) >= 0; } else if (leftOperand instanceof Double && rightOperand instanceof Double) { return ((Double) leftOperand).compareTo((Double) rightOperand) >= 0; } break; case GT_GT: // integer or {@code null} if (leftOperand instanceof BigInteger && rightOperand instanceof BigInteger) { return ((BigInteger) leftOperand).shiftRight(((BigInteger) rightOperand).intValue()); } break; case LT: // numeric or {@code null} if (leftOperand instanceof BigInteger && rightOperand instanceof BigInteger) { return ((BigInteger) leftOperand).compareTo((BigInteger) rightOperand) < 0; } else if (leftOperand instanceof Double && rightOperand instanceof Double) { return ((Double) leftOperand).compareTo((Double) rightOperand) < 0; } break; case LT_EQ: // numeric or {@code null} if (leftOperand instanceof BigInteger && rightOperand instanceof BigInteger) { return ((BigInteger) leftOperand).compareTo((BigInteger) rightOperand) <= 0; } else if (leftOperand instanceof Double && rightOperand instanceof Double) { return ((Double) leftOperand).compareTo((Double) rightOperand) <= 0; } break; case LT_LT: // integer or {@code null} if (leftOperand instanceof BigInteger && rightOperand instanceof BigInteger) { return ((BigInteger) leftOperand).shiftLeft(((BigInteger) rightOperand).intValue()); } break; case MINUS: // numeric or {@code null} if (leftOperand instanceof BigInteger && rightOperand instanceof BigInteger) { return ((BigInteger) leftOperand).subtract((BigInteger) rightOperand); } else if (leftOperand instanceof Double && rightOperand instanceof Double) { return ((Double) leftOperand).doubleValue() - ((Double) rightOperand).doubleValue(); } break; case PERCENT: // numeric or {@code null} if (leftOperand instanceof BigInteger && rightOperand instanceof BigInteger) { return ((BigInteger) leftOperand).remainder((BigInteger) rightOperand); } else if (leftOperand instanceof Double && rightOperand instanceof Double) { return ((Double) leftOperand).doubleValue() % ((Double) rightOperand).doubleValue(); } break; case PLUS: // numeric or {@code null} if (leftOperand instanceof BigInteger && rightOperand instanceof BigInteger) { return ((BigInteger) leftOperand).add((BigInteger) rightOperand); } else if (leftOperand instanceof Double && rightOperand instanceof Double) { return ((Double) leftOperand).doubleValue() + ((Double) rightOperand).doubleValue(); } break; case STAR: // numeric or {@code null} if (leftOperand instanceof BigInteger && rightOperand instanceof BigInteger) { return ((BigInteger) leftOperand).multiply((BigInteger) rightOperand); } else if (leftOperand instanceof Double && rightOperand instanceof Double) { return ((Double) leftOperand).doubleValue() * ((Double) rightOperand).doubleValue(); } break; case SLASH: // numeric or {@code null} if (leftOperand instanceof BigInteger && rightOperand instanceof BigInteger) { if (!rightOperand.equals(BigInteger.ZERO)) { return ((BigInteger) leftOperand).divide((BigInteger) rightOperand); } else { return Double.valueOf(((BigInteger) leftOperand).doubleValue() / ((BigInteger) rightOperand).doubleValue()); } } else if (leftOperand instanceof Double && rightOperand instanceof Double) { return ((Double) leftOperand).doubleValue() / ((Double) rightOperand).doubleValue(); } break; case TILDE_SLASH: // numeric or {@code null} if (leftOperand instanceof BigInteger && rightOperand instanceof BigInteger) { if (!rightOperand.equals(BigInteger.ZERO)) { return ((BigInteger) leftOperand).divide((BigInteger) rightOperand); } else { return BigInteger.ZERO; } } else if (leftOperand instanceof Double && rightOperand instanceof Double) { return BigInteger.valueOf(Double.valueOf( Math.floor(((Double) leftOperand).doubleValue() / ((Double) rightOperand).doubleValue())).longValue()); } break; default: // Fall through to return the default value. break; } // TODO(brianwilkerson) This doesn't handle numeric conversions. return visitExpression(node); } @Override public Object visitBooleanLiteral(BooleanLiteral node) { return node.getValue() ? Boolean.TRUE : Boolean.FALSE; } @Override public Object visitDoubleLiteral(DoubleLiteral node) { return Double.valueOf(node.getValue()); } @Override public Object visitIntegerLiteral(IntegerLiteral node) { return node.getValue(); } @Override public Object visitInterpolationExpression(InterpolationExpression node) { Object value = node.getExpression().accept(this); if (value == null || value instanceof Boolean || value instanceof String || value instanceof BigInteger || value instanceof Double) { return value; } return NOT_A_CONSTANT; } @Override public Object visitInterpolationString(InterpolationString node) { return node.getValue(); } @Override public Object visitListLiteral(ListLiteral node) { ArrayList<Object> list = new ArrayList<Object>(); for (Expression element : node.getElements()) { Object value = element.accept(this); if (value == NOT_A_CONSTANT) { return value; } list.add(value); } return list; } @Override public Object visitMapLiteral(MapLiteral node) { HashMap<String, Object> map = new HashMap<String, Object>(); for (MapLiteralEntry entry : node.getEntries()) { Object key = entry.getKey().accept(this); Object value = entry.getValue().accept(this); if (!(key instanceof String) || value == NOT_A_CONSTANT) { return NOT_A_CONSTANT; } map.put((String) key, value); } return map; } @Override public Object visitMethodInvocation(MethodInvocation node) { // TODO(brianwilkerson) Need to look for invocation of "identical". return visitNode(node); } @Override public Object visitNode(AstNode node) { return NOT_A_CONSTANT; } @Override public Object visitNullLiteral(NullLiteral node) { return null; } @Override public Object visitParenthesizedExpression(ParenthesizedExpression node) { return node.getExpression().accept(this); } @Override public Object visitPrefixedIdentifier(PrefixedIdentifier node) { // TODO(brianwilkerson) Resolve the identifier. return getConstantValue(null); } @Override public Object visitPrefixExpression(PrefixExpression node) { Object operand = node.getOperand().accept(this); if (operand == NOT_A_CONSTANT) { return operand; } switch (node.getOperator().getType()) { case BANG: if (operand == Boolean.TRUE) { return Boolean.FALSE; } else if (operand == Boolean.FALSE) { return Boolean.TRUE; } // TODO(brianwilkerson) We might need to support !null, but I don't know yet what value to return. break; case TILDE: if (operand instanceof BigInteger) { return ((BigInteger) operand).not(); } break; case MINUS: if (operand == null) { return null; } else if (operand instanceof BigInteger) { return ((BigInteger) operand).negate(); } else if (operand instanceof Double) { return Double.valueOf(-((Double) operand).doubleValue()); } break; default: // Fall through to return the default value. break; } return NOT_A_CONSTANT; } @Override public Object visitPropertyAccess(PropertyAccess node) { // TODO(brianwilkerson) Resolve the property. return getConstantValue(null); } @Override public Object visitSimpleIdentifier(SimpleIdentifier node) { // TODO(brianwilkerson) Resolve the identifier. return getConstantValue(null); } @Override public Object visitSimpleStringLiteral(SimpleStringLiteral node) { return node.getValue(); } @Override public Object visitStringInterpolation(StringInterpolation node) { StringBuilder builder = new StringBuilder(); for (InterpolationElement element : node.getElements()) { Object value = element.accept(this); if (value == NOT_A_CONSTANT) { return value; } builder.append(value); } return builder.toString(); } @Override public Object visitSymbolLiteral(SymbolLiteral node) { // TODO(brianwilkerson) This isn't optimal because a Symbol is not a String. StringBuilder builder = new StringBuilder(); for (Token component : node.getComponents()) { if (builder.length() > 0) { builder.append('.'); } builder.append(component.getLexeme()); } return builder.toString(); } /** * Return the constant value of the static constant represented by the given element. * * @param element the element whose value is to be returned * @return the constant value of the static constant */ private Object getConstantValue(Element element) { // TODO(brianwilkerson) Implement this if (element instanceof FieldElement) { FieldElement field = (FieldElement) element; if (field.isStatic() && field.isConst()) { //field.getConstantValue(); } // } else if (element instanceof VariableElement) { // VariableElement variable = (VariableElement) element; // if (variable.isStatic() && variable.isConst()) { // //variable.getConstantValue(); // } } return NOT_A_CONSTANT; } }