You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
576 lines
18 KiB
576 lines
18 KiB
//==============================================================================
|
|
//
|
|
// Copyright (c) 2014-
|
|
// Authors:
|
|
// * Joachim Klein <klein@tcs.inf.tu-dresden.de> (TU Dresden)
|
|
// * David Mueller <david.mueller@tcs.inf.tu-dresden.de> (TU Dresden)
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// This file is part of PRISM.
|
|
//
|
|
// PRISM is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; either version 2 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// PRISM is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with PRISM; if not, write to the Free Software Foundation,
|
|
// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
//
|
|
//==============================================================================
|
|
|
|
|
|
package automata;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.BitSet;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
|
|
import prism.PrismException;
|
|
import jhoafparser.ast.AtomAcceptance;
|
|
import jhoafparser.ast.AtomLabel;
|
|
import jhoafparser.ast.BooleanExpression;
|
|
import jhoafparser.consumer.HOAConsumer;
|
|
import jhoafparser.consumer.HOAConsumerException;
|
|
import jhoafparser.util.ImplicitEdgeHelper;
|
|
import jltl2ba.APElement;
|
|
import jltl2ba.APSet;
|
|
import jltl2dstar.APMonom;
|
|
import jltl2dstar.APMonom2APElements;
|
|
import acceptance.AcceptanceGenRabin;
|
|
import acceptance.AcceptanceGenRabin.GenRabinPair;
|
|
import acceptance.AcceptanceGeneric;
|
|
import acceptance.AcceptanceOmega;
|
|
import acceptance.AcceptanceRabin;
|
|
import acceptance.AcceptanceRabin.RabinPair;
|
|
|
|
/**
|
|
* A HOAConsumer for jhoafparser that constructs a prism.DA from the parsed automaton.
|
|
* <br>
|
|
* The automaton has to be deterministic and complete, with state-based acceptance and
|
|
* labels (explicit/implicit) on the edges.
|
|
* <br>
|
|
* If the automaton has transition-based acceptance, {@code TransitionBasedAcceptanceException}
|
|
* is thrown.
|
|
* <br>
|
|
* There are (currently) more restrictions on the automaton:
|
|
* <ul>
|
|
* <li>The Start and States headers have to be present</li>
|
|
* <li>All explicit edge labels have to be in disjunctive normal form (disjunction of conjunctive clauses)</li>
|
|
* </ul>
|
|
*/
|
|
public class HOAF2DA implements HOAConsumer {
|
|
|
|
|
|
/** An exception that is thrown to indicate that the automaton had transition based acceptance. */
|
|
@SuppressWarnings("serial")
|
|
public class TransitionBasedAcceptanceException extends HOAConsumerException {
|
|
public TransitionBasedAcceptanceException(String e) {super(e);}
|
|
}
|
|
|
|
/** The resulting deterministic automaton */
|
|
private DA<BitSet, ? extends AcceptanceOmega> da;
|
|
/** The set of atomic propositions of the automaton (in APSet form) */
|
|
private APSet aps = new APSet();
|
|
|
|
/** Size, i.e. number of states */
|
|
private int size;
|
|
/** Do we know the number of states? Is provided by the optional HOA States-header */
|
|
private boolean knowSize = false;
|
|
|
|
/** Start state (index) */
|
|
private int startState;
|
|
/** Do we know the start state? Is provided by the HOA Start-header */
|
|
private boolean knowStartState = false;
|
|
|
|
/** The acceptance condition */
|
|
private BooleanExpression<AtomAcceptance> accExpr = null;
|
|
/** The condition name from the acc-name header (optional) */
|
|
private String accName;
|
|
/** The extra information from the acc-name header (optional) */
|
|
private List<Object> extraInfo;
|
|
|
|
/** For each acceptance set in the HOA automaton, the set of states that are included in that set */
|
|
private List<BitSet> acceptanceSets = null;
|
|
/** The set of acceptance set indizes where state membership has to be inverted */
|
|
private Set<Integer> negateAcceptanceSetMembership = null;
|
|
/** The list of atomic propositions (in List form) */
|
|
private List<String> apList;
|
|
|
|
/** The helper for handling implicit edges */
|
|
ImplicitEdgeHelper implicitEdgeHelper = null;
|
|
|
|
/** Clear the various state information */
|
|
public void clear() {
|
|
aps = new APSet();
|
|
|
|
implicitEdgeHelper = null;
|
|
|
|
size = 0;
|
|
knowSize = false;
|
|
|
|
startState = 0;
|
|
knowStartState = false;
|
|
|
|
accExpr = null;
|
|
accName = null;
|
|
extraInfo = null;
|
|
|
|
acceptanceSets = null;
|
|
negateAcceptanceSetMembership = null;
|
|
apList = null;
|
|
}
|
|
|
|
/** Constructor */
|
|
public HOAF2DA() {
|
|
}
|
|
|
|
@Override
|
|
public boolean parserResolvesAliases() {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void notifyHeaderStart(String version) throws HOAConsumerException {
|
|
// NOP
|
|
}
|
|
|
|
@Override
|
|
public void setNumberOfStates(int numberOfStates)
|
|
throws HOAConsumerException {
|
|
size = numberOfStates;
|
|
knowSize = true;
|
|
}
|
|
|
|
@Override
|
|
public void addStartStates(List<Integer> stateConjunction)
|
|
throws HOAConsumerException {
|
|
if(stateConjunction.size() > 1 || knowStartState) {
|
|
throw new HOAConsumerException("Not a deterministic automaton: More then one Start state");
|
|
}
|
|
startState = stateConjunction.get(0).intValue();
|
|
knowStartState = true;
|
|
}
|
|
|
|
@Override
|
|
public void addAlias(String name, BooleanExpression<AtomLabel> labelExpr)
|
|
throws HOAConsumerException {
|
|
// NOP, aliases are already resolved
|
|
}
|
|
|
|
@Override
|
|
public void setAPs(List<String> aps) throws HOAConsumerException {
|
|
apList = aps;
|
|
|
|
for (String ap : aps) {
|
|
this.aps.addAP(ap);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setAcceptanceCondition(int numberOfSets,
|
|
BooleanExpression<AtomAcceptance> accExpr)
|
|
throws HOAConsumerException {
|
|
this.accExpr = accExpr;
|
|
}
|
|
|
|
@Override
|
|
public void provideAcceptanceName(String name, List<Object> extraInfo)
|
|
throws HOAConsumerException {
|
|
accName = name;
|
|
this.extraInfo = extraInfo;
|
|
}
|
|
|
|
@Override
|
|
public void setName(String name) throws HOAConsumerException {
|
|
// NOP
|
|
}
|
|
|
|
@Override
|
|
public void setTool(String name, String version) throws HOAConsumerException {
|
|
// NOP
|
|
}
|
|
|
|
@Override
|
|
public void addProperties(List<String> properties)
|
|
throws HOAConsumerException {
|
|
if(!properties.contains("deterministic")) {
|
|
// we don't know yet whether the automaton is actually deterministic...
|
|
}
|
|
if(properties.contains("univ-branch")) {
|
|
throw new HOAConsumerException("A HOAF with universal branching is not deterministic");
|
|
}
|
|
|
|
if(properties.contains("state-labels")) {
|
|
throw new HOAConsumerException("Can't handle state labelling");
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void addMiscHeader(String name, List<Object> content)
|
|
throws HOAConsumerException {
|
|
if (name.substring(0,1).toUpperCase().equals(name.substring(0,1))) {
|
|
throw new HOAConsumerException("Unknown header "+name+" potentially containing semantic information, can not handle");
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void notifyBodyStart() throws HOAConsumerException {
|
|
if (!knowSize) {
|
|
throw new HOAConsumerException("Can currently only parse automata where the number of states is specified in the header");
|
|
}
|
|
if (!knowStartState) {
|
|
throw new HOAConsumerException("Not a deterministic automaton: No initial state specified (Start header)");
|
|
}
|
|
if (startState >= size) {
|
|
throw new HOAConsumerException("Initial state "+startState+" is out of range");
|
|
}
|
|
|
|
da = new DA<BitSet,AcceptanceGeneric>(size);
|
|
da.setStartState(startState);
|
|
|
|
if (apList == null) {
|
|
// no call to setAPs
|
|
apList = new ArrayList<String>(0);
|
|
}
|
|
da.setAPList(apList);
|
|
implicitEdgeHelper = new ImplicitEdgeHelper(apList.size());
|
|
|
|
DA.switchAcceptance(da, prepareAcceptance());
|
|
}
|
|
|
|
/**
|
|
* Prepare an acceptance condition for the parsed automaton.
|
|
* Called in notifyBodyStart()
|
|
**/
|
|
private AcceptanceOmega prepareAcceptance() throws HOAConsumerException
|
|
{
|
|
if (accName != null) {
|
|
if (accName.equals("Rabin")) {
|
|
return prepareAcceptanceRabin();
|
|
} else if (accName.equals("generalized-Rabin")) {
|
|
return prepareAcceptanceGenRabin();
|
|
}
|
|
}
|
|
|
|
acceptanceSets = new ArrayList<BitSet>();
|
|
return prepareAcceptanceGeneric(accExpr);
|
|
}
|
|
|
|
/**
|
|
* Prepare a generic acceptance condition for the parsed automaton.
|
|
**/
|
|
private AcceptanceGeneric prepareAcceptanceGeneric(BooleanExpression<AtomAcceptance> expr) throws HOAConsumerException
|
|
{
|
|
switch (expr.getType()) {
|
|
case EXP_TRUE:
|
|
return new AcceptanceGeneric(true);
|
|
case EXP_FALSE:
|
|
return new AcceptanceGeneric(false);
|
|
case EXP_AND:
|
|
return new AcceptanceGeneric(AcceptanceGeneric.ElementType.AND,
|
|
prepareAcceptanceGeneric(expr.getLeft()),
|
|
prepareAcceptanceGeneric(expr.getRight()));
|
|
case EXP_OR:
|
|
return new AcceptanceGeneric(AcceptanceGeneric.ElementType.OR,
|
|
prepareAcceptanceGeneric(expr.getLeft()),
|
|
prepareAcceptanceGeneric(expr.getRight()));
|
|
case EXP_NOT:
|
|
throw new HOAConsumerException("Boolean negation not allowed in acceptance expression");
|
|
case EXP_ATOM: {
|
|
int index = expr.getAtom().getAcceptanceSet();
|
|
while (index >= acceptanceSets.size()) {
|
|
// ensure that the acceptanceSets array is large enough
|
|
acceptanceSets.add(null);
|
|
}
|
|
if (acceptanceSets.get(index) == null) {
|
|
// this acceptance set index has not been seen yet, create BitSet
|
|
acceptanceSets.set(index, new BitSet());
|
|
}
|
|
BitSet acceptanceSet = acceptanceSets.get(index);
|
|
switch (expr.getAtom().getType()) {
|
|
case TEMPORAL_FIN:
|
|
if (expr.getAtom().isNegated()) {
|
|
return new AcceptanceGeneric(AcceptanceGeneric.ElementType.FIN_NOT, acceptanceSet);
|
|
} else {
|
|
return new AcceptanceGeneric(AcceptanceGeneric.ElementType.FIN, acceptanceSet);
|
|
}
|
|
case TEMPORAL_INF:
|
|
if (expr.getAtom().isNegated()) {
|
|
return new AcceptanceGeneric(AcceptanceGeneric.ElementType.INF_NOT, acceptanceSet);
|
|
} else {
|
|
return new AcceptanceGeneric(AcceptanceGeneric.ElementType.INF, acceptanceSet);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new UnsupportedOperationException("Unknown operator in acceptance condition: "+expr);
|
|
}
|
|
|
|
/**
|
|
* Prepare a Rabin acceptance condition from the acc-name header.
|
|
*/
|
|
private AcceptanceRabin prepareAcceptanceRabin() throws HOAConsumerException
|
|
{
|
|
if (extraInfo.size() != 1 ||
|
|
!(extraInfo.get(0) instanceof Integer)) {
|
|
throw new HOAConsumerException("Invalid acc-name: Rabin header");
|
|
}
|
|
|
|
int numberOfPairs = (Integer)extraInfo.get(0);
|
|
AcceptanceRabin acceptanceRabin = new AcceptanceRabin();
|
|
acceptanceSets = new ArrayList<BitSet>(numberOfPairs*2);
|
|
for (int i = 0; i< numberOfPairs; i++) {
|
|
BitSet L = new BitSet();
|
|
BitSet K = new BitSet();
|
|
|
|
acceptanceSets.add(L); // 2*i = Fin(L) = F G !L
|
|
acceptanceSets.add(K); // 2*i+1 = Inf(K) = G F K
|
|
|
|
acceptanceRabin.add(new RabinPair(L,K));
|
|
}
|
|
|
|
return acceptanceRabin;
|
|
}
|
|
|
|
/**
|
|
* Prepare a Generalized Rabin acceptance condition from the acc-name header.
|
|
*/
|
|
private AcceptanceGenRabin prepareAcceptanceGenRabin() throws HOAConsumerException
|
|
{
|
|
if (extraInfo.size() < 1 ||
|
|
!(extraInfo.get(0) instanceof Integer)) {
|
|
throw new HOAConsumerException("Invalid acc-name: generalized-Rabin header");
|
|
}
|
|
|
|
int numberOfPairs = (Integer)extraInfo.get(0);
|
|
if (extraInfo.size() != numberOfPairs + 1) {
|
|
throw new HOAConsumerException("Invalid acc-name: generalized-Rabin header");
|
|
}
|
|
int numberOfKs[] = new int[numberOfPairs];
|
|
for (int i = 0; i < numberOfPairs; i++) {
|
|
if (!(extraInfo.get(i + 1) instanceof Integer)) {
|
|
throw new HOAConsumerException("Invalid acc-name: generalized-Rabin header");
|
|
}
|
|
numberOfKs[i] = (Integer) extraInfo.get(i + 1);
|
|
}
|
|
|
|
AcceptanceGenRabin acceptanceGenRabin = new AcceptanceGenRabin();
|
|
acceptanceSets = new ArrayList<BitSet>(numberOfPairs*2);
|
|
for (int i = 0; i< numberOfPairs; i++) {
|
|
BitSet L = new BitSet();
|
|
acceptanceSets.add(L); // Fin(L) = F G !L
|
|
ArrayList<BitSet> K_list = new ArrayList<BitSet>();
|
|
for (int j = 0; j < numberOfKs[i]; j++) {
|
|
BitSet K_j = new BitSet();
|
|
K_list.add(K_j);
|
|
acceptanceSets.add(K_j); // Inf(K_j) = G F K_j
|
|
}
|
|
acceptanceGenRabin.add(new GenRabinPair(L, K_list));
|
|
}
|
|
|
|
return acceptanceGenRabin;
|
|
}
|
|
|
|
@Override
|
|
public void addState(int id, String info,
|
|
BooleanExpression<AtomLabel> labelExpr, List<Integer> accSignature)
|
|
throws HOAConsumerException {
|
|
implicitEdgeHelper.startOfState(id);
|
|
|
|
if(labelExpr != null) {
|
|
throw new HOAConsumerException("State "+id+" has a state label, currently only supports labels on transitions");
|
|
}
|
|
|
|
if (id >= size) {
|
|
throw new HOAConsumerException("Illegal state index "+id+", out of range");
|
|
}
|
|
|
|
if (accSignature != null) {
|
|
for (int index : accSignature) {
|
|
if (index >= acceptanceSets.size()) {
|
|
// acceptance set index not used in acceptance condition, ignore
|
|
}
|
|
BitSet accSet = acceptanceSets.get(index);
|
|
if (accSet == null) {
|
|
// acceptance set index not used in acceptance condition, ignore
|
|
} else {
|
|
accSet.set(id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void addEdgeImplicit(int stateId, List<Integer> conjSuccessors,
|
|
List<Integer> accSignature) throws HOAConsumerException {
|
|
if (conjSuccessors.size() != 1) {
|
|
throw new HOAConsumerException("Not a DA, state "+stateId+" has transition with conjunctive target");
|
|
}
|
|
|
|
if (accSignature != null) {
|
|
throw new TransitionBasedAcceptanceException("DA has transition-based acceptance (state "+stateId+", currently only state-labeled acceptance is supported");
|
|
}
|
|
|
|
int to = conjSuccessors.get(0);
|
|
|
|
BitSet edge = new BitSet();
|
|
long tmp = implicitEdgeHelper.nextImplicitEdge();
|
|
int index = 0;
|
|
while (tmp != 0) {
|
|
if (tmp % 2 == 1) {
|
|
edge.set(index);
|
|
}
|
|
tmp = tmp >> 1L;
|
|
index++;
|
|
}
|
|
da.addEdge(stateId, edge, to);
|
|
}
|
|
|
|
/**
|
|
* Returns a list of APMonoms for the expression. The expression currently has to be in
|
|
* disjunctive normal form. Returns one APMonom for each clause of the DNF.
|
|
*/
|
|
private List<APMonom> labelExpressionToAPMonom(BooleanExpression<AtomLabel> expr) throws HOAConsumerException {
|
|
List<APMonom> result = new ArrayList<APMonom>();
|
|
|
|
switch (expr.getType()) {
|
|
case EXP_AND:
|
|
case EXP_ATOM:
|
|
case EXP_NOT: {
|
|
APMonom monom = new APMonom();
|
|
labelExpressionToAPMonom(expr, monom);
|
|
result.add(monom);
|
|
return result;
|
|
}
|
|
case EXP_TRUE:
|
|
result.add(new APMonom(true));
|
|
return result;
|
|
case EXP_FALSE:
|
|
result.add(new APMonom(false));
|
|
case EXP_OR:
|
|
result.addAll(labelExpressionToAPMonom(expr.getLeft()));
|
|
result.addAll(labelExpressionToAPMonom(expr.getRight()));
|
|
return result;
|
|
}
|
|
throw new UnsupportedOperationException("Unsupported operator in label expression: "+expr);
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns a single APMonom for a single clause of the overall DNF formula.
|
|
* Modifies APMonom result such that in the end it is correct.
|
|
*/
|
|
private void labelExpressionToAPMonom(BooleanExpression<AtomLabel> expr, APMonom result) throws HOAConsumerException {
|
|
try {
|
|
switch (expr.getType()) {
|
|
case EXP_TRUE:
|
|
case EXP_FALSE:
|
|
case EXP_OR:
|
|
throw new HOAConsumerException("Complex transition labels are not yet supported, only disjunctive normal form: "+expr);
|
|
|
|
case EXP_AND:
|
|
labelExpressionToAPMonom(expr.getLeft(), result);
|
|
labelExpressionToAPMonom(expr.getRight(), result);
|
|
return;
|
|
case EXP_ATOM: {
|
|
int apIndex = expr.getAtom().getAPIndex();
|
|
if (result.isSet(apIndex) && result.getValue(apIndex)!=true) {
|
|
throw new HOAConsumerException("Complex transition labels are not yet supported, transition label evaluates to false");
|
|
}
|
|
result.setValue(apIndex, true);
|
|
return;
|
|
}
|
|
case EXP_NOT: {
|
|
if (!expr.getLeft().isAtom()) {
|
|
throw new HOAConsumerException("Complex transition labels are not yet supported, only conjunction of (negated) labels");
|
|
}
|
|
int apIndex = expr.getLeft().getAtom().getAPIndex();
|
|
if (result.isSet(apIndex) && result.getValue(apIndex)!=false) {
|
|
throw new HOAConsumerException("Complex transition labels are not yet supported, transition label evaluates to false");
|
|
}
|
|
result.setValue(apIndex, false);
|
|
return;
|
|
}
|
|
}
|
|
} catch (PrismException e) {
|
|
throw new HOAConsumerException("While parsing, APMonom exception: "+e.getMessage());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void addEdgeWithLabel(int stateId,
|
|
BooleanExpression<AtomLabel> labelExpr,
|
|
List<Integer> conjSuccessors, List<Integer> accSignature)
|
|
throws HOAConsumerException {
|
|
|
|
if (conjSuccessors.size() != 1) {
|
|
throw new HOAConsumerException("Not a DA, state "+stateId+" has transition with conjunctive target");
|
|
}
|
|
|
|
if (accSignature != null) {
|
|
throw new TransitionBasedAcceptanceException("DA has transition-based acceptance (state "+stateId+", currently only state-labeled acceptance is supported");
|
|
}
|
|
|
|
if (labelExpr == null) {
|
|
throw new HOAConsumerException("Missing label on transition");
|
|
}
|
|
|
|
int to = conjSuccessors.get(0);
|
|
|
|
for (APMonom monom : labelExpressionToAPMonom(labelExpr)) {
|
|
APMonom2APElements it = new APMonom2APElements(aps, monom);
|
|
while(it.hasNext()) {
|
|
APElement el = it.next();
|
|
if (da.hasEdge(stateId, el)) {
|
|
throw new HOAConsumerException("Not a deterministic automaton, non-determinism detected (state "+stateId+")");
|
|
}
|
|
da.addEdge(stateId, el, to);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void notifyEndOfState(int stateId) throws HOAConsumerException
|
|
{
|
|
implicitEdgeHelper.endOfState();
|
|
}
|
|
|
|
@Override
|
|
public void notifyEnd() throws HOAConsumerException {
|
|
// flip acceptance sets that need negating
|
|
if (negateAcceptanceSetMembership != null) {
|
|
for (int index : negateAcceptanceSetMembership) {
|
|
acceptanceSets.get(index).flip(0, size);
|
|
}
|
|
}
|
|
|
|
clear();
|
|
}
|
|
|
|
@Override
|
|
public void notifyAbort() {
|
|
clear();
|
|
|
|
}
|
|
|
|
public DA<BitSet,? extends AcceptanceOmega> getDA() {
|
|
return da;
|
|
}
|
|
|
|
@Override
|
|
public void notifyWarning(String warning) throws HOAConsumerException
|
|
{
|
|
// warnings are fatal
|
|
throw new HOAConsumerException(warning);
|
|
}
|
|
|
|
}
|