/*
* xtc - The eXTensible Compiler
* Copyright (C) 2004-2007 Robert Grimm
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*/
package xtc.parser;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import xtc.tree.Attribute;
import xtc.tree.Visitor;
/**
* Visitor to test productions and elements for equivalence. This
* visitor tests whether two productions or two elements are
* structurally the same, modulo using different variable names and
* having different nonterminals (for productions). It should only be
* invoked through the {@link #areEquivalent(Production,Production)}
* and {@link #areEquivalent(Element,Element)} methods.
*
* @author Robert Grimm
* @version $Revision: 1.36 $
*/
public class EquivalenceTester extends Visitor {
/** The mapping between equivalent nonterminals. */
protected Map<String, String> nts;
/** The mapping between equivalent variables. */
protected Map<String, String> vars;
/** The element to compare to. */
protected Element e2;
/** Create a new equivalence tester. */
public EquivalenceTester() {
nts = new HashMap<String, String>();
vars = new HashMap<String, String>();
}
/**
* Determine whether the specified productions are equivalent.
*
* @param p1 The first production.
* @param p2 The second production.
* @return <code>true</code> if the two productions are equivalent.
*/
public boolean areEquivalent(Production p1, Production p2) {
if (! Attribute.areEquivalent(p1.attributes, p2.attributes)) return false;
if (null == p1.type) {
if (! p1.dType.equals(p2.dType)) return false;
} else {
if (! p1.type.equals(p2.type)) return false;
}
nts.put(p2.name.name, p1.name.name);
boolean result = areEquivalent(p1.choice, p2.choice);
nts.remove(p2.name.name);
return result;
}
/**
* Determine whether the specified elements are equivalent.
*
* @param e1 The first element.
* @param e2 The second element.
* @return <code>true</code> if the two elements are equivalent.
*/
public boolean areEquivalent(Element e1, Element e2) {
if ((null == e1) || (null == e2)) return (e1 == e2);
if (! e1.getClass().equals(e2.getClass())) return false;
this.e2 = e2;
return (Boolean)dispatch(e1);
}
/**
* Determine whether the specified lists of bindings are equivalent.
*
* @param l1 The first list.
* @param l2 The second list.
* @return <code>true</code> if the two lists are equivalent.
*/
protected boolean areEquivalent(List<Binding> l1, List<Binding> l2) {
if (l1.size() != l2.size()) return false;
Iterator<Binding> iter1 = l1.iterator();
Iterator<Binding> iter2 = l2.iterator();
while (iter1.hasNext()) {
String var1 = iter1.next().name;
String var2 = iter2.next().name;
if ((! var1.equals(var2)) && (! var1.equals(vars.get(var2)))) {
return false;
}
}
return true;
}
/**
* Determine whether the specified generic values are equivalent.
*
* @param v1 The first value.
* @param v2 The second value.
* @return <code>true</code> if the two values are equivalent.
*/
protected boolean areEquivalent(GenericValue v1, GenericValue v2) {
if ((! v1.name.equals(v2.name)) && (! v1.name.equals(nts.get(v2.name)))) {
return false;
}
return areEquivalent(v1.children, v2.children);
}
/** Visit the specified ordered choice. */
public Boolean visit(OrderedChoice c1) {
OrderedChoice c2 = (OrderedChoice)e2;
final int l = c1.alternatives.size();
if (l != c2.alternatives.size()) {
return Boolean.FALSE;
} else {
for (int i=0; i<l; i++) {
if (! areEquivalent(c1.alternatives.get(i),
c2.alternatives.get(i))) {
return Boolean.FALSE;
}
}
return Boolean.TRUE;
}
}
/** Visit the specified repetition. */
public Boolean visit(Repetition r1) {
Repetition r2 = (Repetition)e2;
if (r1.once != r2.once) {
return Boolean.FALSE;
} else {
return areEquivalent(r1.element, r2.element);
}
}
/** Visit the specified sequence. */
public Boolean visit(Sequence s1) {
Sequence s2 = (Sequence)e2;
final int l = s1.elements.size();
if (l != s2.elements.size()) {
return Boolean.FALSE;
} else {
for (int i=0; i<l; i++) {
if (! areEquivalent(s1.elements.get(i),
s2.elements.get(i))) {
return Boolean.FALSE;
}
}
return Boolean.TRUE;
}
}
/** Visit the specified binding. */
public Boolean visit(Binding b1) {
Binding b2 = (Binding)e2;
if (b1.name.equals(b2.name)) {
return areEquivalent(b1.element, b2.element);
} else {
String alt = vars.get(b2.name);
if (null == alt) {
// There is no mapping between b1 and b2. Try it.
vars.put(b2.name, b1.name);
boolean result = areEquivalent(b1.element, b2.element);
vars.remove(b2.name);
return result;
} else if (! b1.name.equals(alt)) {
// There is a mapping and it is different.
return Boolean.FALSE;
} else {
// There is a mapping and it fits.
return areEquivalent(b1.element, b2.element);
}
}
}
/** Visit the specified string match. */
public Boolean visit(StringMatch m1) {
StringMatch m2 = (StringMatch)e2;
return m1.text.equals(m2.text) && areEquivalent(m1.element, m2.element);
}
/** Visit the specified nonterminal. */
public Boolean visit(NonTerminal nt1) {
NonTerminal nt2 = (NonTerminal)e2;
return nt1.equals(nt2) || nt1.name.equals(nts.get(nt2.name));
}
/** Visit the specified character switch. */
public Boolean visit(CharSwitch s1) {
CharSwitch s2 = (CharSwitch)e2;
final int l = s1.cases.size();
if (l != s2.cases.size()) {
return Boolean.FALSE;
} else {
for (int i=0; i<l; i++) {
CharCase c1 = s1.cases.get(i);
CharCase c2 = s2.cases.get(i);
if (! areEquivalent(c1.klass, c2.klass)) {
return Boolean.FALSE;
} else if (! areEquivalent(c1.element, c2.element)) {
return Boolean.FALSE;
}
}
}
if (null == s1.base) {
return null == s2.base;
} else {
if (null == s2.base) {
return Boolean.FALSE;
} else {
return areEquivalent(s1.base, s2.base);
}
}
}
/** Visit the specified parse tree node. */
public Boolean visit(ParseTreeNode n1) {
ParseTreeNode n2 = (ParseTreeNode)e2;
if (! areEquivalent(n1.predecessors, n2.predecessors)) {
return Boolean.FALSE;
}
if ((null == n1.node) != (null == n2.node)) return Boolean.FALSE;
if ((null != n1.node) &&
(! n1.node.name.equals(n2.node.name)) &&
(! n1.node.name.equals(vars.get(n2.node.name)))) {
return Boolean.FALSE;
}
return areEquivalent(n1.successors, n2.successors);
}
/** Visit the specified proper list value. */
public Boolean visit(ProperListValue v1) {
ProperListValue v2 = (ProperListValue)e2;
if (! v1.type.equals(v2.type)) return Boolean.FALSE;
if (! areEquivalent(v1.elements, v2.elements)) return Boolean.FALSE;
if ((null == v1.tail) != (null == v2.tail)) return Boolean.FALSE;
return ((null == v1.tail) ||
v1.tail.name.equals(v2.tail.name) ||
v1.tail.name.equals(vars.get(v2.tail.name)));
}
/** Visit the specified binding value. */
public Boolean visit(BindingValue v1) {
BindingValue v2 = (BindingValue)e2;
return (v1.binding.name.equals(v2.binding.name) ||
v1.binding.name.equals(vars.get(v2.binding.name)));
}
/** Visit the specified action base value. */
public Boolean visit(ActionBaseValue v1) {
ActionBaseValue v2 = (ActionBaseValue)e2;
return ((v1.list.name.equals(v2.list.name) ||
v1.list.name.equals(vars.get(v2.list.name))) &&
(v1.seed.name.equals(v2.seed.name) ||
v1.seed.name.equals(vars.get(v2.seed.name))));
}
/** Visit the specified generic node value. */
public Boolean visit(GenericNodeValue v1) {
GenericNodeValue v2 = (GenericNodeValue)e2;
return areEquivalent(v1, v2) && areEquivalent(v1.formatting, v2.formatting);
}
/** Visit the specified generic action value. */
public Boolean visit(GenericActionValue v1) {
GenericActionValue v2 = (GenericActionValue)e2;
if (! areEquivalent(v1, v2)) return Boolean.FALSE;
if ((! v1.first.equals(v2.first)) &&
(! v1.first.equals(vars.get(v2.first)))) {
return Boolean.FALSE;
}
return areEquivalent(v1.formatting, v2.formatting);
}
/** Visit the specified generic recursion value. */
public Boolean visit(GenericRecursionValue v1) {
GenericRecursionValue v2 = (GenericRecursionValue)e2;
if (! areEquivalent(v1, v2)) return Boolean.FALSE;
if ((! v1.first.equals(v2.first)) &&
(! v1.first.equals(vars.get(v2.first)))) {
return Boolean.FALSE;
}
if (! areEquivalent(v1.formatting, v2.formatting)) {
return Boolean.FALSE;
}
return (v1.list.name.equals(v2.list.name) ||
v1.list.name.equals(vars.get(v2.list.name)));
}
/**
* Visit the specified unary operator. This method provides the
* default implementation for options, predicates, voided elements,
* and parser actions.
*/
public Boolean visit(UnaryOperator op1) {
UnaryOperator op2 = (UnaryOperator)e2;
return areEquivalent(op1.element, op2.element);
}
/**
* Visit the specified element. This method provides the default
* implementation for terminals, node markers, null literals,
* actions, and null, string, text, empty list, generic string, and
* generic text values.
*/
public Boolean visit(Element e1) {
return e1.equals(e2);
}
}