| /* |
| * 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); |
| } |
| } |