//============================================================================== // // Copyright (c) 2014- // Authors: // * Joachim Klein (TU Dresden) // * David Mueller (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. *
* The automaton has to be deterministic and complete, with state-based acceptance and * labels (explicit/implicit) on the edges. *
* If the automaton has transition-based acceptance, {@code TransitionBasedAcceptanceException} * is thrown. *
* There are (currently) more restrictions on the automaton: * */ 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 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 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 extraInfo; /** For each acceptance set in the HOA automaton, the set of states that are included in that set */ private List acceptanceSets = null; /** The set of acceptance set indizes where state membership has to be inverted */ private Set negateAcceptanceSetMembership = null; /** The list of atomic propositions (in List form) */ private List 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 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 labelExpr) throws HOAConsumerException { // NOP, aliases are already resolved } @Override public void setAPs(List aps) throws HOAConsumerException { apList = aps; for (String ap : aps) { this.aps.addAP(ap); } } @Override public void setAcceptanceCondition(int numberOfSets, BooleanExpression accExpr) throws HOAConsumerException { this.accExpr = accExpr; } @Override public void provideAcceptanceName(String name, List 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 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 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(size); da.setStartState(startState); if (apList == null) { // no call to setAPs apList = new ArrayList(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(); return prepareAcceptanceGeneric(accExpr); } /** * Prepare a generic acceptance condition for the parsed automaton. **/ private AcceptanceGeneric prepareAcceptanceGeneric(BooleanExpression 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(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(numberOfPairs*2); for (int i = 0; i< numberOfPairs; i++) { BitSet L = new BitSet(); acceptanceSets.add(L); // Fin(L) = F G !L ArrayList K_list = new ArrayList(); 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 labelExpr, List 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 conjSuccessors, List 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 labelExpressionToAPMonom(BooleanExpression expr) throws HOAConsumerException { List result = new ArrayList(); 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 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 labelExpr, List conjSuccessors, List 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 getDA() { return da; } @Override public void notifyWarning(String warning) throws HOAConsumerException { // warnings are fatal throw new HOAConsumerException(warning); } }