/*
* Copyright 2013-2017 consulo.io
*
* 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 consulo.csharp.ide.debugger;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import com.intellij.psi.PsiElement;
import com.intellij.psi.ResolveResult;
import com.intellij.psi.tree.IElementType;
import consulo.annotations.RequiredReadAction;
import consulo.csharp.ide.debugger.expressionEvaluator.*;
import consulo.csharp.lang.psi.*;
import consulo.csharp.lang.psi.impl.source.CSharpAssignmentExpressionImpl;
import consulo.csharp.lang.psi.impl.source.CSharpBinaryExpressionImpl;
import consulo.csharp.lang.psi.impl.source.CSharpConstantExpressionImpl;
import consulo.csharp.lang.psi.impl.source.CSharpIndexAccessExpressionImpl;
import consulo.csharp.lang.psi.impl.source.CSharpIsExpressionImpl;
import consulo.csharp.lang.psi.impl.source.CSharpMethodCallExpressionImpl;
import consulo.csharp.lang.psi.impl.source.CSharpOperatorReferenceImpl;
import consulo.csharp.lang.psi.impl.source.CSharpPrefixExpressionImpl;
import consulo.csharp.lang.psi.impl.source.resolve.MethodResolveResult;
import consulo.csharp.lang.psi.impl.source.resolve.methodResolving.arguments.NCallArgument;
import consulo.csharp.lang.psi.impl.source.resolve.util.CSharpResolveUtil;
import consulo.dotnet.psi.DotNetExpression;
import consulo.dotnet.psi.DotNetModifier;
import consulo.dotnet.psi.DotNetModifierListOwner;
import consulo.dotnet.psi.DotNetParameter;
import consulo.dotnet.psi.DotNetTypeDeclaration;
import consulo.dotnet.resolve.DotNetTypeRef;
/**
* @author VISTALL
* @since 06.06.14
*/
public class CSharpExpressionEvaluator extends CSharpElementVisitor
{
private List<Evaluator> myEvaluators = new ArrayList<Evaluator>();
@Override
@RequiredReadAction
public void visitIsExpression(CSharpIsExpressionImpl expression)
{
DotNetExpression leftExpression = expression.getExpression();
leftExpression.accept(this);
myEvaluators.add(new IsExpressionEvaluator(expression));
}
@Override
@RequiredReadAction
public void visitConstantExpression(CSharpConstantExpressionImpl expression)
{
PsiElement element = expression.toTypeRef(true).resolve().getElement();
if(!(element instanceof CSharpTypeDeclaration))
{
cantEvaluateExpression();
}
myEvaluators.add(new ConstantEvaluator(expression.getValue(), ((CSharpTypeDeclaration) element).getVmQName()));
}
@Override
@RequiredReadAction
public void visitReferenceExpression(CSharpReferenceExpression expression)
{
CSharpReferenceExpression.ResolveToKind kind = expression.kind();
PsiElement qualifier = expression.getQualifier();
if(qualifier == null)
{
PsiElement resolvedElement = expression.resolve();
if(resolvedElement instanceof DotNetParameter)
{
myEvaluators.add(new ParameterEvaluator((DotNetParameter) resolvedElement));
}
else if(resolvedElement instanceof CSharpLocalVariable)
{
myEvaluators.add(new LocalVariableEvaluator((CSharpLocalVariable) resolvedElement));
}
else if(resolvedElement instanceof CSharpFieldDeclaration || resolvedElement instanceof CSharpPropertyDeclaration)
{
CSharpTypeDeclaration typeDeclaration = null;
if(((DotNetModifierListOwner) resolvedElement).hasModifier(DotNetModifier.STATIC))
{
typeDeclaration = (CSharpTypeDeclaration) resolvedElement.getParent();
myEvaluators.add(StaticObjectEvaluator.INSTANCE);
}
else
{
myEvaluators.add(ThisObjectEvaluator.INSTANCE);
}
if(resolvedElement instanceof CSharpPropertyDeclaration)
{
myEvaluators.add(new PropertyEvaluator(typeDeclaration, (CSharpPropertyDeclaration) resolvedElement));
}
else if(resolvedElement instanceof CSharpFieldDeclaration)
{
myEvaluators.add(new FieldEvaluator(typeDeclaration, (CSharpFieldDeclaration) resolvedElement));
}
}
else if(resolvedElement instanceof CSharpTypeDeclaration)
{
switch(kind)
{
case THIS:
myEvaluators.add(ThisObjectEvaluator.INSTANCE);
break;
default:
myEvaluators.add(StaticObjectEvaluator.INSTANCE);
break;
}
}
else
{
cantEvaluateExpression();
}
}
else
{
qualifier.accept(this);
PsiElement resolvedElement = expression.resolve();
if(resolvedElement instanceof CSharpFieldDeclaration)
{
CSharpTypeDeclaration typeDeclaration = (CSharpTypeDeclaration) resolvedElement.getParent();
myEvaluators.add(new FieldEvaluator(typeDeclaration, (CSharpFieldDeclaration) resolvedElement));
}
else if(resolvedElement instanceof CSharpPropertyDeclaration)
{
CSharpTypeDeclaration typeDeclaration = (CSharpTypeDeclaration) resolvedElement.getParent();
myEvaluators.add(new PropertyEvaluator(typeDeclaration, (CSharpPropertyDeclaration) resolvedElement));
}
else
{
cantEvaluateExpression();
}
}
}
@Override
@RequiredReadAction
public void visitMethodCallExpression(CSharpMethodCallExpressionImpl expression)
{
DotNetExpression callExpression = expression.getCallExpression();
ResolveResult resolveResult = CSharpResolveUtil.findFirstValidResult(expression.multiResolve(false));
if(resolveResult == null || !(resolveResult instanceof MethodResolveResult) || !(resolveResult.getElement() instanceof CSharpMethodDeclaration))
{
cantEvaluateExpression();
}
CSharpMethodDeclaration methodDeclaration = (CSharpMethodDeclaration) resolveResult.getElement();
if(callExpression instanceof CSharpReferenceExpression)
{
CSharpTypeDeclaration typeDeclaration = null;
PsiElement qualifier = ((CSharpReferenceExpression) callExpression).getQualifier();
if(qualifier != null)
{
qualifier.accept(this);
}
else
{
if(methodDeclaration.hasModifier(DotNetModifier.STATIC))
{
typeDeclaration = (CSharpTypeDeclaration) methodDeclaration.getParent();
myEvaluators.add(StaticObjectEvaluator.INSTANCE);
}
else
{
myEvaluators.add(ThisObjectEvaluator.INSTANCE);
}
}
String referenceName = ((CSharpReferenceExpression) callExpression).getReferenceName();
if(referenceName == null)
{
cantEvaluateExpression();
}
pushNArguments((MethodResolveResult) resolveResult);
pushMethodEvaluator(expression, methodDeclaration, typeDeclaration, referenceName);
}
}
@Override
@RequiredReadAction
public void visitBinaryExpression(CSharpBinaryExpressionImpl expression)
{
CSharpOperatorReferenceImpl operatorElement = expression.getOperatorElement();
IElementType operatorElementType = expression.getOperatorElement().getOperatorElementType();
PsiElement element = operatorElement.resolveToCallable();
if(element != null)
{
myEvaluators.add(StaticObjectEvaluator.INSTANCE); // operators always static
pushArguments(expression);
CSharpMethodDeclaration methodDeclaration = (CSharpMethodDeclaration) element;
pushMethodEvaluator(expression, methodDeclaration, (CSharpTypeDeclaration) element.getParent(), getMethodName(operatorElementType, methodDeclaration.getName()));
if(operatorElementType == CSharpTokens.NTEQ)
{
myEvaluators.add(new PrefixOperatorEvaluator(CSharpTokens.EXCL));
}
return;
}
else
{
cantEvaluateExpression();
}
expressionIsNotSupported();
}
@Override
@RequiredReadAction
public void visitPrefixExpression(CSharpPrefixExpressionImpl expression)
{
CSharpOperatorReferenceImpl operatorElement = expression.getOperatorElement();
PsiElement element = operatorElement.resolveToCallable();
if(element != null)
{
if(!element.isPhysical())
{
CSharpCallArgument[] callArguments = expression.getCallArguments();
for(CSharpCallArgument callArgument : callArguments)
{
DotNetExpression argumentExpression = callArgument.getArgumentExpression();
if(argumentExpression == null)
{
cantEvaluateExpression();
}
argumentExpression.accept(this);
}
myEvaluators.add(new PrefixOperatorEvaluator(operatorElement.getOperatorElementType()));
return;
}
}
expressionIsNotSupported();
}
@Override
@RequiredReadAction
public void visitIndexAccessExpression(CSharpIndexAccessExpressionImpl expression)
{
PsiElement parent = expression.getParent();
if(parent instanceof CSharpAssignmentExpressionImpl && ((CSharpAssignmentExpressionImpl) parent).getCallArguments()[0] == expression)
{
expressionIsNotSupported();
}
ResolveResult resolveResult = CSharpResolveUtil.findFirstValidResult(expression.multiResolve(false));
if(resolveResult == null || !(resolveResult instanceof MethodResolveResult) || !(resolveResult.getElement() instanceof CSharpIndexMethodDeclaration))
{
cantEvaluateExpression();
}
CSharpIndexMethodDeclaration indexMethodDeclaration = (CSharpIndexMethodDeclaration) resolveResult.getElement();
expression.getQualifier().accept(this);
pushNArguments((MethodResolveResult) resolveResult);
DotNetTypeRef[] parameterTypeRefs = indexMethodDeclaration.getParameterTypeRefs();
List<DotNetTypeDeclaration> parameterTypes = new ArrayList<DotNetTypeDeclaration>();
for(DotNetTypeRef parameterTypeRef : parameterTypeRefs)
{
PsiElement element = parameterTypeRef.resolve().getElement();
if(!(element instanceof CSharpTypeDeclaration))
{
throw new UnsupportedOperationException("parameter type is not type");
}
parameterTypes.add((DotNetTypeDeclaration) element);
}
myEvaluators.add(new IndexMethodEvaluator(indexMethodDeclaration, parameterTypes));
}
@Override
public void visitElement(PsiElement element)
{
expressionIsNotSupported();
}
@Contract(value = "_ -> fail")
private static void cantEvaluateExpression()
{
throw new IllegalArgumentException("cant evaluate expression");
}
private static void expressionIsNotSupported()
{
throw new UnsupportedOperationException("expression is not supported");
}
@NotNull
private static String getMethodName(IElementType elementType, String originalName)
{
if(elementType == CSharpTokens.EQEQ || elementType == CSharpTokens.NTEQ)
{
return "Equals";
}
return originalName;
}
@RequiredReadAction
private void pushMethodEvaluator(PsiElement scope, CSharpMethodDeclaration methodDeclaration, CSharpTypeDeclaration typeDeclaration, @NotNull String referenceName)
{
DotNetTypeRef[] parameterTypeRefs = methodDeclaration.getParameterTypeRefs();
List<DotNetTypeDeclaration> parameterTypes = new ArrayList<DotNetTypeDeclaration>();
for(DotNetTypeRef parameterTypeRef : parameterTypeRefs)
{
PsiElement element = parameterTypeRef.resolve().getElement();
if(!(element instanceof CSharpTypeDeclaration))
{
throw new UnsupportedOperationException("parameter type is not type");
}
parameterTypes.add((DotNetTypeDeclaration) element);
}
myEvaluators.add(new MethodEvaluator(referenceName, typeDeclaration, parameterTypes));
}
@RequiredReadAction
private void pushNArguments(MethodResolveResult resolveResult)
{
List<NCallArgument> arguments = resolveResult.getCalcResult().getArguments();
for(NCallArgument argument : arguments)
{
CSharpCallArgument callArgument = argument.getCallArgument();
if(callArgument == null)
{
cantEvaluateExpression();
}
DotNetExpression argumentExpression = callArgument.getArgumentExpression();
if(argumentExpression == null)
{
cantEvaluateExpression();
}
argumentExpression.accept(this);
}
}
private void pushArguments(CSharpBinaryExpressionImpl expression)
{
CSharpCallArgument[] callArguments = expression.getCallArguments();
for(CSharpCallArgument callArgument : callArguments)
{
DotNetExpression argumentExpression = callArgument.getArgumentExpression();
if(argumentExpression == null)
{
throw new UnsupportedOperationException("bad operator call argument");
}
argumentExpression.accept(this);
}
}
@NotNull
public List<Evaluator> getEvaluators()
{
return myEvaluators;
}
}