/* * 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); } }