/* * Copyright (C) 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.logic; import de.uni_freiburg.informatik.ultimate.util.datastructures.ScopedHashMap; /** * This class checks if two terms are syntactically equivalent modulo * renaming of variables. E. g., * <code>(let ((x 0)) x)</code> is equivalent to <code>(let ((y 0)) y)</code>, * but not to <code>0</code> or <code>(let ((y 0)) 0)</code>. * * @author Juergen Christ */ public class TermEquivalence extends NonRecursive { private final ScopedHashMap<TermVariable, TermVariable> mRenaming = new ScopedHashMap<TermVariable, TermVariable>(); private void beginScope() { mRenaming.beginScope(); } private void endScope() { mRenaming.endScope(); } private void addRenaming(TermVariable lvar, TermVariable rvar) { mRenaming.put(lvar, rvar); } private boolean checkRenaming(TermVariable lvar, TermVariable rvar) { return mRenaming.get(lvar) == rvar; } @SuppressWarnings("serial") private static final class NotEq extends RuntimeException { // Empty control flow exception } private final static class EndScope implements Walker { public final static EndScope INSTANCE = new EndScope(); @Override public void walk(NonRecursive engine) { final TermEquivalence te = (TermEquivalence) engine; te.endScope(); } } private final static class AddRenaming implements Walker { private final TermVariable mLvar, mRvar; public AddRenaming(TermVariable lvar, TermVariable rvar) { mLvar = lvar; mRvar = rvar; } @Override public void walk(NonRecursive engine) { final TermEquivalence te = (TermEquivalence) engine; te.addRenaming(mLvar, mRvar); } } private final static class TermEq implements Walker { private final Term mLhs, mRhs; public TermEq(Term lhs, Term rhs) { mLhs = lhs; mRhs = rhs; } private final void notEqual() { throw new NotEq(); } @Override public void walk(NonRecursive engine) { final TermEquivalence te = (TermEquivalence) engine; if (mLhs != mRhs) { if (mLhs.getClass() != mRhs.getClass()) { // Cannot be equal notEqual(); } if (mLhs instanceof ApplicationTerm) { final ApplicationTerm l = (ApplicationTerm) mLhs; final ApplicationTerm r = (ApplicationTerm) mRhs; if (l.getFunction() != r.getFunction()) { notEqual(); } final Term[] lparams = l.getParameters(); final Term[] rparams = r.getParameters(); if (lparams.length != rparams.length) { notEqual(); } for (int i = 0; i < lparams.length; ++i) { te.enqueueWalker(new TermEq(lparams[i], rparams[i])); } } else if (mLhs instanceof AnnotatedTerm) { final AnnotatedTerm l = (AnnotatedTerm) mLhs; final AnnotatedTerm r = (AnnotatedTerm) mRhs; final Annotation[] lannot = l.getAnnotations(); final Annotation[] rannot = r.getAnnotations(); if (rannot.length != lannot.length) { notEqual(); } for (int i = 0; i < lannot.length; ++i) { if (!lannot[i].getKey().equals(rannot[i].getKey())) { notEqual(); } if (lannot[i].getValue() instanceof Term && rannot[i].getValue() instanceof Term) { te.enqueueWalker(new TermEq( (Term) lannot[i].getValue(), (Term) rannot[i].getValue())); } else if (lannot[i].getValue() instanceof Term[] && rannot[i].getValue() instanceof Term[]) { final Term[] lv = (Term[]) lannot[i].getValue(); final Term[] rv = (Term[]) lannot[i].getValue(); if (lv.length != rv.length) { notEqual(); } for (int j = 0; j < lv.length; ++j) { te.enqueueWalker(new TermEq(lv[j], rv[j])); } } else if (!lannot[i].getValue().equals( rannot[i].getValue())) { notEqual(); } } } else if (mLhs instanceof LetTerm) { final LetTerm llet = (LetTerm) mLhs; final LetTerm rlet = (LetTerm) mRhs; final TermVariable[] lvars = llet.getVariables(); final TermVariable[] rvars = rlet.getVariables(); if (lvars.length != rvars.length) { notEqual(); } te.enqueueWalker(EndScope.INSTANCE); te.enqueueWalker( new TermEq(llet.getSubTerm(), rlet.getSubTerm())); final Term[] lvals = llet.getValues(); final Term[] rvals = rlet.getValues(); for (int i = 0; i < lvars.length; ++i) { te.enqueueWalker(new AddRenaming(lvars[i], rvars[i])); te.enqueueWalker(new TermEq(lvals[i], rvals[i])); } // te.enqueueWalker(BeginScope.INSTANCE); te.beginScope(); } else if (mLhs instanceof QuantifiedFormula) { final QuantifiedFormula lq = (QuantifiedFormula) mLhs; final QuantifiedFormula rq = (QuantifiedFormula) mRhs; if (lq.getQuantifier() != rq.getQuantifier()) { notEqual(); } final TermVariable[] lv = lq.getVariables(); final TermVariable[] rv = rq.getVariables(); if (lv.length != rv.length) { notEqual(); } te.enqueueWalker(EndScope.INSTANCE); te.beginScope(); for (int i = 0; i < lv.length; ++i) { if (lv[i] != rv[i]) { if (lv[i].getSort() != rv[i].getSort()) { notEqual(); } te.addRenaming(lv[i], rv[i]); } } te.enqueueWalker( new TermEq(lq.getSubformula(), rq.getSubformula())); } else if (mLhs instanceof TermVariable) { final TermVariable lv = (TermVariable) mLhs; final TermVariable rv = (TermVariable) mRhs; if (!te.checkRenaming(lv, rv)) { notEqual(); } } // Term case switch } } } /** * Returns true if the terms are equivalent. * @param lhs the left hand side term. * @param rhs the right hand side term. * @return true if the terms are equivalent modulo variable renaming. */ public boolean equal(Term lhs, Term rhs) { try { run(new TermEq(lhs, rhs)); return true; } catch (final NotEq ignored) { reset(); return false; } } }