/*
* Copyright (C) 2014-2015 Daniel Dietsch (dietsch@informatik.uni-freiburg.de)
* Copyright (C) 2015 University of Freiburg
* Copyright (C) 2015 Vincent Langenfeld (langenfv@informatik.uni-freiburg.de)
*
* This file is part of the ULTIMATE LTL2Aut plug-in.
*
* The ULTIMATE LTL2Aut plug-in is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The ULTIMATE LTL2Aut plug-in 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the ULTIMATE LTL2Aut plug-in. If not, see .
*
* Additional permission under GNU GPL version 3 section 7:
* If you modify the ULTIMATE LTL2Aut plug-in, or any covered work, by linking
* or combining it with Eclipse RCP (or a modified version of Eclipse RCP),
* containing parts covered by the terms of the Eclipse Public License, the
* licensors of the ULTIMATE LTL2Aut plug-in grant you additional permission
* to convey the resulting work.
*/
package de.uni_freiburg.informatik.ultimate.ltl2aut.never2nwa;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import de.uni_freiburg.informatik.ultimate.automata.AutomataLibraryServices;
import de.uni_freiburg.informatik.ultimate.automata.nestedword.INestedWordAutomaton;
import de.uni_freiburg.informatik.ultimate.automata.nestedword.NestedWordAutomaton;
import de.uni_freiburg.informatik.ultimate.automata.nestedword.VpAlphabet;
import de.uni_freiburg.informatik.ultimate.automata.statefactory.DummyStateFactory;
import de.uni_freiburg.informatik.ultimate.boogie.BoogieExpressionTransformer;
import de.uni_freiburg.informatik.ultimate.boogie.annotation.LTLPropertyCheck.CheckableExpression;
import de.uni_freiburg.informatik.ultimate.boogie.ast.AssumeStatement;
import de.uni_freiburg.informatik.ultimate.boogie.ast.BinaryExpression;
import de.uni_freiburg.informatik.ultimate.boogie.ast.BooleanLiteral;
import de.uni_freiburg.informatik.ultimate.boogie.ast.Expression;
import de.uni_freiburg.informatik.ultimate.boogie.ast.IntegerLiteral;
import de.uni_freiburg.informatik.ultimate.boogie.ast.Statement;
import de.uni_freiburg.informatik.ultimate.boogie.ast.UnaryExpression;
import de.uni_freiburg.informatik.ultimate.boogie.symboltable.BoogieSymbolTable;
import de.uni_freiburg.informatik.ultimate.boogie.type.BoogieType;
import de.uni_freiburg.informatik.ultimate.core.model.models.ILocation;
import de.uni_freiburg.informatik.ultimate.core.model.preferences.IPreferenceProvider;
import de.uni_freiburg.informatik.ultimate.core.model.services.ILogger;
import de.uni_freiburg.informatik.ultimate.core.model.services.IUltimateServiceProvider;
import de.uni_freiburg.informatik.ultimate.ltl2aut.ast.AstNode;
import de.uni_freiburg.informatik.ultimate.ltl2aut.ast.BinaryOperator;
import de.uni_freiburg.informatik.ultimate.ltl2aut.ast.BoolLiteral;
import de.uni_freiburg.informatik.ultimate.ltl2aut.ast.ComperativeOperator;
import de.uni_freiburg.informatik.ultimate.ltl2aut.ast.IntLiteral;
import de.uni_freiburg.informatik.ultimate.ltl2aut.ast.LabeledBlock;
import de.uni_freiburg.informatik.ultimate.ltl2aut.ast.Name;
import de.uni_freiburg.informatik.ultimate.ltl2aut.ast.Not;
import de.uni_freiburg.informatik.ultimate.ltl2aut.ast.OptionStatement;
import de.uni_freiburg.informatik.ultimate.ltl2aut.ast.SkipStatement;
import de.uni_freiburg.informatik.ultimate.ltl2aut.preferences.PreferenceInitializer;
import de.uni_freiburg.informatik.ultimate.plugins.generator.rcfgbuilder.cfg.CodeBlock;
import de.uni_freiburg.informatik.ultimate.plugins.generator.rcfgbuilder.cfg.CodeBlockFactory;
import de.uni_freiburg.informatik.ultimate.plugins.generator.rcfgbuilder.cfg.StatementSequence;
import de.uni_freiburg.informatik.ultimate.util.simplifier.NormalFormTransformer;
/**
* Never2Automaton converts the never claim description of an automaton into an an NWA automaton of the AutomataLibrary.
*
* @author Langenfeld
* @author Daniel Dietsch (dietsch@informatik.uni-freiburg.de)
* @note the transformation from Ast to Automaton also brings a transformation from Promela Ast to Boogie Ast nodes.
*/
public class Never2Automaton {
private final IUltimateServiceProvider mServices;
private final AstNode mNeverClaim;
private final ILogger mLogger;
private final Map mIRS;
private final CodeBlockFactory mCodeblockFactory;
private final NestedWordAutomaton mAutomaton;
private final boolean mUseSBE;
private final boolean mRewriteAssumeDuringSBE;
/**
* The Never2Automaton instance will build a Büchi automaton from the input.
*
* @param ast
* @param irs
* @param services
* @param cbf
* @throws Exception
*/
public Never2Automaton(final AstNode ast, final BoogieSymbolTable boogieSymbolTable,
final Map irs, final ILogger logger, final IUltimateServiceProvider services,
final CodeBlockFactory cbf) throws Exception {
mServices = services;
mLogger = logger;
mNeverClaim = ast;
mIRS = irs;
mCodeblockFactory = cbf;
final IPreferenceProvider ups =
mServices.getPreferenceProvider(de.uni_freiburg.informatik.ultimate.ltl2aut.Activator.PLUGIN_ID);
mUseSBE = ups.getBoolean(PreferenceInitializer.LABEL_OPTIMIZE_SBE);
mRewriteAssumeDuringSBE = ups.getBoolean(PreferenceInitializer.LABEL_OPTIMIZE_REWRITEASSUME);
mAutomaton = new NestedWordAutomaton<>(new AutomataLibraryServices(mServices),
new VpAlphabet<>(collectAlphabet()), new DummyStateFactory());
collectStates(mNeverClaim, null);
mLogger.debug(String.format("Resulting automaton is:\n%s", mAutomaton));
}
/**
* get the constructed automaton
*
* @return automaton
*/
public INestedWordAutomaton getAutomaton() {
return mAutomaton;
}
/**
* Walks the AST for labeled blocks and extracts the names as Nodes in the automaton. Nodes starting with "accept"
* are accepting nodes, the one called init is the initial one.
*
* @see LTL2BA, LTL3BA output format
* @param branch
* Ast of the Automaton description in Promela
* @throws Exception
*/
private void collectStates(final AstNode branch, final String preState) throws Exception {
if (branch instanceof LabeledBlock) // add nodes
{
final String state = ((Name) ((LabeledBlock) branch).getValue()).getIdent();
addState(state);
for (final AstNode a : branch.getOutgoingNodes()) {
collectStates(a, state);
}
} else if (branch instanceof BoolLiteral) {
return;
} else if (branch instanceof SkipStatement) {
// case " accept_all: skip
addTransition(preState, getAssumeTrue(), preState);
return;
} else if (branch instanceof Name) {
return;
} else if (branch instanceof OptionStatement) {
// option.body .goto .name
final String succ = ((Name) branch.getOutgoingNodes().get(0).getOutgoingNodes().get(0)).getIdent();
addState(succ);
// add transitions
for (final CodeBlock cond : getAssume(((OptionStatement) branch).getCondition())) {
addTransition(preState, cond, succ);
}
} else {
for (final AstNode a : branch.getOutgoingNodes()) {
collectStates(a, preState);
}
}
}
/**
* Collect all symbols that the automaton will have from the AST which will be all conditions found in the AST.
*
* @param mNeverClaim
* Ast of the Automaton description in Promela
* @return
* @throws Exception
*/
public Set collectAlphabet() throws Exception {
final Set symbols = new HashSet<>();
visitAstForSymbols(mNeverClaim, symbols);
return symbols;
}
private void visitAstForSymbols(final AstNode branch, final Set symbols) throws Exception {
if (branch instanceof BoolLiteral) {
return;
} else if (branch instanceof SkipStatement) {
symbols.add(getAssumeTrue());
} else if (branch instanceof Name) {
return;
} else if (branch instanceof OptionStatement) {
symbols.addAll(getAssume(((OptionStatement) branch).getCondition()));
} else {
for (final AstNode a : branch.getOutgoingNodes()) {
visitAstForSymbols(a, symbols);
}
}
}
private CodeBlock getAssumeTrue() {
final ILocation loc = null;
final StatementSequence ss = mCodeblockFactory.constructStatementSequence(null, null,
new AssumeStatement(loc, new BooleanLiteral(loc, true)));
return ss;
}
private List getAssume(final AstNode condition) throws Exception {
if (condition instanceof Name) {
// this may be already translated by the IRS
final Name name = (Name) condition;
final CheckableExpression checkExpr = mIRS.get(name.getIdent().toUpperCase());
if (checkExpr != null) {
return getAssumeFromCheckableExpression(checkExpr);
}
mLogger.warn("Root condition is a name, but no mapping in IRS found: " + name.getIdent());
}
// this could be an actual neverclaim and we have to translate it
// manually
final CheckableExpression checkExpr = toBoogieAst(condition);
return getAssumeFromCheckableExpression(checkExpr);
}
private List getAssumeFromCheckableExpression(final CheckableExpression checkExpr) {
final ArrayList rtr = new ArrayList<>();
final List preStmts = new ArrayList<>();
if (checkExpr.getStatements() != null) {
preStmts.addAll(checkExpr.getStatements());
}
final ILocation loc = checkExpr.getExpression().getLocation();
for (final Expression expr : simplify(checkExpr.getExpression())) {
final List stmts = new ArrayList<>(preStmts);
stmts.add(new AssumeStatement(loc, expr));
rtr.add(mCodeblockFactory.constructStatementSequence(null, null, stmts));
}
return rtr;
}
private Collection simplify(Expression expr) {
if (mUseSBE) {
final NormalFormTransformer ct = new NormalFormTransformer<>(new BoogieExpressionTransformer());
if (mRewriteAssumeDuringSBE) {
expr = ct.rewriteNotEquals(expr);
}
return ct.toDnfDisjuncts(expr);
}
return Collections.singleton(expr);
}
/**
* Translates the atomic propositions from LTL2Aut.AstNode into Boogie ASTNode for further processing.
*
* @return root node of the proposition as Boogie ASTNodes
* @throws Exception
*/
public CheckableExpression toBoogieAst(final AstNode branch) throws Exception {
if (branch instanceof BinaryOperator) {
final BinaryOperator ncBinOp = (BinaryOperator) branch;
BinaryExpression.Operator op;
switch (ncBinOp.getType()) {
case and:
op = BinaryExpression.Operator.LOGICAND;
break;
case minus:
op = BinaryExpression.Operator.ARITHMINUS;
break;
case or:
op = BinaryExpression.Operator.LOGICOR;
break;
case plus:
op = BinaryExpression.Operator.ARITHPLUS;
break;
case times:
op = BinaryExpression.Operator.ARITHMUL;
break;
case divide:
op = BinaryExpression.Operator.ARITHDIV;
break;
default:
throw new Exception("Binary Operator unknown");
}
final CheckableExpression left = toBoogieAst(branch.getOutgoingNodes().get(0));
CheckableExpression right = toBoogieAst(branch.getOutgoingNodes().get(1));
CheckableExpression expr =
new CheckableExpression(new BinaryExpression(null, op, left.getExpression(), right.getExpression()),
mergeStatements(left, right));
if (branch.getOutgoingNodes().size() > 2) {
for (int i = 2; i < branch.getOutgoingNodes().size(); i++) {
right = toBoogieAst(branch.getOutgoingNodes().get(i));
expr = new CheckableExpression(
new BinaryExpression(null, op, expr.getExpression(), right.getExpression()),
mergeStatements(expr, right));
}
}
return expr;
} else if (branch instanceof BoolLiteral) {
return new CheckableExpression(
new BooleanLiteral(null, BoogieType.TYPE_BOOL, ((BoolLiteral) branch).getValue()), null);
} else if (branch instanceof ComperativeOperator) {
BinaryExpression.Operator op;
switch (((ComperativeOperator) branch).getType()) {
case equals:
op = BinaryExpression.Operator.COMPEQ;
break;
case geq:
op = BinaryExpression.Operator.COMPGEQ;
break;
case greater:
op = BinaryExpression.Operator.COMPGT;
break;
default:
throw new Exception("Binary Operator unknown");
}
final CheckableExpression left = toBoogieAst(branch.getOutgoingNodes().get(0));
final CheckableExpression right = toBoogieAst(branch.getOutgoingNodes().get(1));
final CheckableExpression expr =
new CheckableExpression(new BinaryExpression(null, op, left.getExpression(), right.getExpression()),
mergeStatements(left, right));
return expr;
} else if (branch instanceof IntLiteral) {
return new CheckableExpression(new IntegerLiteral(null, Integer.toString(((IntLiteral) branch).getValue())),
null);
} else if (branch instanceof Name) {
final Name name = (Name) branch;
final CheckableExpression checkExpr = mIRS.get(name.getIdent().toUpperCase());
if (checkExpr != null) {
return checkExpr;
}
} else if (branch instanceof Not) {
final CheckableExpression right = toBoogieAst(branch.getOutgoingNodes().get(0));
return new CheckableExpression(
new UnaryExpression(null, UnaryExpression.Operator.LOGICNEG, right.getExpression()),
right.getStatements());
} else if (branch instanceof Name) {
final Name name = (Name) branch;
if ("true".equalsIgnoreCase(name.getIdent())) {
return new CheckableExpression(new BooleanLiteral(null, true), null);
} else if ("false".equalsIgnoreCase(name.getIdent())) {
return new CheckableExpression(new BooleanLiteral(null, false), null);
}
}
throw new Exception(String.format("Type %s should not occur as part of a atomic Proposition in LTL",
branch.getClass().toString()));
}
private static List mergeStatements(final CheckableExpression... exprs) {
final List rtr = new ArrayList<>();
for (final CheckableExpression expr : exprs) {
if (expr.getStatements() != null) {
rtr.addAll(expr.getStatements());
}
}
return rtr;
}
private void addTransition(final String predecessor, final CodeBlock letter, final String successor) {
mAutomaton.getVpAlphabet().getInternalAlphabet().add(letter);
mAutomaton.addInternalTransition(predecessor, letter, successor);
}
private void addState(final String state) {
if (!mAutomaton.getStates().contains(state)) {
mAutomaton.addState(state.endsWith("init"), state.startsWith("accept"), state);
}
}
}