blob: dea9296f12c6ea231bb0982c04ed45477e5d8380 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* 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.jetbrains.python.psi.impl;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.PythonFQDNNames;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* TODO: Merge PythonDataflowUtil, {@link com.jetbrains.python.psi.impl.PyConstantExpressionEvaluator} and {@link com.jetbrains.python.psi.impl.PyEvaluator} and all its inheritors and improve Abstract Interpretation
*
* @author yole
*/
public class PyEvaluator {
private Set<PyExpression> myVisited = new HashSet<PyExpression>();
private Map<String, Object> myNamespace;
private boolean myEvaluateCollectionItems = true;
private boolean myEvaluateKeys = true;
public void setNamespace(Map<String, Object> namespace) {
myNamespace = namespace;
}
public void setEvaluateCollectionItems(boolean evaluateCollectionItems) {
myEvaluateCollectionItems = evaluateCollectionItems;
}
/**
* @param evaluateKeys evaluate keys for dicts or not (i.e. you wanna see string or StringLiteralExpressions as keys)
*/
public void setEvaluateKeys(final boolean evaluateKeys) {
myEvaluateKeys = evaluateKeys;
}
@Nullable
public Object evaluate(@Nullable PyExpression expr) {
if (expr == null || myVisited.contains(expr)) {
return null;
}
myVisited.add(expr);
if (expr instanceof PyParenthesizedExpression) {
return evaluate(((PyParenthesizedExpression)expr).getContainedExpression());
}
if (expr instanceof PySequenceExpression) {
return evaluateSequenceExpression((PySequenceExpression)expr);
}
if (expr instanceof PyCallExpression) {
return evaluateCall((PyCallExpression)expr);
}
else if (expr instanceof PyReferenceExpression) {
return evaluateReferenceExpression((PyReferenceExpression)expr);
}
else if (expr instanceof PyStringLiteralExpression) {
return ((PyStringLiteralExpression)expr).getStringValue();
}
else if (expr instanceof PyBinaryExpression) {
PyBinaryExpression binaryExpr = (PyBinaryExpression)expr;
PyElementType op = binaryExpr.getOperator();
if (op == PyTokenTypes.PLUS) {
Object lhs = evaluate(binaryExpr.getLeftExpression());
Object rhs = evaluate(binaryExpr.getRightExpression());
if (lhs != null && rhs != null) {
return concatenate(lhs, rhs);
}
}
}
return null;
}
/**
* Evaluates some sequence (tuple, list)
*
* @param expr seq expression
* @return evaluated seq
*/
protected Object evaluateSequenceExpression(PySequenceExpression expr) {
PyExpression[] elements = expr.getElements();
if (expr instanceof PyDictLiteralExpression) {
Map<Object, Object> result = new HashMap<Object, Object>();
for (final PyKeyValueExpression keyValueExpression : ((PyDictLiteralExpression)expr).getElements()) {
addRecordFromDict(result, keyValueExpression.getKey(), keyValueExpression.getValue());
}
return result;
}
else {
List<Object> result = new ArrayList<Object>();
for (PyExpression element : elements) {
result.add(myEvaluateCollectionItems ? evaluate(element) : element);
}
return result;
}
}
public Object concatenate(Object lhs, Object rhs) {
if (lhs instanceof String && rhs instanceof String) {
return (String)lhs + (String)rhs;
}
if (lhs instanceof List && rhs instanceof List) {
List<Object> result = new ArrayList<Object>();
result.addAll((List)lhs);
result.addAll((List)rhs);
return result;
}
return null;
}
protected Object evaluateReferenceExpression(PyReferenceExpression expr) {
if (!expr.isQualified()) {
if (myNamespace != null) {
return myNamespace.get(expr.getReferencedName());
}
PsiElement result = expr.getReference(PyResolveContext.noImplicits()).resolve();
if (result instanceof PyTargetExpression) {
result = ((PyTargetExpression)result).findAssignedValue();
}
if (result instanceof PyExpression) {
return evaluate((PyExpression)result);
}
}
return null;
}
@Nullable
protected Object evaluateCall(PyCallExpression call) {
final PyExpression[] args = call.getArguments();
if (call.isCalleeText(PyNames.REPLACE) && args.length == 2) {
final PyExpression callee = call.getCallee();
if (!(callee instanceof PyQualifiedExpression)) return null;
final PyExpression qualifier = ((PyQualifiedExpression)callee).getQualifier();
Object result = evaluate(qualifier);
if (result instanceof String) {
Object arg1 = evaluate(args[0]);
Object arg2 = evaluate(args[1]);
if (arg1 instanceof String && arg2 instanceof String) {
return ((String)result).replace((String)arg1, (String)arg2);
}
}
}
// Support dict([("k": "v")]) syntax
if (call.isCallee(PythonFQDNNames.DICT_CLASS)) {
final Collection<PyTupleExpression> tuples = PsiTreeUtil.findChildrenOfType(call, PyTupleExpression.class);
if (!tuples.isEmpty()) {
final Map<Object, Object> result = new HashMap<Object, Object>();
for (final PyTupleExpression tuple : tuples) {
final PsiElement[] tupleElements = tuple.getChildren();
if (tupleElements.length != 2) {
return null;
}
final PyExpression key = PyUtil.as(tupleElements[0], PyExpression.class);
final PyExpression value = PyUtil.as(tupleElements[1], PyExpression.class);
if ((key != null)) {
addRecordFromDict(result, key, value);
}
}
return result;
}
}
return null;
}
/**
* Adds record for map when working with dict
*
* @param result map to return to user
* @param key dict key
* @param value dict value
*/
private void addRecordFromDict(@NotNull final Map<Object, Object> result,
@NotNull final PyExpression key,
@Nullable final PyExpression value) {
result.put(myEvaluateKeys ? evaluate(key) : key, myEvaluateCollectionItems ? evaluate(value) : value);
}
/**
* Shortcut that evaluates expression with default params and casts it to particular type (if possible)
*
* @param expression exp to evaluate
* @param resultType expected type
* @param <T> expected type
* @return value if expression is evaluated to this type, null otherwise
*/
@Nullable
public static <T> T evaluate(@Nullable final PyExpression expression, @NotNull final Class<T> resultType) {
return PyUtil.as(new PyEvaluator().evaluate(expression), resultType);
}
}