package com.haskforce.psi;
import com.intellij.openapi.util.Condition;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
public class HaskellPsiUtil {
@NotNull
public static <T extends PsiElement> String[] getTexts(@NotNull List<T> psiElements) {
final int size = psiElements.size();
String[] result = new String[size];
for (int i = 0; i < size; ++i) {
result[i] = psiElements.get(i).getText();
}
return result;
}
public static boolean hasPragma(@NotNull PsiFile file, @NotNull String pragmaName) {
HaskellPpragma[] ppragmas = PsiTreeUtil.getChildrenOfType(file, HaskellPpragma.class);
if (ppragmas == null) return false;
for (HaskellPpragma ppragma : ppragmas) {
if (ppragma.getText().contains(pragmaName)) return true;
}
return false;
}
/**
* Returns a map of module -> alias for each imported module. If a module is imported but not qualified, alias
* will be null.
*/
@NotNull
public static List<Import> parseImports(@NotNull final PsiFile file) {
final boolean noImplicitPrelude = hasPragma(file, "NoImplicitPrelude");
final Import prelude = Import.global("Prelude", false, null);
boolean importedPrelude = false;
HaskellImpdecl[] impdecls = PsiTreeUtil.getChildrenOfType(PsiTreeUtil.getChildOfType(file, HaskellBody.class), HaskellImpdecl.class);
if (impdecls == null) {
if (noImplicitPrelude) return Collections.emptyList();
return Collections.singletonList(prelude);
}
List<Import> result = new ArrayList<Import>(impdecls.length);
for (HaskellImpdecl impdecl : impdecls) {
final List<HaskellQconid> qconids = impdecl.getQconidList();
final int numQconids = qconids.size();
if (numQconids == 0) { continue; }
final HaskellQconid moduleQconid = qconids.get(0);
final String module = moduleQconid.getText();
final String alias = numQconids > 1 ? qconids.get(1).getText() : null;
final boolean isQualified = impdecl.getQualified() != null;
final boolean isHiding = impdecl.getHiding() != null;
final String[] explicitNames;
// Check if we have an empty import list.
if (impdecl.getImpempty() != null) {
explicitNames = ArrayUtils.EMPTY_STRING_ARRAY;
// Otherwise, if we have a left paren, we have an import list.
} else if (impdecl.getLparen() != null) {
explicitNames = getTexts(collectNamedElementsInImporttList(impdecl.getImporttList()));
// At this point, we must not have an import list at all.
} else {
explicitNames = null;
}
importedPrelude = importedPrelude || module.equals("Prelude");
final Import anImport;
if (isQualified) {
if (alias == null) {
anImport = Import.qualified(module, isHiding, explicitNames);
} else {
anImport = Import.qualifiedAs(module, alias, isHiding, explicitNames);
}
} else {
if (alias == null) {
anImport = Import.global(module, isHiding, explicitNames);
} else {
anImport = Import.globalAs(module, alias, isHiding, explicitNames);
}
}
result.add(anImport);
}
if (!importedPrelude && !noImplicitPrelude) { result.add(prelude); }
return result;
}
public static boolean isType(@Nullable PsiElement e, @NotNull IElementType t) {
return e != null && e.getNode().getElementType().equals(t);
}
@NotNull
public static List<PsiElement> getNamedElementsInImportt(HaskellImportt importt) {
final List<PsiElement> result = new ArrayList<PsiElement>(importt.getChildren().length);
importt.acceptChildren(new HaskellVisitor() {
@Override
public void visitCon(@NotNull HaskellCon o) {
result.add(o);
}
@Override
public void visitVarid(@NotNull HaskellVarid o) {
result.add(o);
}
@Override
public void visitVars(@NotNull HaskellVars o) {
result.addAll(o.getVaridList());
}
@Override
public void visitTycon(@NotNull HaskellTycon o) {
result.add(o);
}
});
return result;
}
@NotNull
public static List<PsiElement> collectNamedElementsInImporttList(List<HaskellImportt> importts) {
List<PsiElement> result = new ArrayList<PsiElement>(importts.size() * 2);
for (HaskellImportt importt : importts) {
result.addAll(getNamedElementsInImportt(importt));
}
return result;
}
@NotNull
public static Set<String> getImportModuleNames(@NotNull List<Import> imports) {
return ContainerUtil.map2Set(imports, new Function<Import, String>() {
@Override
public String fun(Import anImport) {
return anImport.module;
}
});
}
public static class Import {
public final boolean isQualified;
public @NotNull final String module;
public @Nullable final String alias;
public final boolean isHiding;
private @Nullable final String[] explicitNames;
private Import(boolean isQualified, @NotNull String module, @Nullable String alias, boolean isHiding, @Nullable String[] explicitNames) {
this.isQualified = isQualified;
this.module = module;
this.alias = alias;
this.isHiding = isHiding;
this.explicitNames = explicitNames;
}
public static Import global(@NotNull String module, boolean isHiding, @Nullable String[] explicitNames) {
return new Import(false, module, null, isHiding, explicitNames);
}
public static Import globalAs(@NotNull String module, @NotNull String alias, boolean isHiding, @Nullable String[] explicitNames) {
return new Import(false, module, alias, isHiding, explicitNames);
}
public static Import qualified(@NotNull String module, boolean isHiding, @Nullable String[] explicitNames) {
return new Import(true, module, module, isHiding, explicitNames);
}
public static Import qualifiedAs(@NotNull String module, @NotNull String alias, boolean isHiding, @Nullable String[] explicitNames) {
return new Import(true, module, alias, isHiding, explicitNames);
}
@Nullable
public String[] getImportedNames() {
return isHiding ? null : explicitNames;
}
@Nullable
public String[] getHidingNames() {
return isHiding ? explicitNames : null;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
@Override
public boolean equals(Object o) {
return EqualsBuilder.reflectionEquals(this, o);
}
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}
}