/* 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 static edu.mit.csail.sdg.alloy4compiler.ast.Sig.UNIV; import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.SIGINT; import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.SEQIDX; import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.STRING; import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.NONE; import java.io.IOException; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import kodkod.ast.Relation; import kodkod.instance.Tuple; import kodkod.instance.TupleFactory; import kodkod.instance.TupleSet; import edu.mit.csail.sdg.alloy4.Err; import edu.mit.csail.sdg.alloy4.ErrorFatal; import edu.mit.csail.sdg.alloy4.ErrorSyntax; import edu.mit.csail.sdg.alloy4.OurUtil; import edu.mit.csail.sdg.alloy4.Pos; import edu.mit.csail.sdg.alloy4.XMLNode; import edu.mit.csail.sdg.alloy4.Util; import edu.mit.csail.sdg.alloy4compiler.ast.Attr; import edu.mit.csail.sdg.alloy4compiler.ast.Expr; import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar; import edu.mit.csail.sdg.alloy4compiler.ast.Sig; 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 reading an A4Solution object from an XML file. */ public final class A4SolutionReader { /** The resulting A4Solution object. */ private final A4Solution sol; /** The provided choices of existing Sig and Field. */ private final LinkedHashSet<Expr> choices = new LinkedHashSet<Expr>(); /** Stores the set of atoms. */ private final TreeSet<String> atoms = new TreeSet<String>(); /** Stores the set of STRING atoms. */ private final TreeSet<String> strings = new TreeSet<String>(); /** Maps each Sig/Field/Skolem id to an XMLNode. */ private final Map<String,XMLNode> nmap = new LinkedHashMap<String,XMLNode>(); /** Maps each Sig id to a Sig. */ private final Map<String,Sig> id2sig = new LinkedHashMap<String,Sig>(); /** Stores the set of all sigs. */ private final Set<Sig> allsigs = Util.asSet((Sig)UNIV, SIGINT, SEQIDX, STRING, NONE); /** Mapes each expression we've seen to its TupleSet. */ private final Map<Expr,TupleSet> expr2ts = new LinkedHashMap<Expr,TupleSet>(); /** The Kodkod tupleset factory. */ private final TupleFactory factory; /** Helper method that returns true if the given attribute value in the given XML node is equal to "yes" */ private static boolean yes(XMLNode node, String attr) { return node.getAttribute(attr).equals("yes"); } /** Helper method that returns an XML node's "label" attribute. */ private static String label(XMLNode node) { return node.getAttribute("label"); } /** Helper method that returns true if the two iterables contain the same elements (though possibly in different order) */ private static boolean sameset(Iterable<Sig> a, Iterable<Sig> b) { ArrayList<Sig> tmp=new ArrayList<Sig>(); for(Sig x:b) tmp.add(x); for(Sig x:a) if (tmp.contains(x)) tmp.remove(x); else return false; return tmp.isEmpty(); } /** Parse tuple. */ private Tuple parseTuple(XMLNode tuple, int arity) throws Err { Tuple ans = null; try { for(XMLNode sub:tuple) if (sub.is("atom")) { Tuple x = factory.tuple(sub.getAttribute("label")); if (ans==null) ans=x; else ans=ans.product(x); } if (ans==null) throw new ErrorFatal("Expecting: <tuple> <atom label=\"..\"/> .. </tuple>"); if (ans.arity()!=arity) throw new ErrorFatal("Expecting: tuple of arity "+arity+" but got tuple of arity "+ans.arity()); return ans; } catch(Throwable ex) { throw new ErrorFatal("Expecting: <tuple> <atom label=\"..\"/> .. </tuple>", ex); } } /** Parse tuples. */ private TupleSet parseTuples(XMLNode tuples, int arity) throws Err { TupleSet ans = factory.noneOf(arity); for(XMLNode sub:tuples) if (sub.is("tuple")) ans.add(parseTuple(sub, arity)); return ans; } /** Parse sig/set. */ private Sig parseSig(String id, int depth) throws IOException, Err { Sig ans = id2sig.get(id); if (ans!=null) return ans; XMLNode node = nmap.get(id); if (node==null) throw new IOException("Unknown SigID "+id+" encountered."); if (!node.is("sig")) throw new IOException("ID "+id+" is not a sig."); String label = label(node); Attr isAbstract = yes(node,"abstract") ? Attr.ABSTRACT : null; Attr isOne = yes(node,"one") ? Attr.ONE : null; Attr isLone = yes(node,"lone") ? Attr.LONE : null; Attr isSome = yes(node,"some") ? Attr.SOME : null; Attr isPrivate = yes(node,"private") ? Attr.PRIVATE : null; Attr isMeta = yes(node,"meta") ? Attr.META : null; Attr isEnum = yes(node,"enum") ? Attr.ENUM : null; Attr isExact = yes(node,"exact") ? Attr.EXACT : null; if (yes(node,"builtin")) { if (label.equals(UNIV.label)) { id2sig.put(id, UNIV); return UNIV; } if (label.equals(SIGINT.label)) { id2sig.put(id, SIGINT); return SIGINT; } if (label.equals(SEQIDX.label)) { id2sig.put(id, SEQIDX); return SEQIDX; } if (label.equals(STRING.label)) { id2sig.put(id, STRING); return STRING; } throw new IOException("Unknown builtin sig: "+label+" (id="+id+")"); } if (depth > nmap.size()) throw new IOException("Sig "+label+" (id="+id+") is in a cyclic inheritance relationship."); List<Sig> parents = null; TupleSet ts = factory.noneOf(1); for(XMLNode sub:node) { if (sub.is("atom")) { ts.add(factory.tuple(sub.getAttribute("label"))); continue; } if (!sub.is("type")) continue; Sig parent = parseSig(sub.getAttribute("ID"), depth+1); if (parents==null) parents = new ArrayList<Sig>(); parents.add(parent); } if (parents==null) { String parentID = node.getAttribute("parentID"); Sig parent = parseSig(parentID, depth+1); if (!(parent instanceof PrimSig)) throw new IOException("Parent of sig "+label+" (id="+id+") must not be a subset sig."); for(Expr choice: choices) if (choice instanceof PrimSig && parent==((PrimSig)choice).parent && ((Sig)choice).label.equals(label)) { ans=(Sig)choice; choices.remove(choice); break; } if (ans==null) { ans = new PrimSig(label, (PrimSig)parent, isAbstract, isLone, isOne, isSome, isPrivate, isMeta, isEnum); allsigs.add(ans); } } else { for(Expr choice:choices) if (choice instanceof SubsetSig && ((Sig)choice).label.equals(label) && sameset(parents, ((SubsetSig)choice).parents)) { ans=(Sig)choice; choices.remove(choice); break; } if (ans==null) { ans = new SubsetSig(label, parents, isExact, isLone, isOne, isSome, isPrivate, isMeta); allsigs.add(ans); } } id2sig.put(id, ans); expr2ts.put(ans, ts); if (ans instanceof PrimSig) { // Add the atoms in this SIG into all parent sigs for(PrimSig ans2 = ((PrimSig)ans).parent; ans2!=null && !ans2.builtin; ans2 = ans2.parent) { TupleSet ts2 = expr2ts.get(ans2); if (ts2==null) ts2 = ts.clone(); else { ts2 = ts2.clone(); ts2.addAll(ts); } expr2ts.put(ans2, ts2); } } return ans; } /** Parse type. */ private Expr parseType(XMLNode node) throws IOException, Err { Expr expr = null; if (!node.is("types")) throw new IOException("<types>...</type> expected"); for(XMLNode n:node) if (n.is("type")) { Sig sig=parseSig(n.getAttribute("ID"), 0); if (expr==null) expr=sig; else expr=expr.product(sig); } if (expr==null) throw new IOException("<type ID=../> expected"); return expr; } /** Parse field. */ private Field parseField(String id) throws IOException, Err { final XMLNode node = nmap.get(id); if (node==null) throw new IOException("Unknown FieldID "+id+" encountered."); if (!node.is("field")) throw new IOException("ID "+id+" is not a field."); String label = label(node); Pos isPrivate = yes(node,"private") ? Pos.UNKNOWN : null; Pos isMeta = yes(node,"meta") ? Pos.UNKNOWN : null; Expr type = null; for(XMLNode sub:node) if (sub.is("types")) { Expr t=parseType(sub); if (type==null) type=t; else type=type.plus(t); } int arity; if (type==null || (arity=type.type().arity())<2) throw new IOException("Field "+label+" is maltyped."); String parentID = node.getAttribute("parentID"); Sig parent = id2sig.get(parentID); if (parent==null) throw new IOException("ID "+parentID+" is not a sig."); Field field = null; for(Field f: parent.getFields()) if (f.label.equals(label) && f.type().arity()==arity && choices.contains(f)) { field=f; choices.remove(f); break; } if (field==null) field = parent.addTrickyField(Pos.UNKNOWN, isPrivate, null, null, isMeta, new String[] {label}, UNIV.join(type)) [0]; TupleSet ts = parseTuples(node, arity); expr2ts.put(field, ts); return field; } /** Parse skolem. */ private ExprVar parseSkolem(String id) throws IOException, Err { final XMLNode node = nmap.get(id); if (node==null) throw new IOException("Unknown ID "+id+" encountered."); if (!node.is("skolem")) throw new IOException("ID "+id+" is not a skolem."); String label = label(node); Expr type = null; for(XMLNode sub:node) if (sub.is("types")) { Expr t=parseType(sub); if (type==null) type=t; else type=type.plus(t); } int arity; if (type==null || (arity=type.type().arity())<1) throw new IOException("Skolem "+label+" is maltyped."); ExprVar var = ExprVar.make(Pos.UNKNOWN, label, type.type()); TupleSet ts = parseTuples(node, arity); expr2ts.put(var, ts); return var; } /** Parse everything. */ private A4SolutionReader(Iterable<Sig> sigs, XMLNode xml) throws IOException, Err { for(Sig s:sigs) if (!s.builtin) { allsigs.add(s); choices.add(s); for(Field f:s.getFields()) choices.add(f); } // find <instance>..</instance> if (!xml.is("alloy")) throw new ErrorSyntax("The XML file's root node must be <alloy> or <instance>."); XMLNode inst = null; for(XMLNode sub: xml) if (sub.is("instance")) { inst=sub; break; } if (inst==null) throw new ErrorSyntax("The XML file must contain an <instance> element."); // set up the basic values of the A4Solution object final int bitwidth = Integer.parseInt(inst.getAttribute("bitwidth")); final int maxseq = Integer.parseInt(inst.getAttribute("maxseq")); final int max = Util.max(bitwidth), min = Util.min(bitwidth); if (bitwidth>=1 && bitwidth<=30) for(int i=min; i<=max; i++) { atoms.add(Integer.toString(i)); } for(XMLNode x:inst) { String id=x.getAttribute("ID"); if (id.length()>0 && (x.is("field") || x.is("skolem") || x.is("sig"))) { if (nmap.put(id, x)!=null) throw new IOException("ID "+id+" is repeated."); if (x.is("sig")) { boolean isString = STRING.label.equals(label(x)) && yes(x, "builtin"); for(XMLNode y:x) if (y.is("atom")) { String attr = y.getAttribute("label"); atoms.add(attr); if (isString) strings.add(attr); } } } } // create the A4Solution object A4Options opt = new A4Options(); opt.originalFilename = inst.getAttribute("filename"); sol = new A4Solution(inst.getAttribute("command"), bitwidth, maxseq, strings, atoms, null, opt, 1); factory = sol.getFactory(); // parse all the sigs, fields, and skolems for(Map.Entry<String,XMLNode> e:nmap.entrySet()) if (e.getValue().is("sig")) parseSig(e.getKey(), 0); for(Map.Entry<String,XMLNode> e:nmap.entrySet()) if (e.getValue().is("field")) parseField(e.getKey()); for(Map.Entry<String,XMLNode> e:nmap.entrySet()) if (e.getValue().is("skolem")) parseSkolem(e.getKey()); for(Sig s:allsigs) if (!s.builtin) { TupleSet ts = expr2ts.remove(s); if (ts==null) ts = factory.noneOf(1); // If the sig was NOT mentioned in the XML file... Relation r = sol.addRel(s.label, ts, ts); sol.addSig(s, r); for(Field f: s.getFields()) { ts = expr2ts.remove(f); if (ts==null) ts=factory.noneOf(f.type().arity()); // If the field was NOT mentioned in the XML file... r = sol.addRel(s.label+"."+f.label, ts, ts); sol.addField(f, r); } } for(Map.Entry<Expr,TupleSet> e:expr2ts.entrySet()) { ExprVar v = (ExprVar)(e.getKey()); TupleSet ts = e.getValue(); Relation r = sol.addRel(v.label, ts, ts); sol.kr2type(r, v.type()); } // Done! sol.solve(null, null, null, false); } /** Parse the XML element into an AlloyInstance. * * <p> The list of sigs, if not null, will be used as the sigs (and their fields) that we expect to exist; * <br> if there's a sig or field X in the list but not in the XML, then X's tupleset will be regarded as empty; * <br> if there's a sig or field X in the XML but not in the list, then X (and its value in XML file) is added to the solution. */ public static A4Solution read(Iterable<Sig> sigs, XMLNode xml) throws Err { try { if (sigs == null) sigs = new ArrayList<Sig>(); A4SolutionReader x = new A4SolutionReader(sigs, xml); return x.sol; } catch(Throwable ex) { if (ex instanceof Err) throw ((Err)ex); else throw new ErrorFatal("Fatal error occured: "+ex, ex); } } }