package com.siberika.idea.pascal.lang.parser; import com.intellij.lang.PsiBuilder; import com.intellij.lang.parser.GeneratedParserUtilBase; import com.intellij.navigation.ItemPresentation; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.impl.source.resolve.FileContextUtil; import com.intellij.psi.search.FileTypeIndex; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.PsiElementProcessor; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.indexing.FileBasedIndex; import com.siberika.idea.pascal.PascalFileType; import com.siberika.idea.pascal.PascalIcons; import com.siberika.idea.pascal.lang.psi.PasClassHelperDecl; import com.siberika.idea.pascal.lang.psi.PasClassTypeDecl; import com.siberika.idea.pascal.lang.psi.PasClosureExpr; import com.siberika.idea.pascal.lang.psi.PasConstDeclaration; import com.siberika.idea.pascal.lang.psi.PasEntityScope; import com.siberika.idea.pascal.lang.psi.PasFullyQualifiedIdent; import com.siberika.idea.pascal.lang.psi.PasGenericTypeIdent; import com.siberika.idea.pascal.lang.psi.PasInterfaceTypeDecl; import com.siberika.idea.pascal.lang.psi.PasNamedIdent; import com.siberika.idea.pascal.lang.psi.PasNamespaceIdent; import com.siberika.idea.pascal.lang.psi.PasObjectDecl; import com.siberika.idea.pascal.lang.psi.PasRecordDecl; import com.siberika.idea.pascal.lang.psi.PasRecordHelperDecl; import com.siberika.idea.pascal.lang.psi.PasTypeDecl; import com.siberika.idea.pascal.lang.psi.PascalNamedElement; import com.siberika.idea.pascal.lang.psi.PascalPsiElement; import com.siberika.idea.pascal.lang.psi.PascalQualifiedIdent; import com.siberika.idea.pascal.lang.psi.PascalStructType; import com.siberika.idea.pascal.lang.psi.impl.PascalRoutineImpl; 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 javax.swing.*; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Set; import java.util.TreeSet; import java.util.regex.Pattern; /** * Author: George Bakhtadze * Date: 12/9/12 */ @SuppressWarnings("unchecked") public class PascalParserUtil extends GeneratedParserUtilBase { private static final Logger LOG = Logger.getInstance(PascalParserUtil.class); public static final Collection<String> EXPLICIT_UNITS = Arrays.asList("system", "$builtins"); public static final int MAX_STRUCT_TYPE_RESOLVE_RECURSION = 1000; public static boolean parsePascal(PsiBuilder builder_, int level, Parser parser) { PsiFile file = builder_.getUserDataUnprotected(FileContextUtil.CONTAINING_FILE_KEY); String filename = "<unknown>"; if ((file != null) && (file.getVirtualFile() != null)) { //System.out.println("Parse: " + file.getVirtualFile().getName()); filename = file.getName(); } //builder_.setDebugMode(true); ErrorState state = ErrorState.get(builder_); boolean res = parseAsTree(state, builder_, level, DUMMY_BLOCK, true, parser, TRUE_CONDITION); return res; } @NotNull public static Collection<PascalNamedElement> findSymbols(final Project project, final String pattern) { final Set<PascalNamedElement> result = new HashSet<PascalNamedElement>(); final Pattern p = Pattern.compile("\\w*" + pattern + "\\w*"); processProjectElements(project, new PsiElementProcessor<PascalNamedElement>() { @Override public boolean execute(@NotNull PascalNamedElement element) { if (p.matcher(element.getName()).matches()) { result.add(element); } return true; } }, PascalStructType.class, PasConstDeclaration.class, PascalRoutineImpl.class, PasNamedIdent.class, PasGenericTypeIdent.class); return new ArrayList<PascalNamedElement>(result); } @NotNull public static <T extends PascalNamedElement> Collection<T> findSymbols(final Project project, final String pattern, Class<T> clazz) { final Set<T> result = new HashSet<T>(); final Pattern p = Pattern.compile("\\w*" + pattern + "\\w*"); processProjectElements(project, new PsiElementProcessor<T>() { @Override public boolean execute(@NotNull T element) { if (p.matcher(element.getName()).matches()) { result.add(element); } return true; } }, clazz); return new ArrayList<T>(result); } public static Collection<PascalNamedElement> findClasses(Project project, final String pattern) { final Set<PascalNamedElement> result = new HashSet<PascalNamedElement>(); final Pattern p = Pattern.compile("\\w*" + pattern + "\\w*"); processProjectElements(project, new PsiElementProcessor<PascalStructType>() { @Override public boolean execute(@NotNull PascalStructType element) { if (p.matcher(element.getName()).matches()) { result.add(element); } return true; } }, PascalStructType.class); return new ArrayList<PascalNamedElement>(result); } @SuppressWarnings("unchecked") private static Collection<PascalNamedElement> findTypes(PsiElement element, final String key) { Collection<PascalNamedElement> result = retrieveSortedVisibleEntitiesDecl(element, key, PasGenericTypeIdent.class); return result; } private static boolean isSameAffectingScope(PsiElement innerSection, PsiElement outerSection) { for (int i = 0; i < 4; i++) { if (innerSection == outerSection) { return true; } if ((null == innerSection) || PsiUtil.isInstanceOfAny(innerSection, PasClassTypeDecl.class, PasClassHelperDecl.class, PasInterfaceTypeDecl.class, PasObjectDecl.class, PasRecordDecl.class, PasRecordHelperDecl.class, PasClosureExpr.class, PascalRoutineImpl.class)) { return false; } innerSection = PsiUtil.getNearestAffectingDeclarationsRoot(innerSection); } return false; } /** * add used unit interface declarations to result * @param result list of declarations to add unit declarations to * @param current element which should be affected by a unit declaration in order to be added to result */ @SuppressWarnings("ConstantConditions") private static void addUsedUnitDeclarations(Collection<PascalNamedElement> result, PsiElement current, String name) { for (PascalQualifiedIdent usedUnitName : PsiUtil.getUsedUnits(current.getContainingFile())) { addUnitDeclarations(result, current.getProject(), ModuleUtilCore.findModuleForPsiElement(usedUnitName), usedUnitName.getName(), name); } for (String unitName : EXPLICIT_UNITS) { addUnitDeclarations(result, current.getProject(), ModuleUtilCore.findModuleForPsiElement(current), unitName, name); } } private static void addUnitDeclarations(Collection<PascalNamedElement> result, Project project, Module module, String unitName, String name) { PascalNamedElement usedUnit = PasReferenceUtil.findUnit(project, PasReferenceUtil.findUnitFiles(project, module), unitName); if (usedUnit != null) { addDeclarations(result, PsiUtil.getModuleInterfaceSection(usedUnit), name); } } /** * Add all declarations of entities with matching names from the specified section to result * @param result list of declarations to add declarations to * @param section section containing declarations * @param name name which a declaration should match */ private static void addDeclarations(Collection<PascalNamedElement> result, PsiElement section, String name) { if (section != null) { result.addAll(retrieveEntitiesFromSection(section, name, getEndOffset(section), PasNamedIdent.class, PasGenericTypeIdent.class, PasNamespaceIdent.class)); } } private static int getEndOffset(PsiElement section) { return section != null ? section.getTextRange().getEndOffset() : -1; } /** * Returns type scope from type declaration in type section * @param typeIdent name of declaring type * @param recursionCount to prevent infinite recursion * @return scope if type is structured */ @Nullable public static PasEntityScope getStructTypeByIdent(@NotNull PascalNamedElement typeIdent, int recursionCount) { if (recursionCount > MAX_STRUCT_TYPE_RESOLVE_RECURSION) { return PsiUtil.getElementPasModule(typeIdent); } if (PsiUtil.isTypeDeclPointingToSelf(typeIdent)) { return PsiUtil.getElementPasModule(typeIdent); } PasTypeDecl typeDecl = PsiTreeUtil.getNextSiblingOfType(typeIdent, PasTypeDecl.class); if (typeDecl != null) { PasEntityScope strucTypeDecl = PsiTreeUtil.findChildOfType(typeDecl, PasEntityScope.class, true); if (strucTypeDecl != null) { // structured type return strucTypeDecl; } else { // regular type PasFullyQualifiedIdent typeId = PsiTreeUtil.findChildOfType(typeDecl, PasFullyQualifiedIdent.class, true); return getStructTypeByTypeIdent(typeId, recursionCount); } } return null; } @Nullable private static PasEntityScope getStructTypeByTypeIdent(@Nullable PascalQualifiedIdent typeId, int recursionCount) { if (typeId != null) { PsiElement section = PsiUtil.getNearestAffectingDeclarationsRoot(typeId); Collection<PascalNamedElement> entities = retrieveEntitiesFromSection(section, typeId.getName(), getEndOffset(section), PasGenericTypeIdent.class); addUsedUnitDeclarations(entities, typeId, typeId.getName()); for (PascalNamedElement element : entities) { return getStructTypeByIdent(element, recursionCount + 1); } } return null; } /** * Returns list of entities matching the specified key and classes which may be visible from the element * @param element - element which should be affected by returned named entities * @param key - key which should match entities names * @param classes - classes of entities to retrieve * @return list of entities sorted in such a way that entity nearest to element comes first */ private static <T extends PascalNamedElement> Collection<PascalNamedElement> retrieveSortedVisibleEntitiesDecl(PsiElement element, String key, Class<? extends T>... classes) { Collection<PascalNamedElement> result = new TreeSet<PascalNamedElement>(new Comparator<PascalNamedElement>() { @Override public int compare(PascalNamedElement o1, PascalNamedElement o2) { return o2.getTextRange().getStartOffset() - o1.getTextRange().getStartOffset(); } }); if (null == element.getContainingFile()) { return result; } int offset = element.getTextRange().getStartOffset(); if (PsiUtil.allowsForwardReference(element)) { offset = element.getContainingFile().getTextLength(); } result.addAll(retrieveEntitiesFromSection(PsiUtil.getNearestAffectingDeclarationsRoot(element), key, offset, classes)); return result; } @NotNull private static <T extends PascalNamedElement> Collection<PascalNamedElement> retrieveEntitiesFromSection(PsiElement section, String key, int maxOffset, Class<? extends T>...classes) { final Set<PascalNamedElement> result = new LinkedHashSet<PascalNamedElement>(); if (section != null) { for (PascalNamedElement namedElement : PsiUtil.findChildrenOfAnyType(section, classes)) { if (((null == key) || key.equalsIgnoreCase(namedElement.getName()))) { if ((namedElement.getTextRange().getStartOffset() < maxOffset) && isSameAffectingScope(PsiUtil.getNearestAffectingDeclarationsRoot(namedElement), section)) { result.remove(namedElement); result.add(namedElement); } } } result.addAll(retrieveEntitiesFromSection(PsiUtil.getNearestAffectingDeclarationsRoot(section), key, maxOffset, classes)); } return result; } public static ItemPresentation getPresentation(final PascalNamedElement element) { return new ItemPresentation() { @Nullable @Override public String getPresentableText() { if (element instanceof PascalRoutineImpl) { return element.getText(); } return element.getName() + getType(element); } @Nullable @Override public String getLocationString() { return element.getContainingFile() != null ? element.getContainingFile().getName() : "-"; } @Nullable @Override public Icon getIcon(boolean unused) { return PascalIcons.GENERAL; } }; } private static String getType(PascalNamedElement item) { if (item instanceof PasClassTypeDecl) { return " [Class]"; } else if (item instanceof PasRecordDecl) { return " [Record]"; } else if (item instanceof PasObjectDecl) { return " [Oject]"; } else if (item instanceof PasClassHelperDecl) { return " [Class helper]"; } else if (item instanceof PasRecordHelperDecl) { return " [Record helper]"; } else if (item instanceof PasConstDeclaration) { return " [Const]"; } else if (item instanceof PasTypeDecl) { return " [Type]"; } return ""; } /** * Handle all elements of the specified classes in project source (not in PPU) with the given processor */ public static <T extends PascalPsiElement> void processProjectElements(Project project, PsiElementProcessor<T> processor, Class<? extends T>... clazz) { Collection<VirtualFile> virtualFiles = FileBasedIndex.getInstance().getContainingFiles(FileTypeIndex.NAME, PascalFileType.INSTANCE, GlobalSearchScope.allScope(project)); for (VirtualFile virtualFile : virtualFiles) { PascalFile pascalFile = (PascalFile) PsiManager.getInstance(project).findFile(virtualFile); if (pascalFile != null) { for (T element : PsiUtil.findChildrenOfAnyType(pascalFile, clazz)) { processor.execute(element); } } } } }