/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package edu.mit.csail.sdg.alloy4compiler.translator;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import edu.mit.csail.sdg.alloy4.A4Reporter;
import edu.mit.csail.sdg.alloy4.ConstList;
import edu.mit.csail.sdg.alloy4.Err;
import edu.mit.csail.sdg.alloy4.ErrorAPI;
import edu.mit.csail.sdg.alloy4.ErrorFatal;
import edu.mit.csail.sdg.alloy4.Util;
import edu.mit.csail.sdg.alloy4.Version;
import edu.mit.csail.sdg.alloy4compiler.ast.Expr;
import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar;
import edu.mit.csail.sdg.alloy4compiler.ast.Func;
import edu.mit.csail.sdg.alloy4compiler.ast.Sig;
import edu.mit.csail.sdg.alloy4compiler.ast.Type;
import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field;
import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig;
import edu.mit.csail.sdg.alloy4compiler.ast.Sig.SubsetSig;
/** This helper class contains helper routines for writing an A4Solution object out as an XML file. */
public final class A4SolutionWriter {
/** Maps each Sig, Field, and Skolem to a unique id. */
private final IdentityHashMap<Expr,String> map = new IdentityHashMap<Expr,String>();
/** This is the solution we're writing out. */
private final A4Solution sol;
/** This is the A4Reporter that we're sending diagnostic messages to; can be null if none. */
private final A4Reporter rep;
/** This is the list of toplevel sigs. */
private final List<PrimSig> toplevels = new ArrayList<PrimSig>();
/** This is the output file. */
private final PrintWriter out;
/** Helper method that returns a unique id for the given Sig, Field, or Skolem. */
private String map(Expr obj) {
String id = map.get(obj);
if (id==null) { id=Integer.toString(map.size()); map.put(obj, id); }
return id;
}
/** Helper method that returns the list of direct subsignatures. */
private Iterable<PrimSig> children(PrimSig x) throws Err {
if (x==Sig.NONE) return new ArrayList<PrimSig>();
if (x!=Sig.UNIV) return x.children(); else return toplevels;
}
/** Write the given Expr and its Type. */
private boolean writeExpr(String prefix, Expr expr) throws Err {
Type type = expr.type();
if (!type.hasTuple()) return false;
if (sol!=null) {
// Check to see if the tupleset is *really* fully contained inside "type".
// If not, then grow "type" until the tupleset is fully contained inside "type"
Expr sum = type.toExpr();
int lastSize = (-1);
while(true) {
A4TupleSet ts = (A4TupleSet)(sol.eval(expr.minus(sum)));
int n = ts.size();
if (n<=0) break;
if (lastSize>0 && lastSize<=n) throw new ErrorFatal("An internal error occurred in the evaluator.");
lastSize=n;
Type extra = ts.iterator().next().type();
type = type.merge(extra);
sum = sum.plus(extra.toExpr());
}
// Now, write out the tupleset
A4TupleSet ts = (A4TupleSet)(sol.eval(expr));
for(A4Tuple t: ts) {
if (prefix.length()>0) { out.print(prefix); prefix=""; }
out.print(" <tuple>");
for(int i=0; i<t.arity(); i++) Util.encodeXMLs(out, " <atom label=\"", t.atom(i), "\"/>");
out.print(" </tuple>\n");
}
}
// Now, write out the type
if (prefix.length()>0) return false;
for(List<PrimSig> ps: type.fold()) {
out.print(" <types>");
for(PrimSig sig: ps) Util.encodeXMLs(out, " <type ID=\"", map(sig), "\"/>");
out.print(" </types>\n");
}
return true;
}
/** Write the given Sig. */
private A4TupleSet writesig(final Sig x) throws Err {
A4TupleSet ts = null, ts2 = null;
if (x==Sig.NONE) return null; // should not happen, but we test for it anyway
if (sol==null && x.isMeta!=null) return null; // When writing the metamodel, skip the metamodel sigs!
if (x instanceof PrimSig) for(final PrimSig sub:children((PrimSig)x)) {
A4TupleSet ts3 = writesig(sub);
if (ts2==null) ts2 = ts3; else ts2 = ts2.plus(ts3);
}
if (rep!=null) rep.write(x);
Util.encodeXMLs(out, "\n<sig label=\"", x.label, "\" ID=\"", map(x));
if (x instanceof PrimSig && x!=Sig.UNIV) Util.encodeXMLs(out, "\" parentID=\"", map(((PrimSig)x).parent));
if (x.builtin) out.print("\" builtin=\"yes");
if (x.isAbstract!=null) out.print("\" abstract=\"yes");
if (x.isOne!=null) out.print("\" one=\"yes");
if (x.isLone!=null) out.print("\" lone=\"yes");
if (x.isSome!=null) out.print("\" some=\"yes");
if (x.isPrivate!=null) out.print("\" private=\"yes");
if (x.isMeta!=null) out.print("\" meta=\"yes");
if (x instanceof SubsetSig && ((SubsetSig)x).exact) out.print("\" exact=\"yes");
if (x.isEnum!=null) out.print("\" enum=\"yes");
out.print("\">\n");
try {
if (sol!=null && x!=Sig.UNIV && x!=Sig.SIGINT && x!=Sig.SEQIDX) {
ts = (A4TupleSet)(sol.eval(x));
for(A4Tuple t: ts.minus(ts2)) Util.encodeXMLs(out, " <atom label=\"", t.atom(0), "\"/>\n");
}
} catch(Throwable ex) {
throw new ErrorFatal("Error evaluating sig " + x.label, ex);
}
if (x instanceof SubsetSig) for(Sig p:((SubsetSig)x).parents) Util.encodeXMLs(out, " <type ID=\"", map(p), "\"/>\n");
out.print("</sig>\n");
for(Field field: x.getFields()) writeField(field);
return ts;
}
/** Write the given Field. */
private void writeField(Field x) throws Err {
try {
if (sol==null && x.isMeta!=null) return; // when writing the metamodel, skip the metamodel fields!
if (x.type().hasNoTuple()) return; // we do not allow "none" in the XML file's type declarations
if (rep!=null) rep.write(x);
Util.encodeXMLs(out, "\n<field label=\"", x.label, "\" ID=\"", map(x), "\" parentID=\"", map(x.sig));
if (x.isPrivate!=null) out.print("\" private=\"yes");
if (x.isMeta!=null) out.print("\" meta=\"yes");
out.print("\">\n");
writeExpr("", x);
out.print("</field>\n");
} catch(Throwable ex) {
throw new ErrorFatal("Error evaluating field "+x.sig.label+"."+x.label, ex);
}
}
/** Write the given Skolem. */
private void writeSkolem(ExprVar x) throws Err {
try {
if (sol==null) return; // when writing a metamodel, skip the skolems
if (x.type().hasNoTuple()) return; // we do not allow "none" in the XML file's type declarations
StringBuilder sb = new StringBuilder();
Util.encodeXMLs(sb, "\n<skolem label=\"", x.label, "\" ID=\"", map(x), "\">\n");
if (writeExpr(sb.toString(), x)) { out.print("</skolem>\n"); }
} catch(Throwable ex) {
throw new ErrorFatal("Error evaluating skolem "+x.label, ex);
}
}
/** If sol==null, write the list of Sigs as a Metamodel, else write the solution as an XML file. */
private A4SolutionWriter(A4Reporter rep, A4Solution sol, Iterable<Sig> sigs, int bitwidth, int maxseq, String originalCommand, String originalFileName, PrintWriter out, Iterable<Func> extraSkolems) throws Err {
this.rep = rep;
this.out = out;
this.sol = sol;
for (Sig s:sigs) if (s instanceof PrimSig && ((PrimSig)s).parent==Sig.UNIV) toplevels.add((PrimSig)s);
out.print("<instance bitwidth=\""); out.print(bitwidth);
out.print("\" maxseq=\""); out.print(maxseq);
out.print("\" command=\""); Util.encodeXML(out, originalCommand);
out.print("\" filename=\""); Util.encodeXML(out, originalFileName);
if (sol==null) out.print("\" metamodel=\"yes");
out.print("\">\n");
writesig(Sig.UNIV);
for (Sig s:sigs) if (s instanceof SubsetSig) writesig(s);
if (sol!=null) for (ExprVar s:sol.getAllSkolems()) { if (rep!=null) rep.write(s); writeSkolem(s); }
int m=0;
if (sol!=null && extraSkolems!=null) for(Func f:extraSkolems) if (f.count()==0 && f.call().type().hasTuple()) {
String label = f.label;
while(label.length()>0 && label.charAt(0)=='$') label=label.substring(1);
label="$"+label;
try {
if (rep!=null) rep.write(f.call());
StringBuilder sb = new StringBuilder();
Util.encodeXMLs(sb, "\n<skolem label=\"", label, "\" ID=\"m"+m+"\">\n");
if (writeExpr(sb.toString(), f.call())) { out.print("</skolem>\n"); }
m++;
} catch(Throwable ex) {
throw new ErrorFatal("Error evaluating skolem "+label, ex);
}
}
out.print("\n</instance>\n");
}
/** If this solution is a satisfiable solution, this method will write it out in XML format. */
static void writeInstance(A4Reporter rep, A4Solution sol, PrintWriter out, Iterable<Func> extraSkolems, Map<String,String> sources) throws Err {
if (!sol.satisfiable()) throw new ErrorAPI("This solution is unsatisfiable.");
try {
Util.encodeXMLs(out, "<alloy builddate=\"", Version.buildDate(), "\">\n\n");
new A4SolutionWriter(rep, sol, sol.getAllReachableSigs(), sol.getBitwidth(), sol.getMaxSeq(), sol.getOriginalCommand(), sol.getOriginalFilename(), out, extraSkolems);
if (sources!=null) for(Map.Entry<String,String> e: sources.entrySet()) {
Util.encodeXMLs(out, "\n<source filename=\"", e.getKey(), "\" content=\"", e.getValue(), "\"/>\n");
}
out.print("\n</alloy>\n");
} catch(Throwable ex) {
if (ex instanceof Err) throw (Err)ex; else throw new ErrorFatal("Error writing the solution XML file.", ex);
}
if (out.checkError()) throw new ErrorFatal("Error writing the solution XML file.");
}
/** Write the metamodel as <instance>..</instance> in XML format. */
public static void writeMetamodel(ConstList<Sig> sigs, String originalFilename, PrintWriter out) throws Err {
try {
new A4SolutionWriter(null, null, sigs, 4, 4, "show metamodel", originalFilename, out, null);
} catch(Throwable ex) {
if (ex instanceof Err) throw (Err)ex; else throw new ErrorFatal("Error writing the solution XML file.", ex);
}
if (out.checkError()) throw new ErrorFatal("Error writing the solution XML file.");
}
}