/*
* Copyright (C) 2012-2013 University of Freiburg
*
* This file is part of SMTInterpol.
*
* SMTInterpol 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.
*
* SMTInterpol 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 SMTInterpol. If not, see <http://www.gnu.org/licenses/>.
*/
package de.uni_freiburg.informatik.ultimate.smtinterpol.proofcheck;
import java.util.ArrayDeque;
import de.uni_freiburg.informatik.ultimate.logic.AnnotatedTerm;
import de.uni_freiburg.informatik.ultimate.logic.ApplicationTerm;
import de.uni_freiburg.informatik.ultimate.logic.ConstantTerm;
import de.uni_freiburg.informatik.ultimate.logic.QuantifiedFormula;
import de.uni_freiburg.informatik.ultimate.logic.Term;
import de.uni_freiburg.informatik.ultimate.logic.TermVariable;
import de.uni_freiburg.informatik.ultimate.logic.Theory;
/**
* This class is used to convert a substitution proof step (@eq).
*
* NOTE: Currently substitution is applied only within ApplicationTerm.
* There are, however, methods for unpacking AnnotatedTerm and
* QuantifiedFormula. The latter was never tested, since it was not
* supported. For AnnotatedTerm, there was only the ':quoted' annotation,
* which must not be unpacked. So if there should be support for a new
* annotation, this case must be excluded.
* The places are marked with <quantifiers> and <annotations>,
* respectively.
*
* @author Christian Schilling
*/
public class SubstitutionConverter extends AConverter {
// <annotations> or <quantifiers>
/*
private static final int G_UNEXPANDED = 0;
private static final int G_EXPANDED = 1;
private static final int G_REPLACED = 2;
*/
/**
* @param appendable appendable to write the proof to
* @param theory the theory
* @param converter term converter
* @param simplifier computation simplifier
*/
public SubstitutionConverter(final Appendable appendable,
final Theory theory, final TermConverter converter,
final ComputationSimplifier simplifier) {
super(appendable, theory, converter, simplifier);
}
/**
* This method converts a term with respect to a rewrite equality.
* All occurrences of the left-hand side term are replaced by the term
* on the right-hand side.
* Since the substitution may replace only parts of the original formula,
* the result must be computed.
*
* @param origin term that is rewritten
* @param rewrite equality rule
* @return the term with substituted parts
*/
public Term convert(final Term origin, final ApplicationTerm rewrite) {
assert ((rewrite.getFunction().getName() == "=")
&& (rewrite.getParameters().length == 2));
final Term lhs = rewrite.getParameters()[0];
final Term rhs = rewrite.getParameters()[1];
/*
* NOTE: This assertion is not relied on and should only detect useless
* proof steps.
*/
assert (lhs != rhs) : "Rewrite has equal lhs and rhs.";
// trivial case: whole formula is substituted
if (origin == lhs) {
return rhs;
}
// <annotations> or <quantifiers>: excluded, currently not reasonable
if ((origin instanceof AnnotatedTerm)
|| (origin instanceof QuantifiedFormula)) {
throw new IllegalArgumentException(
"AnnotatedTerm or QuantifiedFormula cannot be partially "
+ "substituted in the current version.");
}
// compute substitution result
final Term result = substitute(origin, lhs, rhs).getTerm();
assert (result != null);
return result;
}
/**
* This method substitutes every occurrence in the original term.
* To do this, the term is transformed into a term tree on-the-fly (see
* {@link ATermNode} for details regarding the tree) in a top-bottom manner
* using a stack. There are two possibilities:
*
* 1) Whenever an occurrence of the pattern was found, the current path is
* pruned and the parent node is informed about a change in its child node.
* If all child nodes have been expanded, the parent node rewrites its own
* term to capture the substitution and recursively informs its own parent
* node.
*
* 2) All child nodes expanded themselves to the leaf level. In this case
* the parent node needs not change its term and just must inform its
* parent node.
*
* This way in the end the root node is reached again and the method
* terminates.
*
* @param origin original term
* @param pattern pattern that is replaced
* @param replace term that is inserted for the pattern
* @return the root node, which contains the replaced formula
*/
private ATermNode substitute(final Term origin, final Term pattern,
final Term replace) {
ATermNode node;
assert ((origin instanceof ApplicationTerm)
|| (origin instanceof AnnotatedTerm)
|| (origin instanceof QuantifiedFormula));
if (origin instanceof ApplicationTerm) {
node = new AppTermNode(null, (ApplicationTerm)origin);
} else if (origin instanceof AnnotatedTerm) {
// <annotations>
assert false;
return null;
// node = new AnnotTermNode(null, (AnnotatedTerm)origin);
} else {
// <quantifiers>
assert (origin instanceof QuantifiedFormula);
assert false;
return null;
// node = new QuantTermNode(null, (QuantifiedFormula)origin);
}
final ArrayDeque<ATermNode> stack =
new ArrayDeque<SubstitutionConverter.ATermNode>();
stack.push(node);
// search in the term tree and replace all occurrences of the pattern
while (true) {
node = stack.pop();
final Term next = node.next();
// no parameters left, finalize node
if (next == null) {
final ATermNode parent = node.mParent;
// root node, stop
if (parent == null) {
break;
} else {
// go on with parent node; also inform about changes
if (node.isNew()) {
parent.replace(node.getTerm());
}
node = parent;
}
} else {
// still parameters left
// pattern fits, term is replaced
if (next == pattern) {
node.replace(replace);
} else {
// else a new node is constructed
if (next instanceof ApplicationTerm) {
node = new AppTermNode(node,
(ApplicationTerm)next);
} //NOCHECKSTYLE
// <annotations>
/*
else if (next instanceof AnnotatedTerm) {
node = new AnnotTermNode(node,
(AnnotatedTerm)next);
}
*/
// <quantifiers>
/*
else if (next instanceof QuantifiedFormula) {
node = new QuantTermNode(node,
(QuantifiedFormula)next);
}
*/
// other terms should not occur or are not interesting
else {
assert ((next instanceof ConstantTerm)
|| (next instanceof TermVariable)
// <annotations>
|| (next instanceof AnnotatedTerm)
// <quantifiers>
|| (next instanceof QuantifiedFormula)
);
}
}
}
stack.push(node);
}
// root node with substitutions applied
return node;
}
/**
* This class is used to construct an on-the-fly term tree.
* It contains the following information: its parent node, its term,
* if its term changed, and the next child node (only reasonable for an
* ApplicationTerm).
*/
private abstract class ATermNode {
// parent node, null for the root
ATermNode mParent;
/**
* @param parent the parent node (null for root node)
*/
public ATermNode(final ATermNode parent) {
mParent = parent;
}
/**
* This method indicates if the term has changed.
*
* @return true iff term has changed
*/
abstract boolean isNew();
/**
* This is the getter for the term. It returns the original term if no
* change occurred and the changed term otherwise.
*
* @return the associated term
*/
abstract Term getTerm();
/**
* This method returns the next child node or null if all of them have
* been visited. A child is the next parameter for an ApplicationTerm
* and the sub-term for an AnnotatedTerm or a QuantifiedFormula.
*
* @return the next parameter if possible, null otherwise
*/
abstract Term next();
/**
* This method replaces the current child node with the given term.
*
* @param replace new parameter term
*/
abstract void replace(Term replace);
}
/**
* This class is represents an ApplicationTerm in the term tree.
*/
private class AppTermNode extends ATermNode {
// associated term
final ApplicationTerm mTerm;
// next expanded parameter
int mParams;
// array of new parameters, null if no change occurred so far
Term[] mReplace;
/**
* @param parent the parent node (null for root node)
* @param term the associated term
*/
public AppTermNode(final ATermNode parent,
final ApplicationTerm term) {
super(parent);
mTerm = term;
mParams = term.getParameters().length;
mReplace = null;
}
@Override
boolean isNew() {
return (mReplace != null);
}
@Override
Term getTerm() {
// no change, use old term
if (mReplace == null) {
return mTerm;
} else {
// inner term has changed, create new term
// fill the parameter array (some elements may be null)
final Term[] oldParams = mTerm.getParameters();
for (int i = mReplace.length - 1; i >= 0; --i) {
if (mReplace[i] == null) {
mReplace[i] = oldParams[i];
}
}
return mTheory.term(mTerm.getFunction(), mReplace);
}
}
@Override
Term next() {
// finished expansion
if (mParams == 0) {
return null;
} else {
// still parameters to go
assert (mParams > 0);
return mTerm.getParameters()[--mParams];
}
}
@Override
void replace(final Term replace) {
assert (replace != null);
// no replacement before, initialize array
if (mReplace == null) {
mReplace = new Term[mTerm.getParameters().length];
}
mReplace[mParams] = replace;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append('{');
builder.append(mTerm.toStringDirect());
builder.append(", ");
builder.append(Integer.toString(mParams));
builder.append(", ");
if (mReplace == null) {
builder.append("null");
} else {
builder.append(mReplace);
}
builder.append('}');
return builder.toString();
}
}
// <annotations>
// /**
// * This class is represents an AnnotatedTerm in the term tree.
// */
// private class AnnotTermNode extends ATermNode {
// // associated term
// AnnotatedTerm m_term;
// // status
// int m_status;
//
// /**
// * @param parent the parent node (null for root node)
// * @param term the associated term
// */
// public AnnotTermNode(final ATermNode parent,
// final AnnotatedTerm term) {
// super(parent);
// m_term = term;
// m_status = G_UNEXPANDED;
// }
//
// @Override
// boolean isNew() {
// return (m_status == G_REPLACED);
// }
//
// @Override
// Term getTerm() {
// return m_term;
// }
//
// @Override
// Term next() {
// // sub-term already expanded
// if (m_status != G_UNEXPANDED) {
// return null;
// }
// // expand sub-term
// else {
// m_status = G_EXPANDED;
// return m_term.getSubterm();
// }
// }
//
// @Override
// void replace(final Term replace) {
// assert ((m_status != G_REPLACED) && (replace != null));
// m_status = G_REPLACED;
//
// m_term = (AnnotatedTerm)
// m_theory.annotatedTerm(m_term.getAnnotations(), replace);
// }
//
// @Override
// public String toString() {
// final StringBuilder builder = new StringBuilder();
//
// builder.append("{");
// builder.append(m_term.toStringDirect());
// builder.append(", ");
// builder.append(m_status == G_UNEXPANDED ? "unexpanded" :
// m_status == G_EXPANDED ? "expanded" : "changed");
// builder.append("}");
//
// return builder.toString();
// }
// }
// <quantifiers>
// /**
// * This class is represents a QuantifiedFormula in the term tree.
// */
// private class QuantTermNode extends ATermNode {
// // associated term
// QuantifiedFormula m_term;
// // status
// int m_status;
//
// /**
// * @param parent the parent node (null for root node)
// * @param term the associated term
// */
// public QuantTermNode(final ATermNode parent,
// final QuantifiedFormula term) {
// super(parent);
// m_term = term;
// m_status = G_UNEXPANDED;
// }
//
// @Override
// boolean isNew() {
// return (m_status == G_REPLACED);
// }
//
// @Override
// Term getTerm() {
// return m_term;
// }
//
// @Override
// Term next() {
// // sub-term already expanded
// if (m_status != G_UNEXPANDED) {
// return null;
// }
// // expand sub-term
// else {
// m_status = G_EXPANDED;
// return m_term.getSubformula();
// }
// }
//
// @Override
// void replace(final Term replace) {
// assert ((m_status != G_REPLACED) && (replace != null));
// m_status = G_REPLACED;
//
// if (m_term.getQuantifier() == QuantifiedFormula.EXISTS) {
// m_term = (QuantifiedFormula)
// m_theory.exists(m_term.getVariables(), replace);
// }
// else if (m_term.getQuantifier() == QuantifiedFormula.FORALL) {
// m_term = (QuantifiedFormula)
// m_theory.forall(m_term.getVariables(), replace);
// }
// else {
// throw new UnsupportedOperationException(
// "The quantifier is not supported.");
// }
// }
//
// @Override
// public String toString() {
// final StringBuilder builder = new StringBuilder();
//
// builder.append("{");
// builder.append(m_term.toStringDirect());
// builder.append(", ");
// builder.append(m_status == G_UNEXPANDED ? "unexpanded" :
// m_status == G_EXPANDED ? "expanded" : "changed");
// builder.append("}");
//
// return builder.toString();
// }
// }
}