/* * 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.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import xtc.Constants; import xtc.tree.Attribute; import xtc.util.Runtime; import xtc.util.Utilities; /** * Visitor to fold duplicate productions into a single production. * * <p />This visitor assumes that the entire grammar is contained in a * single module. * * @author Robert Grimm * @version $Revision: 1.36 $ */ public class DuplicateProductionFolder extends GrammarVisitor { /** The map of from folded nonterminals to replacement nonterminals. */ protected final Map<NonTerminal, NonTerminal> folded; /** * Create a new duplicate production folder. * * @param runtime The runtime. * @param analyzer The analyzer utility. */ public DuplicateProductionFolder(Runtime runtime, Analyzer analyzer) { super(runtime, analyzer); folded = new HashMap<NonTerminal, NonTerminal>(); } /** Visit the specified grammar. */ public Object visit(Module m) { // Initialize the per-grammar state. analyzer.register(this); analyzer.init(m); // Iterate over the grammar, folding duplicates, until none are // found. EquivalenceTester tester = new EquivalenceTester(); boolean foundDuplicates = false; boolean changed; do { // Clear the map of replacement nonterminals. folded.clear(); changed = false; // Compare all productions with each other. final int l = m.productions.size(); for (int i=0; i<l; i++) { FullProduction p1 = (FullProduction)m.productions.get(i); // Skip top-level productions, generic productions, and // productions that already have been folded. if (p1.hasAttribute(Constants.ATT_PUBLIC) || Generifier.isGeneric(p1) || analyzer.isMarked(p1.qName)) { continue; } // The nonterminal for the shared production, the shared // production, and the list of nonterminal names represented // by the shared production. NonTerminal nt = null; FullProduction shared = null; List<String> sources = null; boolean isTextOnly = false; boolean isToken = false; boolean hasOption = false; for (int j=i+1; j<l; j++) { FullProduction p2 = (FullProduction)m.productions.get(j); // Skip top-level productions and productions that are not // equivalent. if (p2.hasAttribute(Constants.ATT_PUBLIC) || (! tester.areEquivalent(p1, p2)) || (p1.getBooleanProperty(Properties.TEXT_ONLY) != p2.getBooleanProperty(Properties.TEXT_ONLY)) || (p1.getBooleanProperty(Properties.TOKEN) != p2.getBooleanProperty(Properties.TOKEN))) { continue; } // We found duplicate productions. foundDuplicates = true; changed = true; if (null == nt) { // This is the first duplicate pair for the production at // index i. nt = analyzer.shared(); shared = p1; if (p1.hasProperty(Properties.DUPLICATES)) { sources = Properties.getDuplicates(p1); } else { sources = new ArrayList<String>(); sources.add(p1.qName.toString()); } isTextOnly = p1.getBooleanProperty(Properties.TEXT_ONLY); isToken = p1.getBooleanProperty(Properties.TOKEN); hasOption = p1.hasProperty(Properties.OPTION); // Establish a mapping to the shared production. folded.put(p1.name, nt); } if (p2.hasProperty(Properties.DUPLICATES)) { sources.addAll(Properties.getDuplicates(p2)); } else { sources.add(p2.qName.toString()); } if (p2.hasProperty(Properties.OPTION)) { hasOption = true; } // Mark the production for future removal. analyzer.mark(p2.qName); // Establish a mapping to the shared production. folded.put(p2.name, nt); } if (null != nt) { // Create the shared production. shared = new FullProduction(new ArrayList<Attribute>(shared.attributes), shared.type, nt, nt.qualify(analyzer.module().name.name), shared.choice); shared.setProperty(Properties.DUPLICATES, sources); if (isTextOnly) { shared.setProperty(Properties.TEXT_ONLY, Boolean.TRUE); } if (isToken) { shared.setProperty(Properties.TOKEN, Boolean.TRUE); } if (hasOption) { shared.setProperty(Properties.OPTION, Boolean.TRUE); } // Replace the first duplicate with the shared production. analyzer.remove(p1); m.productions.remove(i); analyzer.startAdding(); analyzer.add(shared); analyzer.addNewProductionsAt(i); } } // If we have not found any duplicate productions, we fall out // of the loop. if (! changed) { break; } // Modify all nonterminals to reference the shared productions. for (Production p : m.productions) dispatch(p); // Remove duplicate productions from the grammar. for (Iterator<Production> iter=m.productions.iterator(); iter.hasNext();) { Production p = iter.next(); if (analyzer.isMarked(p.qName)) { analyzer.unmark(p.qName); analyzer.remove((FullProduction)p); iter.remove(); } } } while (changed); // Print debugging information. if (runtime.test("optionVerbose") && foundDuplicates) { Iterator iter = m.productions.iterator(); while (iter.hasNext()) { FullProduction p = (FullProduction)iter.next(); if (p.hasProperty(Properties.DUPLICATES)) { String lst = Utilities.format(Properties.getDuplicates(p)); System.err.println("[Folding " + lst + " into " + p.qName + ']'); } } } return null; } /** Visit the specified nonterminal. */ public Element visit(NonTerminal nt) { NonTerminal alt = folded.get(nt); return (null == alt)? nt : alt; } }