/** * This file is licensed under the terms of the Modified BSD License. */ package abs.backend.erlang; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.List; import java.util.Set; import abs.frontend.ast.ParamDecl; import abs.frontend.ast.VarOrFieldDecl; import com.google.common.collect.Sets; /** * Used for tracking variables through different scopes * * @author Georg Göri * */ public class Vars extends LinkedHashMap<String, Var> { public static final String PREFIX = "V_"; private static final long serialVersionUID = 1L; private int temp = 1; /** * Copy Constructor */ public Vars(Vars vars) { super(vars); temp = vars.temp; } public Vars() { } public static Vars n(abs.frontend.ast.List<ParamDecl> args) { Vars l = new Vars(); for (ParamDecl n : args) { l.put(n.getName(), new Var(n.hasReferences())); } return l; } public static Vars n(String... name) { Vars l = new Vars(); for (String n : name) { // Suspect that references may exist when we don't know the type. l.put(n, new Var(true)); } return l; } public String getTemp() { return "T_" + temp++; } /** * Introduce a new variable */ public String nV(VarOrFieldDecl d) { String name = d.getName(); Var v = get((Object) name); if (v != null) if (v.isSet()) throw new RuntimeException("Var already exists:"+name+":" +d.getFileName()+"@" + d.getStartLine()+"/"+d.getStartColumn()); else put(name, v.inc()); else put(name, new Var(d.hasReferences())); return get(name); } /** * Introduces a new variable and ignores if it overloads a previous one * (used for let) */ public String nVignoreOverload(VarOrFieldDecl d) { String name = d.getName(); put(name, new Var(d.hasReferences())); return get(name); } /** * Increase counter of variable */ public String inc(String name) { // TODO: Review, can you get(name) directly and test for null, avoiding the duplicate lookup? [stolz] if (containsKey(name)) { put(name, super.get(name).inc()); } else { // Suspect that references may exist when we don't know the type. put(name, new Var(true)); } return get(name); } public void incAll() { for (java.util.Map.Entry<String, Var> a : this.entrySet()) if (a.getValue().isSet()) a.setValue(a.getValue().inc()); } /** * Mark a variable as awaited (i.e. a get will not block). */ public void await(String name) { Var v = super.get(name); if (v != null) { put(name, v.await()); } } public boolean canBlock(String name) { Var v = super.get(name); if (v != null) { return v.canBlock(); } return true; } /** * Get Erlang name of a variable */ public String get(String name) { if (!super.get(name).isSet()) throw new RuntimeException("Tried to access protected but not set var"); int c = super.get(name).getCount(); return PREFIX + name + "_" + c; } /** * Return a instance for a new branch */ public Vars pass() { return new Vars(this); } public String toParamList() { StringBuilder sb = new StringBuilder("["); boolean first = true; for (java.util.Map.Entry<String, Var> a : this.entrySet()) { if (a.getValue().isSet()) { if (!first) sb.append(','); first = false; sb.append(PREFIX).append(a.getKey()).append("_").append(a.getValue().getCount()); } } return sb.append("]").toString(); } public String toStack() { StringBuilder sb = new StringBuilder(); sb.append("[O,DC"); for (Map.Entry<String, Var> a : this.entrySet()) { Var v = a.getValue(); if (!v.isSet() || !v.hasReferences()) { continue; } sb.append(","); sb.append(PREFIX).append(a.getKey()).append("_").append(v.getCount()); } sb.append("|lists:map(fun({_, X}) -> X end, maps:to_list(get(vars))) ++ Stack]"); return sb.toString(); } /** * Removes all vars,which are not contained in vars */ public void retainAll(Vars vars) { for (String k : Sets.difference(this.keySet(), vars.keySet()).immutableCopy()) remove(k); } /** * Merges multiple Var instance in that one, so counters of variables are * set to maximum. To have this variables also bound, in each branch, this * method will return necessary code lines which set this variables * */ public List<String> merge(List<Vars> vars) { List<StringBuilder> mergeLines = new ArrayList<StringBuilder>(vars.size()); for (int i = 0; i < vars.size(); i++) mergeLines.add(new StringBuilder(",")); Set<String> used = new HashSet<String>(); for (java.util.Map.Entry<String, Var> a : this.entrySet()) { if (a.getValue().isSet()) { used.add(a.getKey()); Var max = Var.max(vars, a.getKey()); Iterator<Vars> itV = vars.iterator(); Iterator<StringBuilder> itM = mergeLines.iterator(); while (itV.hasNext()) { Var v = itV.next().get((Object) a.getKey()); if (v.getCount() < max.getCount()) itM.next() .append(String.format("V_%s_%s=V_%s_%s,", a.getKey(), max.getCount(), a.getKey(), v.getCount())); else itM.next(); } a.setValue(new Var(max.getCount(), true, a.getValue().hasReferences())); } } // Set temp to max for (Vars v : vars) temp = Math.max(temp, v.temp); // Hide all variables, which are not used in this instance Map<String, Boolean> allVars = new HashMap<String, Boolean>(); for (Vars vs : vars) { for (Map.Entry<String, Var> v : vs.entrySet()) { if (!allVars.containsKey(v.getKey()) || !allVars.get(v.getKey())) { allVars.put(v.getKey(), v.getValue().hasReferences()); } } } for (String k : Sets.difference(allVars.keySet(), used)) this.put(k, new Var(Var.max(vars, k).getCount(), false, allVars.get(k))); // Now that we know all vars across all branches, we know whether they may block for (Map.Entry<String, Var> v : entrySet()) { boolean canBlock = false; for (Vars vs : vars) { if (vs.containsKey(v.getKey())) { canBlock |= vs.get((Object) v.getKey()).canBlock(); } } if (!canBlock) { v.setValue(v.getValue().await()); } } // Built return val List<String> res = new ArrayList<String>(vars.size()); for (StringBuilder sb : mergeLines) { sb.deleteCharAt(sb.length() - 1); res.add(sb.toString()); } return res; } /** * Simplified version of merge */ public void hideIntroduced(Vars child) { for (java.util.Map.Entry<String, Var> k : child.entrySet()) if (!containsKey(k.getKey()) || !k.getValue().isSet()) this.put(k.getKey(), new Var(k.getValue().getCount(), false, k.getValue().hasReferences(), k.getValue().canBlock())); temp = Math.max(temp, child.temp); } public void updateTemp(Vars child) { temp = Math.max(temp, child.temp); } } /** * Variable tracking information */ class Var { static Var max(Collection<Vars> varsC, String name) { Var max = null; for (Vars vars : varsC) if (vars.containsKey(name) && (max == null ? -1 : max.getCount()) < vars.get((Object) name).getCount()) max = vars.get((Object) name); return max; } private final int count; // False if it was used in an previous scope, so it has to be remembered but // not used private final boolean set; private final boolean hasReferences; private final boolean canBlock; public Var(int count, boolean set, boolean hasReferences, boolean canBlock) { this.count = count; this.set = set; this.hasReferences = hasReferences; this.canBlock = canBlock; } public Var(int count, boolean set, boolean hasReferences) { this(count, set, hasReferences, true); } public Var(boolean hasReferences) { this(0, true, hasReferences, true); } public Var await() { return new Var(count, set, hasReferences, false); } public Var inc() { return new Var(count + 1, true, hasReferences); } public int getCount() { return count; } public boolean isSet() { return set; } public boolean hasReferences() { return hasReferences; } public boolean canBlock() { return canBlock; } }