blob: b1fca2893970920daff64d69a0a1244541b430fb [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.blockEvaluator;
import com.google.common.collect.Sets;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.QualifiedName;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyEvaluator;
import com.jetbrains.python.psi.impl.PyPathEvaluator;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author yole
*/
public class PyBlockEvaluator {
@NotNull
private final PyEvaluationResult myEvaluationResult = new PyEvaluationResult();
@NotNull
private final PyEvaluationContext myContext;
private final Set<PyFile> myVisitedFiles;
private final Set<String> myDeclarationsToTrack = new HashSet<String>();
private String myCurrentFilePath;
private Object myReturnValue;
private boolean myEvaluateCollectionItems = true;
/**
* @param evaluationContext context, obtained via {@link #getContext()}. Pass it here to enable cache. See {@link com.jetbrains.python.psi.impl.blockEvaluator.PyEvaluationContext}
* for more info
* @see com.jetbrains.python.psi.impl.blockEvaluator.PyEvaluationContext
*/
public PyBlockEvaluator(@NotNull final PyEvaluationContext evaluationContext) {
this(Sets.<PyFile>newHashSet(), evaluationContext);
}
/**
* Create evaluator with out of cache context
*/
public PyBlockEvaluator() {
this(new PyEvaluationContext());
}
private PyBlockEvaluator(@NotNull final Set<PyFile> visitedFiles, @NotNull final PyEvaluationContext evaluationContext) {
myVisitedFiles = visitedFiles;
myContext = evaluationContext;
}
public void trackDeclarations(String attrName) {
myDeclarationsToTrack.add(attrName);
}
public void evaluate(PyElement element) {
VirtualFile vFile = element.getContainingFile().getVirtualFile();
myCurrentFilePath = vFile != null ? vFile.getPath() : null;
if (myVisitedFiles.contains(element)) {
return;
}
myVisitedFiles.add((PyFile)element.getContainingFile());
PyElement statementContainer = element instanceof PyFunction ? ((PyFunction)element).getStatementList() : element;
if (statementContainer == null) {
return;
}
statementContainer.acceptChildren(new MyPyElementVisitor());
}
private void processExtendCall(PyCallExpression node, String nameBeingExtended) {
PyExpression arg = node.getArguments()[0];
Object value = myEvaluationResult.myNamespace.get(nameBeingExtended);
if (value instanceof List) {
Object argValue = prepareEvaluator().evaluate(arg);
myEvaluationResult.myNamespace.put(nameBeingExtended, prepareEvaluator().concatenate(value, argValue));
}
if (myDeclarationsToTrack.contains(nameBeingExtended)) {
List<PyExpression> declarations = myEvaluationResult.myDeclarations.get(nameBeingExtended);
if (declarations != null) {
PyPsiUtils.sequenceToList(declarations, arg);
}
}
}
private void processUpdateCall(PyCallExpression node, String name) {
Object value = myEvaluationResult.myNamespace.get(name);
if (value instanceof Map) {
Object argValue = prepareEvaluator().evaluate(node.getArguments()[0]);
if (argValue instanceof Map) {
((Map)value).putAll((Map)argValue);
}
}
}
private PyEvaluator prepareEvaluator() {
PyEvaluator evaluator = createEvaluator();
evaluator.setNamespace(myEvaluationResult.myNamespace);
evaluator.setEvaluateCollectionItems(myEvaluateCollectionItems);
return evaluator;
}
protected PyEvaluator createEvaluator() {
return new PyPathEvaluator(myCurrentFilePath);
}
public Object getValue(String name) {
return myEvaluationResult.myNamespace.get(name);
}
@Nullable
public String getValueAsString(String name) {
Object value = myEvaluationResult.myNamespace.get(name);
return value instanceof String ? (String)value : null;
}
@Nullable
public List getValueAsList(String name) {
Object value = myEvaluationResult.myNamespace.get(name);
return value instanceof List ? (List)value : null;
}
@NotNull
public List<String> getValueAsStringList(String name) {
Object value = myEvaluationResult.myNamespace.get(name);
if (value instanceof List) {
List valueList = (List)value;
for (Object o : valueList) {
if (o != null && !(o instanceof String)) {
return Collections.emptyList();
}
}
return (List<String>)value;
}
if (value instanceof String) {
return Collections.singletonList((String)value);
}
return Collections.emptyList();
}
public Set<PyFile> getVisitedFiles() {
return myVisitedFiles;
}
public Object getReturnValue() {
return myReturnValue;
}
public void setEvaluateCollectionItems(boolean evaluateCollectionItems) {
myEvaluateCollectionItems = evaluateCollectionItems;
}
@NotNull
public List<PyExpression> getDeclarations(@NotNull final String name) {
return myEvaluationResult.getDeclarations(name);
}
/**
* @return so-called context. You may pass it to any instance of {@link com.jetbrains.python.psi.impl.blockEvaluator.PyBlockEvaluator}
* to make instances share their cache
*/
@NotNull
public PyEvaluationContext getContext() {
return myContext;
}
private class MyPyElementVisitor extends PyElementVisitor {
@Override
public void visitPyAssignmentStatement(PyAssignmentStatement node) {
PyExpression expression = node.getLeftHandSideExpression();
if (expression instanceof PyTargetExpression) {
String name = expression.getName();
PyExpression value = ((PyTargetExpression)expression).findAssignedValue();
myEvaluationResult.myNamespace.put(name, prepareEvaluator().evaluate(value));
if (myDeclarationsToTrack.contains(name)) {
List<PyExpression> declarations = new ArrayList<PyExpression>();
PyPsiUtils.sequenceToList(declarations, value);
myEvaluationResult.myDeclarations.put(name, declarations);
}
}
else if (expression instanceof PySubscriptionExpression) {
PyExpression operand = ((PySubscriptionExpression)expression).getOperand();
PyExpression indexExpression = ((PySubscriptionExpression)expression).getIndexExpression();
if (operand instanceof PyReferenceExpression && ((PyReferenceExpression)operand).getQualifier() == null) {
Object currentValue = myEvaluationResult.myNamespace.get(((PyReferenceExpression)operand).getReferencedName());
if (currentValue instanceof Map) {
Object mapKey = prepareEvaluator().evaluate(indexExpression);
if (mapKey != null) {
Object value = myEvaluateCollectionItems ? prepareEvaluator().evaluate(node.getAssignedValue()) : node.getAssignedValue();
((Map)currentValue).put(mapKey, value);
}
}
}
}
}
@Override
public void visitPyAugAssignmentStatement(PyAugAssignmentStatement node) {
PyExpression target = node.getTarget();
String name = target.getName();
if (target instanceof PyReferenceExpression && !((PyReferenceExpression)target).isQualified() && name != null) {
Object currentValue = myEvaluationResult.myNamespace.get(name);
if (currentValue != null) {
Object rhs = prepareEvaluator().evaluate(node.getValue());
myEvaluationResult.myNamespace.put(name, prepareEvaluator().concatenate(currentValue, rhs));
}
if (myDeclarationsToTrack.contains(name)) {
List<PyExpression> declarations = myEvaluationResult.myDeclarations.get(name);
if (declarations != null) {
PyPsiUtils.sequenceToList(declarations, node.getValue());
}
}
}
}
@Override
public void visitPyExpressionStatement(PyExpressionStatement node) {
node.getExpression().accept(this);
}
@Override
public void visitPyCallExpression(PyCallExpression node) {
PyExpression callee = node.getCallee();
if (callee instanceof PyReferenceExpression) {
PyReferenceExpression calleeRef = (PyReferenceExpression)callee;
PyExpression qualifier = calleeRef.getQualifier();
if (qualifier instanceof PyReferenceExpression) {
PyReferenceExpression qualifierRef = (PyReferenceExpression)qualifier;
if (!qualifierRef.isQualified()) {
if (PyNames.EXTEND.equals(calleeRef.getReferencedName()) && node.getArguments().length == 1) {
processExtendCall(node, qualifierRef.getReferencedName());
}
else if (PyNames.UPDATE.equals(calleeRef.getReferencedName()) && node.getArguments().length == 1) {
processUpdateCall(node, qualifierRef.getReferencedName());
}
}
}
}
}
@Override
public void visitPyFromImportStatement(final PyFromImportStatement node) {
if (node.isFromFuture()) return;
final PsiElement source = PyUtil.turnDirIntoInit(node.resolveImportSource());
if (source instanceof PyFile) {
final PyFile pyFile = (PyFile)source;
PyEvaluationResult newlyEvaluatedResult = myContext.getCachedResult(pyFile);
if (newlyEvaluatedResult == null) {
final PyBlockEvaluator importEvaluator = new PyBlockEvaluator(myVisitedFiles, myContext);
importEvaluator.myDeclarationsToTrack.addAll(myDeclarationsToTrack);
importEvaluator.evaluate(pyFile);
newlyEvaluatedResult = importEvaluator.myEvaluationResult;
myContext.cache(pyFile, newlyEvaluatedResult);
}
if (node.isStarImport()) {
// TODO honor __all__ here
myEvaluationResult.myNamespace.putAll(newlyEvaluatedResult.myNamespace);
myEvaluationResult.myDeclarations.putAll(newlyEvaluatedResult.myDeclarations);
}
else {
for (final PyImportElement element : node.getImportElements()) {
final String nameOfVarInOurModule = element.getVisibleName();
final QualifiedName nameOfVarInExternalModule = element.getImportedQName();
if ((nameOfVarInOurModule == null) || (nameOfVarInExternalModule == null)) {
continue;
}
final Object value = newlyEvaluatedResult.myNamespace.get(nameOfVarInExternalModule.toString());
myEvaluationResult.myNamespace.put(nameOfVarInOurModule, value);
final List<PyExpression> declarations = newlyEvaluatedResult.getDeclarations(nameOfVarInOurModule);
if (myEvaluationResult.myDeclarations.containsKey(nameOfVarInOurModule)) {
myEvaluationResult.myDeclarations.get(nameOfVarInOurModule).addAll(declarations);
}
else {
myEvaluationResult.myDeclarations.put(nameOfVarInOurModule, declarations);
}
}
}
}
}
@Override
public void visitPyIfStatement(PyIfStatement node) {
PyStatementList list = node.getIfPart().getStatementList();
if (list != null) {
list.acceptChildren(this);
}
}
@Override
public void visitPyReturnStatement(PyReturnStatement node) {
myReturnValue = prepareEvaluator().evaluate(node.getExpression());
}
}
}