/*
* Copyright 2010 Jean-Paul Balabanian and Yngve Devik Hammersland
*
* This file is part of glsl4idea.
*
* Glsl4idea is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* Glsl4idea is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with glsl4idea. If not, see <http://www.gnu.org/licenses/>.
*/
package glslplugin.lang.elements.expressions;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiCheckedRenameElement;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNameIdentifierOwner;
import com.intellij.util.IncorrectOperationException;
import glslplugin.lang.elements.GLSLElement;
import glslplugin.lang.elements.GLSLIdentifier;
import glslplugin.lang.elements.GLSLReferenceElement;
import glslplugin.lang.elements.declarations.*;
import glslplugin.lang.elements.reference.GLSLConstructorReference;
import glslplugin.lang.elements.reference.GLSLFunctionReference;
import glslplugin.lang.elements.reference.GLSLReferenceBase;
import glslplugin.lang.elements.reference.GLSLTypeReference;
import glslplugin.lang.elements.types.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* GLSLFunctionCallExpression is a function call expression or a constructor expression.
* What it really is determines what methods (of this class) are sensible to call.
*
* There are three possible valid children states:
* TYPE_SPECIFIER = Constructor mode of built-in types or their arrays
* FUNCTION_NAME ARRAY_DECLARATOR* = Constructor mode of struct arrays
* FUNCTION_NAME = Constructor mode of structs or an actual function call, determined by references in scope
* All modes always followed by LEFT_PAREN parameter list RIGHT_PAREN.
* (That is, if the tree is syntactically valid.)
*
* Examples:
* int A = int(4);
* int[] B = int[3](1,2,3);
* MyVec C = MyVec(3.5,6.7);
* MyVec[] D = MyVec[2]( MyVec(1,2), MyVec(3,4) );
* bool E = randomBoolean();
*
* @author Yngve Devik Hammersland
* Date: Jan 29, 2009
* Time: 10:34:04 AM
*/
public class GLSLFunctionCallExpression extends GLSLExpression implements GLSLReferenceElement {
public GLSLFunctionCallExpression(@NotNull ASTNode astNode) {
super(astNode);
}
//region Tree mining
//Constructor only
@Nullable
private GLSLTypeSpecifier getConstructorTypeSpecifier(){
return findChildByClass(GLSLTypeSpecifier.class);
}
@Nullable
private GLSLArraySpecifier[] getConstructorArraySpecifiers(){
return findChildrenByClass(GLSLArraySpecifier.class);
}
//Shared
@Nullable
public GLSLIdentifier getIdentifier() {
return findChildByClass(GLSLIdentifier.class);
}
@Nullable
public GLSLParameterList getParameterList() {
return findChildByClass(GLSLParameterList.class);
}
@NotNull
public GLSLType[] getParameterTypes(){
GLSLParameterList parameterList = getParameterList();
if(parameterList != null)return parameterList.getParameterTypes();
else return GLSLType.EMPTY_ARRAY;
}
//endregion
//region Shared
private boolean isStructReference(GLSLIdentifier identifier){
return findDefinedStruct(identifier) != null;
}
public boolean isConstructor(){
if(getConstructorTypeSpecifier() != null)return true;
if(getConstructorArraySpecifiers().length > 0)return true;
GLSLIdentifier identifier = getIdentifier();
if(identifier == null)return false;//Default to function call in the case of malformed tree
return isStructReference(identifier);
}
@NotNull
private GLSLType getTypeOfStructConstructor(GLSLIdentifier identifier, GLSLArraySpecifier[] arraySpecifiers){
GLSLStructType structType = findDefinedStruct(identifier);
if(structType == null)return GLSLTypes.UNKNOWN_TYPE;
if(arraySpecifiers.length == 0){
return structType;
}else{
int[] dimensions = new int[arraySpecifiers.length];
for (int i = 0; i < arraySpecifiers.length; i++) {
dimensions[i] = arraySpecifiers[i].getDimensionSize();
}
clarifyConstructorArrayDimensions(dimensions);
return new GLSLArrayType(structType, dimensions);
}
}
private GLSLType getTypeOfFunctionCall(GLSLIdentifier identifier, GLSLType[] parameterTypes){
List<GLSLFunctionType> functionTypes = findDefinedFunctions(identifier.getName(), parameterTypes);
if(functionTypes.size() == 1){
return functionTypes.get(0).getReturnType();
}else{
//Can't resolve with certainty
return GLSLTypes.UNKNOWN_TYPE;
}
}
@NotNull
@Override
public GLSLType getType() {
{ //If name is built in type, resolve as constructor
GLSLTypeSpecifier constructorTypeSpecifier = getConstructorTypeSpecifier();
if(constructorTypeSpecifier != null){
GLSLType constructorType = constructorTypeSpecifier.getType();
//NOTE: Built-in types have different handling of array sizes, it is a part of their type specifier
// Structs don't have that - their array sizes are separate elements after specifier
if(constructorType instanceof GLSLArrayType){
//That array may be implicitly sized, if so - clarify it using parameter list
GLSLArrayType arrayType = (GLSLArrayType) constructorType;
final int[] dimensions = arrayType.getDimensions();
clarifyConstructorArrayDimensions(dimensions);
}
return constructorType;
}
}
{ //Not a built in type, both possibilities still open
GLSLIdentifier identifier = getIdentifier();
if(identifier == null)return GLSLTypes.UNKNOWN_TYPE; //Can't deduce type
GLSLArraySpecifier[] constructorArraySpecifiers = getConstructorArraySpecifiers();
if(constructorArraySpecifiers.length > 0 || isStructReference(identifier)){
//It has some array specifiers or is struct type, it has to be constructor
return getTypeOfStructConstructor(identifier, constructorArraySpecifiers);
}else{
return getTypeOfFunctionCall(identifier, getParameterTypes());
}
}
}
//endregion
//region Struct only
@Nullable
private GLSLStructType findDefinedStruct(GLSLIdentifier identifier){
final GLSLTypeDefinition typeDefinition = GLSLTypeReference.findTypeDefinition(identifier, identifier.getName());
if(typeDefinition == null)return null;
return typeDefinition.getType();
}
private void clarifyConstructorArrayDimensions(final int[] dimensions){
final GLSLParameterList parameterList = getParameterList();
if(parameterList == null)return;
if (dimensions.length >= 1) {
if (dimensions[0] == GLSLArrayType.UNDEFINED_SIZE_DIMENSION) {
dimensions[0] = parameterList.getParameters().length;
}
}
for (int i = 1; i < dimensions.length; i++) {
if (dimensions[i] == GLSLArrayType.UNDEFINED_SIZE_DIMENSION) {
//Clarify further
//TODO
}
}
}
//endregion
//region Function only
@NotNull
private List<GLSLFunctionType> findDefinedFunctions(String name, GLSLType[] parameterTypes){
ArrayList<GLSLFunctionType> result = new ArrayList<GLSLFunctionType>();
PsiElement current = findParentByClass(GLSLFunctionDefinition.class);
while (current != null) {
if (current instanceof GLSLFunctionDeclaration) {
GLSLFunctionType functionType = ((GLSLFunctionDeclaration) current).getType();
if (name.equals(functionType.getName())) {
switch (functionType.getParameterCompatibilityLevel(parameterTypes)) {
case COMPATIBLE_WITH_IMPLICIT_CONVERSION:
result.add(functionType);
break;
case DIRECTLY_COMPATIBLE:
result.clear();
result.add(functionType);
return result;
case INCOMPATIBLE:
break;
default:
assert false : "Unsupported compatibility level.";
}
}
}
current = current.getPrevSibling();
}
return result;
}
@NotNull
public String getFunctionName() {
GLSLIdentifier identifier = getIdentifier();
if(identifier != null){
return identifier.getName();
}
return "(unknown)";
}
/**
* @return All function types this call can possibly call
*/
@NotNull
public List<GLSLFunctionType> getPossibleCalledFunctions(){
final GLSLIdentifier identifier = getIdentifier();
if(identifier == null)return Collections.emptyList();
return findDefinedFunctions(identifier.getText(), getParameterTypes());
}
/**
* @return Called function type or null if ambiguous
*/
@Nullable
public GLSLFunctionType getCalledFunctionType(){
final List<GLSLFunctionType> possibilities = getPossibleCalledFunctions();
if(possibilities.size() == 1){
return possibilities.get(0);
}else{
return null;
}
}
@NotNull
public GLSLReferenceBase<GLSLIdentifier, ? extends GLSLElement> getReferenceProxy() {
if(isConstructor()){
return new GLSLConstructorReference(this);
}else{
return new GLSLFunctionReference(this);
}
}
//endregion
@Override
public String toString() {
if(isConstructor()){
return "Constructor call: "+getType().getTypename();
}else{
return "Function call: " + getFunctionName();
}
}
@Override
public String getName() {
if(isConstructor()){
return getType().getTypename();
} else {
return getFunctionName();
}
}
}