/*
* 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.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import de.uni_freiburg.informatik.ultimate.logic.AnnotatedTerm;
import de.uni_freiburg.informatik.ultimate.logic.ApplicationTerm;
import de.uni_freiburg.informatik.ultimate.logic.FunctionSymbol;
import de.uni_freiburg.informatik.ultimate.logic.Term;
import de.uni_freiburg.informatik.ultimate.logic.Theory;
/**
* This class is used to convert a lemma of equality (congruence closure, CC).
*
* @author Christian Schilling
*/
public class LemmaCCConverter extends AConverter {
// true iff fast proofs shall be printed
private final boolean mFastProofs;
/**
* @param appendable appendable to write the proof to
* @param theory the theory
* @param converter term converter
* @param simplifier computation simplifier
*/
public LemmaCCConverter(final Appendable appendable, final Theory theory,
final TermConverter converter,
final ComputationSimplifier simplifier, final boolean fastProofs) {
super(appendable, theory, converter, simplifier);
mFastProofs = fastProofs;
}
/**
* This method converts a Congruence Closure (CC) lemma.
*
* The structure of a CC lemma is as follows:
* big disjunction A | B (length at least 2, order not determined)
* 0 or 1 positive equality A: 0 means 'False', else 1 ('diseq')
* n or n-1 negated equalities B
*
* The validity of the lemma is given by the rule of the excluded middle
* 'A | ~A', where here '~A ==> B', which is equivalent to '~B ==> A'.
* Note that B ('b_1 ~= b_2 | ... | b_(k-1) ~= b_k') becomes
* ('b_1 = b_2 & ... & b_(k-1) = b_k') when negated.
*
* Two cases are possible:
*
* 1) A exists. Then first the B literals are taken to the left hand side
* (negated). The sub-path information given by SMTInterpol is used to show
* the transitivity resulting in 'b_1 = b_k', which is A and hence finishes
* the proof.
* Sub-paths behave like lemmata which have to be sorted topologically and
* then be proven in Isabelle in the correct order.
* To find this order, a special data structure is used, see
* {@link SubPathInformation}.
*
* 2) A is 'False'. Then the transitivity rule applied to the negated
* B chain is of the form 'b_1 = b_k', which then is equal to 'False'
* according to the theory. The rest is as in case 1.
*
* @param lemma CC formula with sub-path information
* @return the result (may have been changed)
*/
public ApplicationTerm convert(final AnnotatedTerm lemma) {
// lemma that is proven
assert (lemma.getSubterm() instanceof ApplicationTerm);
ApplicationTerm result = (ApplicationTerm)lemma.getSubterm();
assert (result.getFunction() == mTheory.mOr);
/*
* annotations guiding the proof
* first annotation is the positive equality ('diseq', if existent)
* then (":subpath", transitivity chain explaining the main path)
* then (":subpath", transitivity chain explaining a path)^*
*/
final Object[] annotations =
(Object[])lemma.getAnnotations()[0].getValue();
/*
* check if there is a positive disjunct and find its position
* and make sure that it is in the front (used for proof structure)
*/
final boolean noDiseq = (annotations.length % 2 == 0);
assert (annotations.length >= 2);
// there is a positive disjunct, find it (perhaps create a new term)
if (!noDiseq) {
assert ((annotations.length >= 3)
&& (annotations[0] instanceof ApplicationTerm));
result = sortDiseq(result, (ApplicationTerm)annotations[0]);
}
// disjuncts
final Term[] disjuncts = result.getParameters();
assert (disjuncts.length > 1);
// header
mConverter.convert(result);
writeString("\"\nproof - {\n");
final int offset = noDiseq ? 0 : 1;
final Term[] mainpath = (Term[])annotations[1 + offset];
/*
* data structure for finding the path
* NOTE: exploits the fact that Java division is rounding down
*/
final SubPathInformation[] subpaths =
new SubPathInformation[annotations.length / 2];
for (int i = 0, annotIndex = 1 + offset; i < subpaths.length;
++i, annotIndex += 2) {
subpaths[i] =
new SubPathInformation((Term[])annotations[annotIndex], i);
}
final SubPathStack lemmaOrder = new SubPathStack(subpaths.length);
/* create hash maps for fast access */
// map from pair of terms to disjunct with their equality
final HashMap<TermTuple, ApplicationTerm> eq2disjunct =
new HashMap<TermTuple, ApplicationTerm>(
(int)((disjuncts.length - offset + 1) / 0.75) + 1);
for (int i = offset; i < disjuncts.length; ++i) {
assert (unquote(disjuncts[i]) != null);
final ApplicationTerm equality = unquote(disjuncts[i]);
final Term[] equalTerms = equality.getParameters();
assert (equalTerms.length == 2);
eq2disjunct.put(new TermTuple(equalTerms[0], equalTerms[1]),
equality);
}
// map from pair of terms to sub-path in between
final HashMap<TermTuple, SubPathInformation> eq2subpath =
new HashMap<TermTuple, SubPathInformation>(
(int)((annotations.length / 2) / 0.75) + 1);
for (int i = 0; i < subpaths.length; ++i) {
final Term[] subpath = subpaths[i].mSubpath;
final TermTuple pair =
new TermTuple(subpath[0], subpath[subpath.length - 1]);
assert(!eq2subpath.containsKey(pair));
eq2subpath.put(pair, subpaths[i]);
}
// construct lemmata for sub-paths
constructSubpathLemmata(subpaths, eq2subpath, eq2disjunct, lemmaOrder);
// proof of lemmata (in correct order)
proveLemmata(subpaths, disjuncts.length, mainpath, offset, lemmaOrder);
// finish proof
writeString("} qed\n");
return result;
}
/**
* This method sorts the disjunction if the diseq is not the first
* disjunct.
*
* @param disjunction the disjunction
* @param diseq the diseq
* @return the old disjunction if the diseq is the first disjunct, else a
* new disjunction
*/
private ApplicationTerm sortDiseq(final ApplicationTerm disjunction,
final ApplicationTerm diseq) {
assert (diseq.getFunction().getName() == "=");
final Term[] disjuncts = disjunction.getParameters();
for (int i = 0; i < disjuncts.length; ++i) {
if (disjuncts[i] instanceof AnnotatedTerm) {
assert (((AnnotatedTerm)disjuncts[i]).getSubterm() == diseq);
/*
* the diseq is not the first disjunct
* assert this in a new disjunction
* (needed for the proof structure)
*/
if (i > 0) {
final Term[] newDisjuncts = new Term[disjuncts.length];
newDisjuncts[0] = diseq;
System.arraycopy(disjuncts, 0, newDisjuncts, 1, i);
System.arraycopy(disjuncts, i + 1, newDisjuncts, i + 1,
newDisjuncts.length - i - 1);
return mTheory.term(mTheory.mOr, disjuncts);
} else {
return disjunction;
}
}
}
throw new IllegalArgumentException(
"The Diseq was not found, but should exist.");
}
/**
* This method sets up the structures to prove the lemmata for the
* sub-paths.
*
* @param subpaths the sub-paths
* @param eq2subpath mapping equality -> sub-path
* @param eq2disjunct mapping equality -> equality disjunct
* @param lemmaOrder stack
*/
private void constructSubpathLemmata(final SubPathInformation[] subpaths,
final HashMap<TermTuple, SubPathInformation> eq2subpath,
final HashMap<TermTuple, ApplicationTerm> eq2disjunct,
final SubPathStack lemmaOrder) {
// predefined objects (for pointer sharing, more efficient)
final AssumptionStep assumSym = new AssumptionStep(true);
final AssumptionStep assumNonsym = new AssumptionStep(false);
final CongruenceSubstep argsEqual = new CongruenceSubstep(null, false);
// construct lemmata for sub-paths
final SubPathStack stack =
new SubPathStack(subpaths[0], subpaths.length);
while (true) {
assert (!stack.isEmpty());
final SubPathInformation current = stack.top();
// current sub-path is finished, so pop it
if (current.isFinished()) {
current.finish();
stack.pop();
assert current.checkCorrectness();
// lemma can be proven with information at hand
lemmaOrder.push(current);
// last sub-path (= main path) finished
if (stack.isEmpty()) {
return;
}
stack.top().stepCongruence(current.mEqualities);
} else {
// current sub-path goes on
// check if equality follows from assumptions
final Term[] subpath = current.mSubpath;
final Term firstTerm = subpath[current.mLocalIndex];
final Term secondTerm = subpath[current.mLocalIndex + 1];
TermTuple pair = new TermTuple(firstTerm, secondTerm);
final ApplicationTerm assumption = eq2disjunct.get(pair);
// assumption
if (assumption != null) { // NOPMD
assert ((eq2subpath.get(pair) == null)
|| ((eq2subpath.get(pair).mSteps.length == 1)
&& ((eq2subpath.get(pair).mSteps[0] == null)
|| (eq2subpath.get(pair).mSteps[0]
instanceof AssumptionStep))));
current.stepAssumption(
assumption,
(firstTerm == assumption.getParameters()[0])
? assumNonsym : assumSym);
} else {
// no assumption, so must be a congruence
assert ((firstTerm instanceof ApplicationTerm)
&& (secondTerm instanceof ApplicationTerm));
final ApplicationTerm first = (ApplicationTerm)firstTerm;
final ApplicationTerm second = (ApplicationTerm)secondTerm;
// find sub-path for next arguments (if they are not equal)
assert ((first.getFunction() == second.getFunction())
&& (first.getParameters().length
== second.getParameters().length));
final ISubPathStep aStep =
current.mSteps[current.mLocalIndex];
final CongruenceStep cStep;
// first encounter
if (aStep == null) {
cStep = new CongruenceStep(
first.getParameters().length);
} else {
assert (aStep instanceof CongruenceStep);
cStep = (CongruenceStep)aStep;
}
if (!cStep.isFinished()) { // NOPMD
final int subindex = cStep.mIndex;
final Term argumentFirst =
first.getParameters()[subindex];
final Term argumentSecond =
second.getParameters()[subindex];
// arguments are equal, so skip them
if (argumentFirst == argumentSecond) {
cStep.mSubsteps[subindex] = argsEqual;
} else {
pair = new TermTuple(argumentFirst,
argumentSecond);
/* find correct sub-path
* do not look for assumptions, since the proof
* format ensures that there exists a sub-path
*/
final SubPathInformation next =
eq2subpath.get(pair);
assert (next != null);
final boolean symmetry =
(next.mSubpath[0] != argumentFirst);
assert ((!symmetry)
|| (next.mSubpath[0] == argumentSecond));
assert (cStep.mSubsteps[subindex] == null);
cStep.mSubsteps[subindex] =
new CongruenceSubstep(next, symmetry);
// sub-path not visited yet, compute it
if (!next.isFinished()) {
stack.push(next);
}
}
current.mSteps[current.mLocalIndex] = cStep;
} else {
current.stepCongruence(null);
}
}
}
}
}
/**
* This method constructs the proof for the lemmata in Isabelle.
*
* A fast proof is supported that concatenates each sub-lemma to one
* 'by'-proof. The proof is written with pre-built strings that may
* change to handle the last comma in the fast proof (it should be a
* closing parenthesis instead). This could also be achieved by writing
* the proof to a StringBuilder and replace the character.
*
* @param subpaths the sub-paths
* @param length length of the disjunction
* @param mainpath main path
* @param offset offset (0 if diseq is missing, else 1)
* @param lemmaOrder stack
*/
private void proveLemmata(final SubPathInformation[] subpaths,
final int length, final Term[] mainpath, final int offset,
final SubPathStack lemmaOrder) {
// strings for fast and slow proofs
final String startFast = mFastProofs ? "by (" : "";
final String startReset = mFastProofs ? "" : "apply (";
final String startNoParReset = mFastProofs ? "" : "apply ";
final String startCopy = mFastProofs ? ",\n" : "apply (";
final String startNoParCopy = mFastProofs ? ",\n" : "apply ";
final String nextLine = mFastProofs ? "" : ")\n";
final String nextLineNoPar = mFastProofs ? "" : "\n";
int index = 1;
for (final SubPathInformation currentpath : lemmaOrder.mStack) {
if (currentpath.isTrivial()) {
continue;
}
final Term[] subpath = currentpath.mSubpath;
// reset start string
String start = startReset;
String startNoPar = startNoParReset;
// proof of main-path using lemmata
if (currentpath.mGlobalIndex == 0) {
// start proving the whole CC lemma
writeString("show ?thesis\n");
writeString(startFast);
/*
* first disjunct is not present, so add the according literal
* equivalent to 'False'
*/
if (offset == 0) {
writeString(start);
writeString("rule cc_false [where p = \"");
mConverter.convertToAppendable(mainpath[0], mAppendable);
writeString(" = ");
mConverter.convertToAppendable(
mainpath[mainpath.length - 1], mAppendable);
writeString("\"], ");
writeString(mSimplifier.getRule());
writeString(nextLine);
// overwrite start string
start = startCopy;
}
// take each negative literal to get a big meta-implication
if (length - 1 - offset > 0) {
writeString(start);
writeString("intro cc_to_asm, rule cc_to_asm_bin");
writeString(nextLine);
} else {
assert (length - 1 - offset == 0);
writeString(start);
writeString("rule cc_to_asm_bin");
writeString(nextLine);
}
// overwrite start string
start = startCopy;
startNoPar = startNoParCopy;
} else {
// proof of a lemma
writeString("have ");
writeString(CC_LEMMA_PREFIX);
writeString(Integer.toString(index));
if (currentpath.mEqualities.isEmpty()) {
writeString(": \"");
} else {
writeString(": \"[|");
String append = "";
for (final ApplicationTerm equality : currentpath.mEqualities) {
writeString(append);
append = "; ";
mConverter.convertToAppendable(equality,
mAppendable);
}
writeString("|] ==> ");
}
mConverter.convertToAppendable(subpath[0], mAppendable);
writeString(" = ");
mConverter.convertToAppendable(
subpath[subpath.length - 1], mAppendable);
writeString("\"\n");
writeString(startFast);
}
final ISubPathStep[] steps = currentpath.mSteps;
for (int i = 0; i < steps.length; ++i) {
final ISubPathStep step = steps[i];
final Term secondTerm = subpath[i + 1];
// first step is always a transitivity step
if (i < steps.length - 1) {
writeString(start);
writeString("rule HOL.trans [where s = \"");
mConverter.convertToAppendable(secondTerm, mAppendable);
writeString("\"]");
writeString(nextLine);
// overwrite start string
start = startCopy;
startNoPar = startNoParCopy;
}
/* second step is either by assumption or by lemma */
// proof by assumption (possibly symmetric)
if (step instanceof AssumptionStep) {
if (((AssumptionStep)step).mSymmetry) {
writeString(start);
writeString("erule HOL.sym");
writeString(nextLine);
} else {
writeString(startNoPar);
writeString("assumption");
writeString(nextLineNoPar);
}
} else {
final CongruenceStep cStep = (CongruenceStep)step;
final CongruenceSubstep[] substeps = cStep.mSubsteps;
// make a copy of the function to change single arguments
assert (secondTerm instanceof ApplicationTerm);
final FunctionSymbol function =
((ApplicationTerm)secondTerm).getFunction();
final Term[] firstParameters =
((ApplicationTerm)subpath[i]).getParameters();
final Term[] secondParameters =
((ApplicationTerm)secondTerm).getParameters();
final Term[] parameters = Arrays.copyOf(secondParameters,
secondParameters.length);
// explain change in arguments by transitivity steps
for (int j = substeps.length - 1; j > 0; --j) { //NOPMD
// if the arguments are already equal, skip one step
if (parameters[j] == firstParameters[j]) {
continue;
}
// one transitivity step for one argument less
parameters[j] = firstParameters[j];
final ApplicationTerm tmpF =
mTheory.term(function, parameters);
writeString(start);
writeString("rule HOL.trans [where s = \"");
mConverter.convertToAppendable(tmpF, mAppendable);
writeString("\"]");
writeString(nextLine);
}
// explain congruence steps
for (int j = 0; j < substeps.length; ++j) {
// if the arguments are already equal, skip one step
if (secondParameters[j] == firstParameters[j]) {
continue;
}
writeString(start);
writeString("rule cc_cong [where x = \"");
mConverter.convertToAppendable(
firstParameters[j], mAppendable);
writeString("\"]");
writeString(nextLine);
final CongruenceSubstep congruence = substeps[j];
// lemma is trivial, so it was not created
final SubPathInformation lemmaSubpath =
congruence.mSubpath;
if (lemmaSubpath.isTrivial()) {
/* symmetry can come from both lemma and current,
* so in case of both do not use symmetry
*/
final boolean symmetry = (congruence.mSymmetry)
^ (((AssumptionStep)lemmaSubpath.mSteps[0]).
mSymmetry);
if (symmetry) {
writeString(start);
writeString("erule HOL.sym");
writeString(nextLine);
} else {
writeString(startNoPar);
writeString("assumption");
writeString(nextLineNoPar);
}
} else {
writeString(start);
writeString("drule (");
writeString(Integer.toString(
congruence.mSubpath.mEqualities.size()));
writeString(") ");
writeString(CC_LEMMA_PREFIX);
writeString(Integer.toString(
congruence.mSubpath.mLemmaNumber));
if (congruence.mSymmetry) {
writeString("[symmetric]");
}
writeString(nextLine);
}
}
}
}
/* finish sub-proof */
// replace last comma by a closing parenthesis
if (mFastProofs) {
writeString(")\n");
} else {
writeString("done\n");
}
// assign lemma number
assert (currentpath.mLemmaNumber == 0);
currentpath.mLemmaNumber = index;
++index;
}
}
/**
* This method unpacks the term from the negated :quoted literals.
*
* @param term the quoted term (must be an AnnotatedTerm)
* @return the inner term
*/
private ApplicationTerm unquote(Term term) {
assert ((term instanceof ApplicationTerm)
&& (((ApplicationTerm)term).getFunction() == mTheory.mNot)
&& (((ApplicationTerm)term).getParameters().length == 1)
&& (((ApplicationTerm)term).getParameters()[0]
instanceof AnnotatedTerm)
&& (((AnnotatedTerm)(((ApplicationTerm)term).getParameters()[0])).
getAnnotations().length == 1)
&& (((AnnotatedTerm)(((ApplicationTerm)term).getParameters()[0])).
getAnnotations()[0].getKey()
== ":quoted")
&& (((AnnotatedTerm)(((ApplicationTerm)term).getParameters()[0])).
getSubterm() instanceof ApplicationTerm)
&& (((ApplicationTerm)((AnnotatedTerm)(((ApplicationTerm)term).
getParameters()[0])).getSubterm()).getFunction().
getName() == "="));
return (ApplicationTerm)((AnnotatedTerm)
(((ApplicationTerm)term).getParameters()[0])).getSubterm();
}
/**
* This class is an unsorted pair of two terms.
* It is used for hashing equality terms, since the order can alter.
*/
private class TermTuple {
// first and second term
final Term mFirst, mSecond;
/**
* @param first first term
* @param second second term
*/
public TermTuple(final Term first, final Term second) {
mFirst = first;
mSecond = second;
}
/**
* The hash code is just the sum of the single hash codes.
* {@inheritDoc}
*/
@Override
public int hashCode() {
return mFirst.hashCode() + mSecond.hashCode();
}
/**
* Two objects are equal if they have the same terms (order ignored).
* {@inheritDoc}
*/
@Override
public boolean equals(final Object other) {
assert (other instanceof TermTuple);
final TermTuple tp = (TermTuple)other;
if (tp.mFirst == mFirst) {
return tp.mSecond == mSecond;
} else if (tp.mFirst == mSecond) {
return tp.mSecond == mFirst;
}
return false;
}
@Override
public String toString() {
return "{{" + mFirst.toStringDirect() + ", "
+ mSecond.toStringDirect() + "}}";
}
}
/**
* This interface represents a step in a sub-path annotation in the proof
* by SMTInterpol. A step can be justified either by an assumption or by
* another sub-path, which means by congruence.
*/
private interface ISubPathStep {
/**
* A step can be justified either by assumption or by congruence.
*
* @return true iff the step is justified by an assumption
*/
public boolean isAssumption();
}
/**
* An assumption step justifies an equality of two terms that is given in
* the assumptions in Isabelle, which means is given as a disjunct in the
* lemma.
*
* The object only has to know if symmetry must be used.
*/
private class AssumptionStep implements ISubPathStep {
// symmetry information
final boolean mSymmetry;
/**
* @param symmetry true iff symmetry rule has to be used
*/
public AssumptionStep(final boolean symmetry) {
mSymmetry = symmetry;
}
/**
* @return true
*/
@Override
public boolean isAssumption() {
return true;
}
@Override
public String toString() {
if (mSymmetry) {
return "{{A, t}}";
}
return "{{A, f}}";
}
}
/**
* A congruence step justifies an equality of two function terms, where
* the function symbols are the same, but at least one argument differs.
* Then for each different argument another sub-path information exists,
* proving the equality, even if it is trivial from the assumptions.
*
* The single argument equalities are given as sub-steps.
*/
private class CongruenceStep implements ISubPathStep {
// sub-steps array
final CongruenceSubstep[] mSubsteps;
// index in the steps array
int mIndex;
/**
* @param size number of arguments
*/
public CongruenceStep(final int size) {
mSubsteps = new CongruenceSubstep[size];
mIndex = 0;
}
/**
* @return false
*/
@Override
public boolean isAssumption() {
return false;
}
/**
* This method tells if the sub-steps are finished.
*
* @return true iff every argument equality is justified
*/
public boolean isFinished() {
/*
* Equal arguments are judged by a placeholder object,
* so no sub-step must be null.
*/
return ((mIndex == mSubsteps.length - 1)
&& (mSubsteps[mIndex] != null));
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("{{C, ");
String append = "";
for (int i = 0; i < mSubsteps.length; ++i) {
builder.append(append);
append = ", ";
builder.append('(');
if (mSubsteps[i] == null) {
builder.append("null");
} else {
builder.append(mSubsteps[i].mSubpath.mGlobalIndex);
builder.append(", ");
builder.append(mSubsteps[i].mSymmetry);
}
builder.append(')');
}
builder.append("}}");
return builder.toString();
}
}
/**
* A congruence sub-step justifies a single equality of two arguments.
* Due to the fact that each non-trivial equality (no syntactical equality)
* is justified by another sub-path, there is no need for recursive
* application of steps, but the sub-steps are final.
*
* The sub-step has to know the sub-path and if symmetry must be used.
*/
private class CongruenceSubstep {
// sub-path explaining the congruence
final SubPathInformation mSubpath;
// symmetry information
final boolean mSymmetry;
/**
* @param subpath sub-path explaining the equality
* @param symmetry true iff symmetry rule has to be used
*/
public CongruenceSubstep(final SubPathInformation subpath,
final boolean symmetry) {
mSubpath = subpath;
mSymmetry = symmetry;
}
@Override
public String toString() {
return "(" + mSubpath.mGlobalIndex + ", " + mSymmetry + ")";
}
}
/**
* This class is the data structure for CC lemmata.
* Each sub-path object from the proof annotation is wrapped into an
* object of this class.
*
* The additional information is the steps array (see {@link ISubPathStep}
* with the current index (reading from left to right), the global index
* used to store the objects and a collection of equalities needed for
* this sub-path.
*
* Equalities are not stored for the main path, since that would be all
* equalities and hence just a waste of memory.
*/
private class SubPathInformation {
// sub-path
final Term[] mSubpath;
// equalities needed for the proof
Collection<ApplicationTerm> mEqualities;
// proof for single steps
final ISubPathStep[] mSteps;
// current index in the sub-path
int mLocalIndex;
// index in the array of sub-paths
final int mGlobalIndex;
// lemma number assigned to this sub-path
int mLemmaNumber;
/**
* @param subpath sub-path wrapped
* @param index global index used to store this object
*/
public SubPathInformation(final Term[] subpath, final int index) {
mSubpath = subpath;
mEqualities = (index > 0) ? new HashSet<ApplicationTerm>() : null;
mSteps = new ISubPathStep[mSubpath.length - 1];
mLocalIndex = 0;
mGlobalIndex = index;
}
/**
* The next step is considered, where the last step was justified by
* an assumption.
*
* @param equality equality of the assumption (must be remembered)
* @param step assumption step (for object sharing, only two existent)
*/
public void stepAssumption(final ApplicationTerm equality,
final AssumptionStep step) {
// main path does not need to store equalities (would be all)
if (mGlobalIndex > 0) {
assert (mEqualities instanceof Set);
mEqualities.add(equality);
}
mSteps[mLocalIndex] = step;
++mLocalIndex;
}
/**
* The next step is considered, where the last step was justified by
* a congruence. This method is used for both the whole step and
* the sub-steps.
*
* @param equalities collection of equalities for sub-steps, else null
*/
public void stepCongruence(
final Collection<ApplicationTerm> equalities) {
// main path does not need to store equalities (would be all)
if ((equalities != null) && (mGlobalIndex > 0)) {
mEqualities.addAll(equalities);
}
assert ((mSteps[mLocalIndex] instanceof AssumptionStep)
|| ((mSubpath[mLocalIndex] instanceof ApplicationTerm)
&& (((ApplicationTerm)mSubpath[mLocalIndex]).
getParameters().length > 0)));
if (mSteps[mLocalIndex] instanceof CongruenceStep) {
final CongruenceStep currentStep =
(CongruenceStep)mSteps[mLocalIndex];
if (currentStep.isFinished()) {
++mLocalIndex;
} else {
++currentStep.mIndex;
}
} else {
++mLocalIndex;
}
}
/**
* This method tells if the sub-path is finished.
*
* @return true iff every step is justified
*/
public boolean isFinished() {
return (mLocalIndex == mSubpath.length - 1);
}
/**
* This method is used in the end to make the Isabelle proof shorter.
*
* A sub-path that only explains an equality from the assumptions
* needs not be given as a lemma, but often occurs due to the fact that
* a congruence is always justified by sub-paths.
*
* Then instead of using a lemma, the proof directly uses the
* assumption.
*
* @return true iff the step follows from the assumptions
*/
public boolean isTrivial() {
assert isFinished();
return ((mSteps.length == 1)
&& (mSteps[0] instanceof AssumptionStep));
}
/**
* This method finalizes an object. This means the hash set is
* copied to an array list to save some memory and have faster
* iteration later.
*/
public void finish() {
if (mGlobalIndex > 0) {
final ArrayList<ApplicationTerm> newEq =
new ArrayList<ApplicationTerm>(mEqualities.size());
newEq.addAll(mEqualities);
mEqualities = newEq;
}
}
/**
* This method is only used for asserting correct behavior.
*
* @return true iff data structure is handled correctly
*/
boolean checkCorrectness() {
for (final ISubPathStep step : mSteps) {
if (step == null) {
return false;
} else {
if (step instanceof CongruenceStep) {
final CongruenceStep cStep = (CongruenceStep)step;
for (final CongruenceSubstep substep : cStep.mSubsteps) {
if (substep == null) {
return false;
}
}
}
}
}
return true;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder(64);
builder.append("{subpath ");
builder.append(mGlobalIndex);
builder.append(", loc ");
builder.append(mLocalIndex);
builder.append(", eq = ");
if (mEqualities == null) {
builder.append("null");
} else {
builder.append(mEqualities.toString());
}
builder.append(", steps = [");
String append = "";
for (int i = 0; i < mSteps.length; ++i) {
builder.append(append);
append = ", ";
if (mSteps[i] == null) {
builder.append("null");
} else {
builder.append(mSteps[i].toString());
}
}
builder.append("]}");
return builder.toString();
}
}
/**
* This class represents a stack used by the CC lemma conversion.
*
* It is used in two places:
*
* 1) The order in which the lemmata are to be introduced in Isabelle
* is given by the order on such a stack.
*
* 2) The sub-paths are handled in depth-first manner, which needs a stack.
*/
private class SubPathStack {
// stack
final SubPathInformation[] mStack;
// index of the current element
int mIndex;
/**
* This constructor is used for storing the order of the lemmata.
*
* @param size the size of the stack
*/
public SubPathStack(final int size) {
mStack = new SubPathInformation[size];
mIndex = -1;
}
/**
* This constructor is used for the sub-path inspection.
*
* @param first the main path, stored at the bottom
* @param size the size of the stack
*/
public SubPathStack(final SubPathInformation first, final int size) {
mStack = new SubPathInformation[size];
mIndex = 0;
mStack[0] = first;
}
/**
* @return true iff the stack is empty
*/
public boolean isEmpty() {
return mIndex < 0;
}
/**
* This method returns the top-most element on the stack without
* removing it.
*
* @return the top-most element
*/
public SubPathInformation top() {
assert (mIndex >= 0 && mIndex < mStack.length);
return mStack[mIndex];
}
/**
* This method pushes an element on the stack.
*
* @param next new element
*/
public void push(final SubPathInformation next) {
assert (mIndex + 1 < mStack.length);
mStack[++mIndex] = next;
}
/**
* This method removes the top-most element. Since the size of the
* stack is constant, the element is not really removed, but only
* the index is decremented.
*/
public void pop() {
--mIndex;
}
@Override
public String toString() {
return "{{" + mStack.toString() + ", " + mIndex + "}}";
}
}
}