/*
* Copyright 2011 Jon S Akhtar (Sylvanaar)
*
* 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 com.sylvanaar.idea.Lua.lang.psi.dataFlow.reachingDefs;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.sylvanaar.idea.Lua.lang.psi.LuaControlFlowOwner;
import com.sylvanaar.idea.Lua.lang.psi.LuaPsiElement;
import com.sylvanaar.idea.Lua.lang.psi.LuaPsiFileBase;
import com.sylvanaar.idea.Lua.lang.psi.controlFlow.ControlFlowUtil;
import com.sylvanaar.idea.Lua.lang.psi.controlFlow.Instruction;
import com.sylvanaar.idea.Lua.lang.psi.controlFlow.ReadWriteVariableInstruction;
import com.sylvanaar.idea.Lua.lang.psi.dataFlow.DFAEngine;
import com.sylvanaar.idea.Lua.lang.psi.expressions.LuaExpression;
import com.sylvanaar.idea.Lua.lang.psi.statements.LuaBlock;
import com.sylvanaar.idea.Lua.lang.psi.statements.LuaStatementElement;
import com.sylvanaar.idea.Lua.lang.psi.types.LuaType;
import gnu.trove.TIntHashSet;
import gnu.trove.TIntObjectHashMap;
import gnu.trove.TIntObjectProcedure;
import gnu.trove.TIntProcedure;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author ven
*/
public class ReachingDefinitionsCollector {
private ReachingDefinitionsCollector() {
}
public static FragmentVariableInfos obtainVariableFlowInformation(final LuaStatementElement first, final LuaStatementElement last) {
LuaPsiElement context = PsiTreeUtil.getParentOfType(first, LuaBlock.class, LuaPsiFileBase.class);
LuaControlFlowOwner flowOwner;
flowOwner = (LuaControlFlowOwner) context;
assert flowOwner != null;
assert PsiTreeUtil.isAncestor(flowOwner, last, true);
final Instruction[] flow = flowOwner.getControlFlow();
final ReachingDefinitionsDfaInstance dfaInstance = new ReachingDefinitionsDfaInstance(flow);
final ReachingDefinitionsSemilattice lattice = new ReachingDefinitionsSemilattice();
final DFAEngine<TIntObjectHashMap<TIntHashSet>> engine = new DFAEngine<TIntObjectHashMap<TIntHashSet>>(flow, dfaInstance, lattice);
final TIntObjectHashMap<TIntHashSet> dfaResult = postprocess(engine.performDFA(), flow, dfaInstance);
final LinkedHashSet<Integer> fragmentInstructions = getFragmentInstructions(first, last, flow);
final int[] postorder = ControlFlowUtil.postorder(flow);
LinkedHashSet<Integer> reachableFromFragmentReads = getReachable(fragmentInstructions, flow, dfaResult, postorder);
LinkedHashSet<Integer> fragmentReads = filterReads(fragmentInstructions, flow);
final Map<String, VariableInfo> imap = new LinkedHashMap<String, VariableInfo>();
final Map<String, VariableInfo> omap = new LinkedHashMap<String, VariableInfo>();
final PsiManager manager = first.getManager();
for (final Integer ref : fragmentReads) {
ReadWriteVariableInstruction rwInstruction = (ReadWriteVariableInstruction) flow[ref];
String name = rwInstruction.getVariableName();
final int[] defs = dfaResult.get(ref).toArray();
if (!allDefsInFragment(defs, fragmentInstructions)) {
addVariable(name, imap, manager, getType(rwInstruction.getElement()));
}
}
for (final Integer ref : reachableFromFragmentReads) {
ReadWriteVariableInstruction rwInstruction = (ReadWriteVariableInstruction) flow[ref];
String name = rwInstruction.getVariableName();
final int[] defs = dfaResult.get(ref).toArray();
if (anyDefInFragment(defs, fragmentInstructions)) {
for (int def : defs) {
if (fragmentInstructions.contains(def)) {
LuaType outputType = getType(flow[def].getElement());
addVariable(name, omap, manager, outputType);
}
}
if (!allProperDefsInFragment(defs, ref, fragmentInstructions, postorder)) {
LuaType inputType = getType(rwInstruction.getElement());
addVariable(name, imap, manager, inputType);
}
}
}
// addClosureUsages(imap, omap, first, last, flowOwner);
final VariableInfo[] iarr = filterNonlocals(imap, first);
final VariableInfo[] oarr = filterNonlocals(omap, first);
return new FragmentVariableInfos() {
public VariableInfo[] getInputVariableNames() {
return iarr;
}
public VariableInfo[] getOutputVariableNames() {
return oarr;
}
};
}
// private static void addClosureUsages(final Map<String, VariableInfo> imap, final Map<String, VariableInfo> omap, final LuaStatement first, final LuaStatement last, LuaControlFlowOwner flowOwner) {
// flowOwner.accept(new LuaRecursiveElementVisitor() {
// public void visitClosure(LuaClosableBlock closure) {
// addUsagesInClosure(imap, omap, closure, first, last);
// super.visitClosure(closure);
// }
//
// private void addUsagesInClosure(final Map<String, VariableInfo> imap, final Map<String, VariableInfo> omap, final LuaClosableBlock closure, final LuaStatement first, final LuaStatement last) {
// closure.accept(new LuaRecursiveElementVisitor() {
// public void visitReferenceExpression(LuaReferenceExpression refExpr) {
// if (refExpr.isQualified()) {
// return;
// }
// PsiElement resolved = refExpr.resolve();
// if (!(resolved instanceof LuaVariable)) {
// return;
// }
// LuaVariable variable = (LuaVariable) resolved;
// if (PsiTreeUtil.isAncestor(closure, variable, true)) {
// return;
// }
// if (variable instanceof ClosureSyntheticParameter &&
// PsiTreeUtil.isAncestor(closure, ((ClosureSyntheticParameter)variable).getClosure(), false)) {
// return;
// }
//
// String name = variable.getName();
// if (name != null) {
// if (!(variable instanceof LuaField)) {
// if (!isInFragment(first, last, resolved)) {
// if (isInFragment(first, last, closure)) {
// addVariable(name, imap, variable.getManager(), variable.getType());
// }
// } else {
// if (!isInFragment(first, last, closure)) {
// addVariable(name, omap, variable.getManager(), variable.getType());
// }
// }
// }
// }
// }
// });
// }
// });
// }
private static void addVariable(String name, Map<String, VariableInfo> map, PsiManager manager, LuaType type) {
VariableInfoImpl info = (VariableInfoImpl) map.get(name);
if (info == null) {
info = new VariableInfoImpl(name, manager);
map.put(name, info);
}
info.addSubtype(type);
}
private static LinkedHashSet<Integer> filterReads(final LinkedHashSet<Integer> instructions, final Instruction[] flow) {
final LinkedHashSet<Integer> result = new LinkedHashSet<Integer>();
for (final Integer i : instructions) {
final Instruction instruction = flow[i];
if (instruction instanceof ReadWriteVariableInstruction && !((ReadWriteVariableInstruction) instruction).isWrite()) {
result.add(i);
}
}
return result;
}
private static boolean allDefsInFragment(int[] defs, LinkedHashSet<Integer> fragmentInstructions) {
for (int def : defs) {
if (!fragmentInstructions.contains(def)) return false;
}
return true;
}
private static boolean allProperDefsInFragment(int[] defs, int ref, LinkedHashSet<Integer> fragmentInstructions, int[] postorder) {
for (int def : defs) {
if (!fragmentInstructions.contains(def) && postorder[def] < postorder[ref]) return false;
}
return true;
}
private static boolean anyDefInFragment(int[] defs, LinkedHashSet<Integer> fragmentInstructions) {
for (int def : defs) {
if (fragmentInstructions.contains(def)) return true;
}
return false;
}
@Nullable
private static LuaType getType(PsiElement element) {
if (element instanceof LuaExpression) return ((LuaExpression) element).getLuaType();
return null;
}
private static VariableInfo[] filterNonlocals(Map<String, VariableInfo> infos, LuaStatementElement place) {
List<VariableInfo> result = new ArrayList<VariableInfo>();
// for (Iterator<VariableInfo> iterator = infos.values().iterator(); iterator.hasNext();) {
// VariableInfo info = iterator.next();
// String name = info.getName();
// LuaPsiElement property = ResolveUtil.resolveProperty(place, name);
// if (property instanceof LuaVariable) iterator.remove();
// else if (property instanceof LuaReferenceExpression) {
// LuaMember member = PsiTreeUtil.getParentOfType(property, LuaMember.class);
// if (member == null) continue;
// else if (!member.hasModifierProperty(PsiModifier.STATIC)) {
// if (member.getContainingClass() instanceof LuaScriptClass) {
// //binding variable
// continue;
// }
// }
// }
// if (ResolveUtil.resolveClass(place, name) == null) {
// result.add(info);
// }
// }
return result.toArray(new VariableInfo[result.size()]);
}
private static LinkedHashSet<Integer> getFragmentInstructions(LuaStatementElement first, LuaStatementElement last, Instruction[] flow) {
LinkedHashSet<Integer> result = new LinkedHashSet<Integer>();
for (Instruction instruction : flow) {
if (isInFragment(instruction, first, last)) {
result.add(instruction.num());
}
}
return result;
}
private static boolean isInFragment(Instruction instruction, LuaStatementElement first, LuaStatementElement last) {
final PsiElement element = instruction.getElement();
if (element == null) return false;
return isInFragment(first, last, element);
}
private static boolean isInFragment(LuaStatementElement first, LuaStatementElement last, PsiElement element) {
final PsiElement parent = first.getParent();
if (!PsiTreeUtil.isAncestor(parent, element, true)) return false;
PsiElement run = element;
while (run.getParent() != parent) run = run.getParent();
return isBetween(first, last, run);
}
private static boolean isBetween(PsiElement first, PsiElement last, PsiElement run) {
while (first != null && first != run) first = first.getNextSibling();
if (first == null) return false;
while (last != null && last != run) last = last.getPrevSibling();
if (last == null) return false;
return true;
}
private static LinkedHashSet<Integer> getReachable(final LinkedHashSet<Integer> fragmentInsns, final Instruction[] flow, TIntObjectHashMap<TIntHashSet> dfaResult, final int[] postorder) {
final LinkedHashSet<Integer> result = new LinkedHashSet<Integer>();
for (Instruction insn : flow) {
if (insn instanceof ReadWriteVariableInstruction &&
!((ReadWriteVariableInstruction) insn).isWrite()) {
final int ref = insn.num();
TIntHashSet defs = dfaResult.get(ref);
defs.forEach(new TIntProcedure() {
public boolean execute(int def) {
if (fragmentInsns.contains(def)) {
if (!fragmentInsns.contains(ref) || postorder[ref] < postorder[def]) {
result.add(ref);
return false;
}
}
return true;
}
});
}
}
return result;
}
@SuppressWarnings({"UnusedDeclaration"})
private static String dumpDfaResult(ArrayList<TIntObjectHashMap<TIntHashSet>> dfaResult, ReachingDefinitionsDfaInstance dfa) {
final StringBuffer buffer = new StringBuffer();
for (int i = 0; i < dfaResult.size(); i++) {
TIntObjectHashMap<TIntHashSet> map = dfaResult.get(i);
buffer.append("At " + i + ":\n");
map.forEachEntry(new TIntObjectProcedure<TIntHashSet>() {
public boolean execute(int i, TIntHashSet defs) {
buffer.append(i).append(" -> ");
defs.forEach(new TIntProcedure() {
public boolean execute(int i) {
buffer.append(i).append(" ");
return true;
}
});
return true;
}
});
buffer.append("\n");
}
return buffer.toString();
}
private static class VariableInfoImpl implements VariableInfo {
private
@NotNull final String myName;
private final PsiManager myManager;
private
@Nullable
LuaType myType;
VariableInfoImpl(@NotNull String name, PsiManager manager) {
myName = name;
myManager = manager;
}
@NotNull
public String getName() {
return myName;
}
@Nullable
public LuaType getType() {
//if (myType instanceof PsiIntersectionType) return ((PsiIntersectionType) myType).getConjuncts()[0];
return myType;
}
void addSubtype(LuaType t) {
// if (t != null) {
// if (myType == null) myType = t;
// else {
// if (!myType.isAssignableFrom(t)) {
// if (t.isAssignableFrom(myType)) {
// myType = t;
// } else {
// //TODO myType = TypesUtil.getLeastUpperBound(myType, t, myManager);
// }
// }
// }
// }
}
}
private static TIntObjectHashMap<TIntHashSet> postprocess(final ArrayList<TIntObjectHashMap<TIntHashSet>> dfaResult, Instruction[] flow, ReachingDefinitionsDfaInstance dfaInstance) {
TIntObjectHashMap<TIntHashSet> result = new TIntObjectHashMap<TIntHashSet>();
for (int i = 0; i < flow.length; i++) {
Instruction insn = flow[i];
if (insn instanceof ReadWriteVariableInstruction) {
ReadWriteVariableInstruction rwInsn = (ReadWriteVariableInstruction) insn;
if (!rwInsn.isWrite()) {
int idx = dfaInstance.getVarIndex(rwInsn.getVariableName());
TIntHashSet defs = dfaResult.get(i).get(idx);
if (defs == null) defs = new TIntHashSet();
result.put(i, defs);
}
}
}
return result;
}
}