/*
* Copyright (C) 2009-2012 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.logic;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.HashMap;
/**
* This is the base class for transforming formulas. It does nothing by itself
* but you can use it to create arbitrary transformations on formulas.
* The transform method applies the transformation in a non-recursive manner.
*
* Subclasses should override the function convert. It takes as argument
* the term to convert and should set its result with setResult. If it needs
* to build a more complex term with transformed arguments, it can enqueue the
* subclasses BuildLetTerm, BuildApplicationTerm, BuildAnnotatedTerm with
* enqueueWalker. The arguments should be added to the work queue by
* pushTerm/pushTerms.
*
* Of course, you can also add your own Build class that takes the converted
* arguments from the conversion stack using getConverted().
*
* @author Jochen Hoenicke
*/
public class TermTransformer extends NonRecursive {
/**
* The term cache.
*/
private final ArrayDeque<HashMap<Term, Term>> mCache =
new ArrayDeque<HashMap<Term,Term>>();
/**
* The converted terms. This is used for example to store the
* arguments of an application term, before the application term is
* evaluated.
*/
private final ArrayDeque<Term> mConverted = new ArrayDeque<Term>();
/**
* This class represents one item of work. It consists of a term and
* some task that still needs to be performed on the term.
*/
private static class Convert implements Walker {
private final Term mTerm;
public Convert(Term term) {
mTerm = term;
}
@Override
public String toString() {
return "Convert " + mTerm.toStringDirect();
}
@Override
public void walk(NonRecursive walker) {
((TermTransformer) walker).cacheConvert(mTerm);
}
}
/**
* Push all terms in the array on the todo stack as CONVERT work item.
* @param terms the array of terms.
*/
protected final void pushTerms(Term[] terms) {
for (int i = terms.length - 1; i >= 0; i--) {
pushTerm(terms[i]);
}
}
/**
* Push a term on the todo stack as CONVERT work item.
* @param term the term to convert.
*/
protected final void pushTerm(Term term) {
enqueueWalker(new Convert(term));
}
/**
* Set the conversion result to term.
* @param term the converted term.
*/
protected final void setResult(Term term) {
mConverted.addLast(term);
}
private static class AddCache implements Walker {
Term mOldTerm;
public AddCache(Term term) {
mOldTerm = term;
}
@Override
public void walk(NonRecursive engine) {
final TermTransformer transformer = (TermTransformer) engine;
transformer.mCache.getLast().put(
mOldTerm, transformer.mConverted.getLast());
}
@Override
public String toString() {
return "AddCache[" + mOldTerm.toStringDirect() + "]";
}
}
private void cacheConvert(Term term) {
final Term newTerm = mCache.getLast().get(term);
if (newTerm == null) {
enqueueWalker(new AddCache(term));
convert(term);
} else {
setResult(newTerm);
}
}
protected void beginScope() {
mCache.addLast(new HashMap<Term, Term>());
}
protected void endScope() {
mCache.removeLast();
}
/**
* The function that does the transformation. Override this function
* if you build your own term transformer. It does not return the result
* but instead it puts it on the converted stack using setResult().
* Instead it can also enqueue some Builders that will in the end put the
* result on the converted stack.
*
* You can always call super.convert() if you do not need to convert
* the term. It will still convert the sub-terms. If you do not want to
* convert the sub terms, call setResult(term) instead.
* @param term The term to convert.
*/
protected void convert(Term term) {
if (term instanceof ConstantTerm
|| term instanceof TermVariable) {
mConverted.addLast(term);
} else if (term instanceof ApplicationTerm) {
enqueueWalker(new BuildApplicationTerm((ApplicationTerm) term));
pushTerms(((ApplicationTerm) term).getParameters());
} else if (term instanceof LetTerm) {
enqueueWalker(new StartLetTerm((LetTerm) term));
pushTerms(((LetTerm) term).getValues());
} else if (term instanceof QuantifiedFormula) {
enqueueWalker(new BuildQuantifier((QuantifiedFormula) term));
pushTerm(((QuantifiedFormula) term).getSubformula());
beginScope();
} else if (term instanceof AnnotatedTerm) {
final AnnotatedTerm annterm = (AnnotatedTerm) term;
enqueueWalker(new BuildAnnotation(annterm));
final Annotation[] annots = annterm.getAnnotations();
for (int i = annots.length - 1; i >= 0; i--) {
final Object value = annots[i].getValue();
if (value instanceof Term) {
pushTerm((Term) value);
} else if (value instanceof Term[]) {
pushTerms((Term[]) value);
}
}
pushTerm(annterm.getSubterm());
return;
} else {
throw new AssertionError("Unknown Term: " + term.toStringDirect());
}
}
public void convertApplicationTerm(
ApplicationTerm appTerm, Term[] newArgs) {
Term newTerm = appTerm;
if (newArgs != appTerm.getParameters()) {
final FunctionSymbol fun = appTerm.getFunction();
final Theory theory = fun.getTheory();
newTerm = theory.term(fun, newArgs);
}
setResult(newTerm);
}
public void preConvertLet(LetTerm oldLet, Term[] newValues) {
beginScope();
enqueueWalker(new BuildLetTerm(oldLet, newValues));
pushTerm(oldLet.getSubTerm());
}
public void postConvertLet(LetTerm oldLet, Term[] newValues, Term newBody) {
Term result = oldLet;
if (oldLet.getValues() != newValues
|| oldLet.getSubTerm() != newBody) {
result = oldLet.getTheory().let(
oldLet.getVariables(), newValues, newBody);
}
setResult(result);
}
public void postConvertQuantifier(QuantifiedFormula old, Term newBody) {
Term newFormula = old;
if (newBody != old.getSubformula()) {
final Theory theory = old.getTheory();
final TermVariable[] vars = old.getVariables();
newFormula = old.getQuantifier() == QuantifiedFormula.EXISTS
? theory.exists(vars, newBody) : theory.forall(vars,newBody);
}
setResult(newFormula);
}
public void postConvertAnnotation(AnnotatedTerm old,
Annotation[] newAnnots, Term newBody) {
final Annotation[] annots = old.getAnnotations();
Term result = old;
if (newBody != old.getSubterm() || newAnnots != annots) {
result = old.getTheory().annotatedTerm(newAnnots, newBody);
}
setResult(result);
}
/**
* Transform a term.
* @param term the term to transform.
* @return the resulting transformed term.
*/
public final Term transform(Term term) {
beginScope();
run(new Convert(term));
endScope();
return mConverted.removeLast();
}
/**
* Get a single converted term from the converted stack. This is the
* dual of pushTerm() that is called after the term were removed
* from the todo stack and pushed to the converted stack.
* @return the new converted term.
*/
protected final Term getConverted() {
return mConverted.removeLast();
}
/**
* Get the converted terms from the converted stack. This is the
* dual of pushTerms() that is called after the term were removed
* from the todo stack and pushed to the converted stack. It takes
* the old terms as argument and checks for changes.
* @param oldArgs the original arguments.
* @return the new converted arguments. It will return the same array
* oldArgs if there were no changes.
*/
protected final Term[] getConverted(Term[] oldArgs) {
Term[] newArgs = oldArgs;
for (int i = oldArgs.length - 1; i >= 0; i--) {
final Term newTerm = getConverted();
if (newTerm != oldArgs[i]) {
if (newArgs == oldArgs) {
newArgs = oldArgs.clone();
}
newArgs[i] = newTerm;
}
}
return newArgs;
}
/**
* Collect the arguments of an application term from the converted stack
* and finish the conversion of appTerm. This is called after the arguments
* of appTerm have been converted. It will put the converted term on
* the converted stack and store it in the cache.
*/
protected static class BuildApplicationTerm implements Walker {
/** the application term to convert. */
private final ApplicationTerm mAppTerm;
public BuildApplicationTerm(ApplicationTerm term) {
mAppTerm = term;
}
@Override
public void walk(NonRecursive engine) {
final TermTransformer transformer = (TermTransformer) engine;
/* collect args and check if they have been changed */
final Term[] oldArgs = mAppTerm.getParameters();
final Term[] newArgs = transformer.getConverted(oldArgs);
transformer.convertApplicationTerm(mAppTerm, newArgs);
}
@Override
public String toString() {
return mAppTerm.getFunction().getApplicationString();
}
}
/**
* Walker that is called after the variable values are transformed
* and before the let body starts.
*/
protected static class StartLetTerm implements Walker {
/** the let term to convert. */
private final LetTerm mLetTerm;
public StartLetTerm(LetTerm term) {
mLetTerm = term;
}
@Override
public void walk(NonRecursive engine) {
final TermTransformer transformer = (TermTransformer) engine;
final Term[] values = transformer.getConverted(mLetTerm.getValues());
transformer.preConvertLet(mLetTerm, values);
}
@Override
public String toString() {
return "let" + Arrays.toString(mLetTerm.getVariables());
}
}
/**
* Collect the sub term and the values of a let term from the
* converted stack and finish the conversion of let term.
*/
protected static class BuildLetTerm implements Walker {
/** the let term to convert. */
private final LetTerm mLetTerm;
/** the converted values that are letted to the variables. */
private final Term[] mNewValues;
public BuildLetTerm(LetTerm term, Term[] newValues) {
mLetTerm = term;
mNewValues = newValues;
}
@Override
public void walk(NonRecursive engine) {
final TermTransformer transformer = (TermTransformer) engine;
final Term newBody = transformer.getConverted();
transformer.postConvertLet(mLetTerm, mNewValues, newBody);
transformer.endScope();
}
@Override
public String toString() {
return "let" + Arrays.toString(mLetTerm.getVariables());
}
}
/**
* Collect the sub term of a quantified formula and build the converted
* formula. The converted sub formula is expected to be on the
* converted stack.
* It stores the converted quantifier on the converted stack and in the
* cache.
*/
protected static class BuildQuantifier implements Walker {
/** the quantifier to convert. */
private final QuantifiedFormula mQuant;
public BuildQuantifier(QuantifiedFormula term) {
mQuant = term;
}
@Override
public void walk(NonRecursive engine) {
final TermTransformer transformer = (TermTransformer) engine;
final Term sub = transformer.getConverted();
transformer.postConvertQuantifier(mQuant, sub);
transformer.endScope();
}
@Override
public String toString() {
return mQuant.getQuantifier() == QuantifiedFormula.EXISTS
? "exists" : "forall";
}
}
/**
* Collect the sub term and annotations of an annotated formula from
* the converted stack. It converts the annotation and stores the
* result in the cache and on the converted stack.
* Note that only Annotations that are of type Term or Term[] are
* converted.
*/
protected static class BuildAnnotation implements Walker {
/** the annotated term. */
private final AnnotatedTerm mAnnotatedTerm;
public BuildAnnotation(AnnotatedTerm term) {
mAnnotatedTerm = term;
}
@Override
public void walk(NonRecursive engine) {
final TermTransformer transformer = (TermTransformer) engine;
final Annotation[] annots = mAnnotatedTerm.getAnnotations();
Annotation[] newAnnots = annots;
for (int i = annots.length - 1; i >= 0; i--) {
final Object value = annots[i].getValue();
Object newValue;
if (value instanceof Term) {
newValue = transformer.getConverted();
} else if (value instanceof Term[]) {
newValue = transformer.getConverted((Term[]) value);
} else {
newValue = value;
}
if (newValue != value) {
if (annots == newAnnots) {
newAnnots = annots.clone();
}
newAnnots[i] = new Annotation(annots[i].getKey(), newValue);
}
}
final Term sub = transformer.getConverted();
transformer.postConvertAnnotation(mAnnotatedTerm, newAnnots, sub);
}
@Override
public String toString() {
return "annotate";
}
}
@Override
public void reset() {
super.reset();
mConverted.clear();
mCache.clear();
}
}