/* 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.ast;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import edu.mit.csail.sdg.alloy4.Err;
import edu.mit.csail.sdg.alloy4.Pos;
import edu.mit.csail.sdg.alloy4.ConstList;
import edu.mit.csail.sdg.alloy4.Util;
import edu.mit.csail.sdg.alloy4.ConstList.TempList;
import edu.mit.csail.sdg.alloy4.ErrorSyntax;
/** Immutable; reresents a "run" or "check" command.
*
* <p> <b>Invariant:</b> expects == -1, 0, or 1
* <p> <b>Invariant:</b> overall >= -1
* <p> <b>Invariant:</b> bitwidth >= -1
* <p> <b>Invariant:</b> maxseq >= -1
* <p> <b>Invariant:</b> maxstring >= -1
*/
public final class Command extends Browsable {
/** If nonnull, it means this command depends on this parent command. */
public final Command parent;
/** The position in the original file where this command was declared; never null. */
public final Pos pos;
/** The label for the command; it is just for pretty-printing and does not have to be unique. */
public final String label;
/** true if this is a "check"; false if this is a "run". */
public final boolean check;
/** The overall scope (0 or higher) (Or -1 if there is no overall scope). */
public final int overall;
/** The integer bitwidth (0 or higher) (Or -1 if it was not specified). */
public final int bitwidth;
/** The maximum sequence length (0 or higher) (Or -1 if it was not specified). */
public final int maxseq;
/** The number of String atoms to allocate (0 or higher) (Or -1 if it was not specified). */
public final int maxstring;
/** The expected answer (either 0 or 1) (Or -1 if there is no expected answer). */
public final int expects;
/** The formula associated with this command. */
public final Expr formula;
/** The list of scopes. */
public final ConstList<CommandScope> scope;
/** This stores a list of Sig whose scope shall be considered "exact", but we may or may not know what its scope is yet. */
public final ConstList<Sig> additionalExactScopes;
/** Returns a human-readable string that summarizes this Run or Check command. */
@Override public final String toString() {
if (parent!=null) { Command p=parent; while(p.parent!=null) p=p.parent; return p.toString(); }
boolean first=true;
StringBuilder sb=new StringBuilder(check?"Check ":"Run ").append(label);
if (overall>=0 && (bitwidth>=0 || maxseq>=0 || scope.size()>0))
sb.append(" for ").append(overall).append(" but");
else if (overall>=0)
sb.append(" for ").append(overall);
else if (bitwidth>=0 || maxseq>=0 || scope.size()>0)
sb.append(" for");
if (bitwidth>=0) { sb.append(" ").append(bitwidth).append(" int"); first=false; }
if (maxseq>=0) { sb.append(first?" ":", ").append(maxseq).append(" seq"); first=false; }
for(CommandScope e:scope) {
sb.append(first?" ":", ").append(e);
first=false;
}
if (expects>=0) sb.append(" expect ").append(expects);
return sb.toString();
}
/** Constructs a new Command object.
*
* @param check - true if this is a "check"; false if this is a "run"
* @param overall - the overall scope (0 or higher) (-1 if no overall scope was specified)
* @param bitwidth - the integer bitwidth (0 or higher) (-1 if it was not specified)
* @param maxseq - the maximum sequence length (0 or higher) (-1 if it was not specified)
* @param formula - the formula that must be satisfied by this command
*/
public Command(boolean check, int overall, int bitwidth, int maxseq, Expr formula) throws ErrorSyntax {
this(null, "", check, overall, bitwidth, maxseq, -1, null, null, formula, null);
}
/** Constructs a new Command object.
*
* @param pos - the original position in the file (must not be null)
* @param label - the label for this command (it is only for pretty-printing and does not have to be unique)
* @param check - true if this is a "check"; false if this is a "run"
* @param overall - the overall scope (0 or higher) (-1 if no overall scope was specified)
* @param bitwidth - the integer bitwidth (0 or higher) (-1 if it was not specified)
* @param maxseq - the maximum sequence length (0 or higher) (-1 if it was not specified)
* @param expects - the expected value (0 or 1) (-1 if no expectation was specified)
* @param scope - a list of scopes (can be null if we want to use default)
* @param additionalExactSig - a list of sigs whose scope shall be considered exact though we may or may not know what the scope is yet
* @param formula - the formula that must be satisfied by this command
*/
public Command(Pos pos, String label, boolean check, int overall, int bitwidth, int maxseq, int expects, Iterable<CommandScope> scope, Iterable<Sig> additionalExactSig, Expr formula, Command parent) {
if (pos==null) pos = Pos.UNKNOWN;
this.formula = formula;
this.pos = pos;
this.label = (label==null ? "" : label);
this.check = check;
this.overall = (overall<0 ? -1 : overall);
this.bitwidth = (bitwidth<0 ? -1 : bitwidth);
this.maxseq = (maxseq<0 ? -1 : maxseq);
this.maxstring = (-1);
this.expects = (expects<0 ? -1 : (expects>0 ? 1 : 0));
this.scope = ConstList.make(scope);
this.additionalExactScopes = ConstList.make(additionalExactSig);
this.parent = parent;
}
/** Constructs a new Command object where it is the same as the current object, except with a different formula. */
public Command change(Expr newFormula) {
return new Command(pos, label, check, overall, bitwidth, maxseq, expects, scope, additionalExactScopes, newFormula, parent);
}
/** Constructs a new Command object where it is the same as the current object, except with a different scope. */
public Command change(ConstList<CommandScope> scope) {
return new Command(pos, label, check, overall, bitwidth, maxseq, expects, scope, additionalExactScopes, formula, parent);
}
/** Constructs a new Command object where it is the same as the current object, except with a different list of "additional exact sigs". */
public Command change(Sig... additionalExactScopes) {
return new Command(pos, label, check, overall, bitwidth, maxseq, expects, scope, Util.asList(additionalExactScopes), formula, parent);
}
/** Constructs a new Command object where it is the same as the current object, except with a different scope for the given sig. */
public Command change(Sig sig, boolean isExact, int newScope) throws ErrorSyntax { return change(sig, isExact, newScope, newScope, 1); }
/** Constructs a new Command object where it is the same as the current object, except with a different scope for the given sig. */
public Command change(Sig sig, boolean isExact, int startingScope, int endingScope, int increment) throws ErrorSyntax {
for(int i=0; i<scope.size(); i++) if (scope.get(i).sig == sig) {
CommandScope sc = new CommandScope(scope.get(i).pos, sig, isExact, startingScope, endingScope, increment);
return change(new TempList<CommandScope>(scope).set(i, sc).makeConst());
}
CommandScope sc = new CommandScope(Pos.UNKNOWN, sig, isExact, startingScope, endingScope, increment);
return change(Util.append(scope, sc));
}
/** Helper method that returns the scope corresponding to a given sig (or return null if the sig isn't named in this command) */
public CommandScope getScope(Sig sig) {
for(int i=0; i<scope.size(); i++) if (scope.get(i).sig==sig) return scope.get(i);
return null;
}
/** Helper method that returns true iff this command contains at least one growable sig. */
public ConstList<Sig> getGrowableSigs() {
TempList<Sig> answer = new TempList<Sig>();
for(CommandScope sc: scope) if (sc.startingScope != sc.endingScope) answer.add(sc.sig);
return answer.makeConst();
}
/** Return a modifiable copy of the set of all String constants used in this command or in any facts embedded in this command. */
public Set<String> getAllStringConstants(Iterable<Sig> sigs) throws Err {
final Set<String> set = new HashSet<String>();
final VisitQuery<Object> findString = new VisitQuery<Object>() {
@Override public final Object visit(ExprConstant x) throws Err {
if (x.op==ExprConstant.Op.STRING) set.add(x.string);
return null;
}
};
for(Command c=this; c!=null; c=c.parent) c.formula.accept(findString);
for(Sig s: sigs) {
for(Expr e: s.getFacts()) e.accept(findString);
for(Decl d: s.getFieldDecls()) d.expr.accept(findString);
}
return set;
}
/** {@inheritDoc} */
@Override public final Pos pos() { return pos; }
/** {@inheritDoc} */
@Override public final Pos span() { return pos; }
/** {@inheritDoc} */
@Override public String getHTML() { return (check?"<b>check</b> ":"<b>run</b> ") + label; }
/** {@inheritDoc} */
@Override public List<? extends Browsable> getSubnodes() { return formula==null ? (new ArrayList<Browsable>(0)) : Util.asList(formula); }
}