package com.siberika.idea.pascal.lang.psi.impl; import com.intellij.extapi.psi.ASTWrapperPsiElement; import com.intellij.lang.ASTNode; import com.intellij.psi.PsiElement; import com.intellij.psi.impl.source.tree.LeafPsiElement; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.SmartList; import com.siberika.idea.pascal.lang.parser.NamespaceRec; import com.siberika.idea.pascal.lang.psi.PasAssignPart; import com.siberika.idea.pascal.lang.psi.PasCallExpr; import com.siberika.idea.pascal.lang.psi.PasClassProperty; import com.siberika.idea.pascal.lang.psi.PasConstDeclaration; import com.siberika.idea.pascal.lang.psi.PasConstExpression; import com.siberika.idea.pascal.lang.psi.PasDereferenceExpr; import com.siberika.idea.pascal.lang.psi.PasEntityScope; import com.siberika.idea.pascal.lang.psi.PasExpr; import com.siberika.idea.pascal.lang.psi.PasExpression; import com.siberika.idea.pascal.lang.psi.PasFormalParameterSection; import com.siberika.idea.pascal.lang.psi.PasFullyQualifiedIdent; import com.siberika.idea.pascal.lang.psi.PasGenericTypeIdent; import com.siberika.idea.pascal.lang.psi.PasIndexExpr; import com.siberika.idea.pascal.lang.psi.PasLiteralExpr; import com.siberika.idea.pascal.lang.psi.PasParenExpr; import com.siberika.idea.pascal.lang.psi.PasProductExpr; import com.siberika.idea.pascal.lang.psi.PasReferenceExpr; import com.siberika.idea.pascal.lang.psi.PasRelationalExpr; import com.siberika.idea.pascal.lang.psi.PasSumExpr; import com.siberika.idea.pascal.lang.psi.PasTypeDecl; import com.siberika.idea.pascal.lang.psi.PasTypeID; import com.siberika.idea.pascal.lang.psi.PasTypes; import com.siberika.idea.pascal.lang.psi.PasUnaryExpr; import com.siberika.idea.pascal.lang.psi.PasUnaryOp; import com.siberika.idea.pascal.lang.psi.PascalNamedElement; import com.siberika.idea.pascal.lang.psi.PascalPsiElement; import com.siberika.idea.pascal.lang.psi.PascalStructType; import com.siberika.idea.pascal.lang.references.PasReferenceUtil; import com.siberika.idea.pascal.util.PsiUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.List; /** * Author: George Bakhtadze * Date: 03/02/2015 */ public class PascalExpression extends ASTWrapperPsiElement implements PascalPsiElement { private static final int MAX_KIND_DIFF = 2; private static final String BUILTIN_SELF_UPPER = PasEntityScope.BUILTIN_SELF.toUpperCase(); public PascalExpression(ASTNode node) { super(node); } public boolean isRoot() { return getParent() instanceof PasExpression; } @Nullable public PascalOperation getOperation() { return PsiUtil.findImmChildOfAnyType(this, PascalOperation.class); } public static List<PasField.ValueType> getTypes(PascalExpression expr) { List<PasField.ValueType> res; if (expr instanceof PasReferenceExpr) { res = getChildType(getFirstChild(expr)); PasField.ValueType fieldType = resolveType(retrieveScope(res), ((PasReferenceExpr) expr).getFullyQualifiedIdent()); if (fieldType != null) { res.add(fieldType); } } else if (expr instanceof PasDereferenceExpr) { res = getChildType(getFirstChild(expr)); res.add(new PasField.ValueType(null, PasField.Kind.POINTER, null, null)); } else if (expr instanceof PasIndexExpr) { res = getChildType(getFirstChild(expr)); if (!res.isEmpty()) { // Replace scope if indexing default array property PasEntityScope scope = res.iterator().next().getTypeScope(); PascalNamedElement defProp = scope != null ? PsiUtil.getDefaultProperty(scope) : null; if (defProp instanceof PasClassProperty) { PasTypeID typeId = ((PasClassProperty) defProp).getTypeID(); if (typeId != null) { PasField.ValueType fieldType = resolveType(scope, typeId.getFullyQualifiedIdent()); if (fieldType != null) { res = new SmartList<PasField.ValueType>(fieldType); } } } } res.add(new PasField.ValueType(null, PasField.Kind.ARRAY, null, null)); } else if (expr instanceof PasProductExpr) { // AS operator case res = getChildType(getLastChild(expr)); } else { res = getChildType(getFirstChild(expr)); } return res; } private static PasField.ValueType resolveType(PasEntityScope scope, PasFullyQualifiedIdent fullyQualifiedIdent) { final Collection<PasField> references = PasReferenceUtil.resolve(null, scope, NamespaceRec.fromElement(fullyQualifiedIdent), PasField.TYPES_ALL, true, 0); if (!references.isEmpty()) { PasField field = references.iterator().next(); PasReferenceUtil.retrieveFieldTypeScope(field); return field.getValueType(); } return null; } private static List<PasField.ValueType> getChildType(PsiElement child) { if (child instanceof PascalExpression) { return PascalExpression.getTypes((PascalExpression) child); } return new SmartList<PasField.ValueType>(); } private static PsiElement getFirstChild(PascalExpression expr) { PsiElement res = expr.getFirstChild(); while ((res != null) && (res.getClass() == LeafPsiElement.class)) { res = res.getNextSibling(); } return res; } private static PsiElement getLastChild(PascalExpression expr) { PsiElement res = expr.getLastChild(); while ((res != null) && (res.getClass() == LeafPsiElement.class)) { res = res.getPrevSibling(); } return res; } public static PasEntityScope retrieveScope(List<PasField.ValueType> types) { PasField.ValueType newScope = null; for (PasField.ValueType type : types) { if (type.field != null) { newScope = type; } } return newScope != null ? newScope.getTypeScope() : null; } public static String infereType(PasExpr expression) { return doInfereType(expression, false); } private static String doInfereType(PasExpr expression, boolean minus) { if (expression instanceof PasLiteralExpr) { PsiElement literal = expression.getFirstChild(); IElementType type = literal.getNode().getElementType(); if ((type == PasTypes.NUMBER_HEX) || (type == PasTypes.NUMBER_OCT) || (type == PasTypes.NUMBER_BIN)) { return Primitive.INTEGER.name; } else if ((type == PasTypes.NUMBER_INT)) { return calcIntType(literal.getText(), minus); } else if (type == PasTypes.NUMBER_REAL) { return calcFloatType(literal.getText()); } else if ((type == PasTypes.TRUE) || (type == PasTypes.FALSE)) { return Primitive.BOOLEAN.name; } else if (type == PasTypes.NIL) { return Primitive.POINTER.name; } else if (type == PasTypes.STRING_FACTOR) { return Primitive.STRING.name; } } else if (expression instanceof PasParenExpr) { return !((PasParenExpr) expression).getExprList().isEmpty() ? infereType(((PasParenExpr) expression).getExprList().get(0)) : null; } else if (expression instanceof PasUnaryExpr) { return infereUnaryExprType((PasUnaryExpr) expression); } else if (expression instanceof PasSumExpr) { PasSumExpr sumExpr = (PasSumExpr) expression; return combineType("-".equals(sumExpr.getAddOp().getText()) ? Operation.SUBSTRACT : Operation.SUM, sumExpr.getExprList()); } else if (expression instanceof PasRelationalExpr) { return Primitive.BOOLEAN.name; } else if (expression instanceof PasProductExpr) { return handleProduct((PasProductExpr) expression); } else if (expression instanceof PasCallExpr) { return inferCallType((PasCallExpr) expression); } else { List<PasField.ValueType> types = getTypes((PascalExpression) expression); PasField.ValueType lastType = null; for (PasField.ValueType type : types) { if (type.field != null) { lastType = type; } else if ((type.kind == PasField.Kind.ARRAY) || (type.kind == PasField.Kind.POINTER)) { lastType = lastType != null ? lastType.baseType : null; } } if (lastType != null) { return getTypeIdentifier(lastType); } } return null; } private static String calcFloatType(String text) { int len = text.lastIndexOf('.'); if (len > 0) { len = text.length() - len - 1; if (len < 8) { return Primitive.SINGLE.name; } else if (len > 15) { return Primitive.EXTENDED.name; } } return Primitive.DOUBLE.name; } private static final int MAX_INT32_LENGTH = "2147483648".length(); private static final int MAX_INT64_LENGTH = "9223372036854775807".length(); private static final long MAX_INT64 = 9223372036854775807L; private static final long MAX_INT32 = 2147483647L; private static final long MIN_INT32 = -2147483648L; private static String calcIntType(String text, boolean negative) { int len = text.length(); if (len < MAX_INT32_LENGTH) { return Primitive.INTEGER.name; } else if (len == MAX_INT32_LENGTH) { return (!negative && isLEThan(text, MAX_INT32)) || (negative && isLEThan(text, -MIN_INT32)) ? Primitive.INTEGER.name : Primitive.INT64.name; } else if (len < MAX_INT64_LENGTH) { return Primitive.INT64.name; } else if (len == MAX_INT64_LENGTH) { return negative || isLEThan(text, MAX_INT64) ? Primitive.INT64.name : Primitive.QWORD.name; } else { return Primitive.QWORD.name; } } private static boolean isLEThan(String text, long l) { try { return Long.valueOf(text) <= l; } catch (NumberFormatException e) { return false; } } private static String handleProduct(PasProductExpr expr) { String op = expr.getMulOp().getText().toUpperCase(); if ("AS".equals(op)) { List<PasExpr> exprs = expr.getExprList(); if (exprs.size() > 1) { return exprs.get(1).getText(); } } return combineType("/".equals(op) ? Operation.DIVISION : Operation.PRODUCT, expr.getExprList()); } private static String inferCallType(PasCallExpr expression) { PasFullyQualifiedIdent ref = PsiTreeUtil.findChildOfType(expression.getExpr(), PasFullyQualifiedIdent.class); if (null == ref) { return null; } Collection<PasField> routines = PasReferenceUtil.resolveExpr(null, NamespaceRec.fromElement(ref), PasField.TYPES_ROUTINE, true, 0); PascalRoutineImpl suitable = null; for (PasField routine : routines) { PascalNamedElement el = routine.getElement(); if (el instanceof PascalRoutineImpl) { suitable = (PascalRoutineImpl) el; PasFormalParameterSection params = suitable.getFormalParameterSection(); // TODO: make type check and handle overload if (params != null && params.getFormalParameterList().size() == expression.getArgumentList().getExprList().size()) { return suitable.getFunctionTypeStr(); } } } if (suitable != null) { return suitable.getFunctionTypeStr(); } // Handle as typecast if ((expression.getExpr() instanceof PasReferenceExpr) && (expression.getArgumentList().getExprList().size() == 1)) { return expression.getExpr().getText(); } return null; } private static String infereUnaryExprType(PasUnaryExpr expression) { PasUnaryOp op = expression.getUnaryOp(); if ("@".equals(op.getText())) { return Primitive.POINTER.name; } else { return doInfereType(expression.getExpr(), "-".equals(op.getText())); } } private static String combineType(Operation op, List<PasExpr> exprList) { PasExpr arg1 = exprList.size() > 0 ? exprList.get(0) : null; PasExpr arg2 = exprList.size() > 1 ? exprList.get(1) : null; if (arg2 != null) { String type1 = infereType(arg1); String type2 = infereType(arg2); if (null == type1) { return type2; } if (null == type2) { return type1; } Primitive prim1 = toPrimitive(type1); if (null == prim1) { return type1; } Primitive prim2 = toPrimitive(type2); if (null == prim2) { return type2; } if (Operation.DIVISION.equals(op)) { return divisionType(prim1, prim2, type1, type2); } int diff = Math.abs(prim1.kind - prim2.kind); if (diff > MAX_KIND_DIFF) { return type1; } else if (0 == diff || 2 == diff) { return prim1.ordinal() > prim2.ordinal() ? type1 : type2; } else { int maxKind = Math.max(prim1.kind, prim2.kind); if (maxKind == Primitive.SINGLE.kind) { return prim1.ordinal() > prim2.ordinal() ? type1 : type2; } // signed and unsigned: widen result Primitive signedPrim = prim1; Primitive unsignedPrim = prim2; if (prim1.kind == Primitive.BYTE.kind) { signedPrim = prim2; unsignedPrim = prim1; } switch (unsignedPrim.size) { case 1: { unsignedPrim = Primitive.SMALLINT; break; } case 2: { unsignedPrim = Primitive.INTEGER; break; } case 4: { unsignedPrim = Primitive.INT64; break; } default: unsignedPrim = Primitive.INT64; } return unsignedPrim.size >= signedPrim.size ? unsignedPrim.name : signedPrim.name; } } return arg1 != null ? infereType(arg1) : null; } private static String divisionType(@NotNull Primitive prim1, @NotNull Primitive prim2, String type1, String type2) { int kind = Math.max(prim1.kind, prim2.kind); if (kind <= Primitive.INTEGER.kind) { // Integer operands return Primitive.SINGLE.name; } else { return prim1.ordinal() > prim2.ordinal() ? type1 : type2; } } private static Primitive toPrimitive(String type) { type = type.toUpperCase(); for (Primitive primitive : Primitive.values()) { if (primitive.name().equals(type)) { return primitive; } } return null; } @Nullable public static String getTypeIdentifier(PasField.ValueType type) { if (type.field != null) { PascalNamedElement el = type.field.getElement(); if ((type.field.fieldType == PasField.FieldType.PSEUDO_VARIABLE) && BUILTIN_SELF_UPPER.equals(type.field.name.toUpperCase())) { return el instanceof PascalStructType ? el.getName() : null; } else if (el instanceof PasGenericTypeIdent) { return el.getText(); } else { PasTypeDecl res = PsiUtil.getTypeDeclaration(type.field.getElement()); if (res != null) { return res.getText(); } else if (type.field.fieldType == PasField.FieldType.CONSTANT) { if ((el != null) && (el.getParent() instanceof PasConstDeclaration)) { PasConstExpression expr = ((PasConstDeclaration) el.getParent()).getConstExpression(); return (expr != null) && (expr.getExpression() != null) ? infereType(expr.getExpression().getExpr()) : null; } } else if (type.field.fieldType == PasField.FieldType.PROPERTY) { PasTypeID typeId = PasReferenceUtil.resolvePropertyType(type.field, (PasClassProperty) type.field.getElement()); return typeId.getText(); } } } return null; } @SuppressWarnings("unchecked") public static String calcAssignStatementType(PsiElement statement) { PasAssignPart rightPart = PsiUtil.findImmChildOfAnyType(statement, PasAssignPart.class); return (rightPart != null) && (rightPart.getExpression() != null) ? infereType(rightPart.getExpression().getExpr()) : null; } @SuppressWarnings("unchecked") public static String calcAssignExpectedType(PsiElement statement) { PasExpression leftPart = PsiUtil.findImmChildOfAnyType(statement, PasAssignPart.class) != null ? PsiUtil.findImmChildOfAnyType(statement, PasExpression.class) : null; return leftPart != null ? infereType(leftPart.getExpr()) : null; } public enum Operation { SUM, SUBSTRACT, PRODUCT, DIVISION } public enum Primitive { BYTE("Byte", 0, 1), WORD("Word", 0, 2), DWORD("DWord", 0, 4), LONGWORD("Longword", 0, 4), CARDINAL("Cardinal", 0, 4), QWORD("QWord", 0, 8), SHORTINT("Shortint", 1, 1), SMALLINT("Smallint", 1, 2), INTEGER("Integer", 1, 4), LONGINT("Longint", 1, 4), NATIVEINT("Nativeint", 1, 4), INT64("Int64", 1, 8), SINGLE("Single", 2, 4), REAL("Real", 2, 6), DOUBLE("Double", 2, 8), EXTENDED("Extended", 2, 10), POINTER("Pointer", 10, 4), BYTEBOOL("ByteBool", 20, 1), BOOLEAN("Boolean", 20, 1), WORDBOOL("WordBool", 20, 2), LONGBOOL("LongBool", 20, 4), CHAR("Char", 30, 1), STRING("String", 31, 4); private final String name; private final int kind; private final int size; Primitive(String name, int kind, int size) { this.name = name; this.kind = kind; this.size = size; } } }