blob: 0d78a5f70ebc9b36b4078d974fa7b8545501d2a3 [file] [log] [blame]
/*
* 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;
}
}