/* * xtc - The eXTensible Compiler * Copyright (C) 2004-2008 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 xtc.Constants; import xtc.util.Runtime; import xtc.type.AST; /** * Visitor to expand choices by inlining productions. This visitor * inlines transient productions if they are essentially the only * element appearing in ordered choices' alternatives. * * <p />Note that this visitor requires that text-only productions * have been marked as such, that all {@link ValueElement value * elements} have been added to the grammar (notably, for generic * productions), and that the grammar's productions have been {@link * ReferenceCounter reference counted}. Also note that this visitor * may result in new opportunities for the {@link * DeadProductionEliminator elimination of dead productions}. * * <p />This visitor assumes that the entire grammar is contained in a * single module. * * @author Robert Grimm * @version $Revision: 1.36 $ */ public class ChoiceExpander extends GrammarVisitor { /** * The processing mode. In regular mode, this visitor traverses a * grammar and inlines into choices; in remove values mode, it * removes value elements; in void values mode, it converts all * value elements into null value elements; and in text values mode, * it converts all value elements into text value elements. */ public static enum Mode { /** Traverse grammar and inline into choices. */ INLINE, /** Remove value elements. */ RM_VALUES, /** Convert value elements into null value elements. */ VOID_VALUES, /** Convert value elements into text value elements. */ TEXT_VALUES, /** Convert value elements into token value elements. */ TOKEN_VALUES } /** The flag for whether the grammar has the state attribute. */ protected boolean hasState; /** The current processing mode. */ protected Mode mode; /** * Create a new choice expander. * * @param runtime The runtime. * @param analyzer The analyzer utility. */ public ChoiceExpander(Runtime runtime, Analyzer analyzer) { super(runtime, analyzer); } /** * Determine whether the specified alternative is a candidate for * replacement. If the alternative is a candidate, this method * returns the nonterminal for inlining. Otherwise, it returns * <code>null</code>. * * @param alternative The alternative. * @param top The flag for whether the alternative appears in a * top-level choice. * @return The candidate nonterminal. */ protected NonTerminal candidate(Sequence alternative, boolean top) { Element e = Analyzer.strip(alternative); if (e instanceof Binding) { // Generally, we accept a top-level alternative containing a // nonterminal that is bound to yyValue. Binding b = (Binding)e; if (top && CodeGenerator.VALUE.equals(b.name) && (b.element instanceof NonTerminal)) { return (NonTerminal)b.element; } } else if (AST.isVoid(analyzer.current().type) || analyzer.current().getBooleanProperty(Properties.TEXT_ONLY) || analyzer.current().getBooleanProperty(Properties.TOKEN)) { // However, for void, text-only, and token-level productions, we // also accept lone nonterminals or a nonterminal followed by a // value element. if (e instanceof NonTerminal) { return (NonTerminal)e; } else if (e instanceof Sequence) { Sequence s = (Sequence)e; if ((2 == s.size()) && (s.get(0) instanceof NonTerminal) && (s.get(1) instanceof ValueElement)) { return (NonTerminal)s.get(0); } } } return null; } /** * Record that the specified production has been inlined and, if * necessary, print a message to the console. * * @param p The production. */ protected void inlined(Production p) { if (runtime.test("optionVerbose")) { System.err.println("[Inlining " + p.qName + " into " + analyzer.current().qName + "]"); } } /** Visit the specified grammar. */ public Object visit(Module m) { // Initialize the per-grammar state. analyzer.register(this); analyzer.init(m); hasState = m.hasAttribute(Constants.ATT_STATEFUL.getName()); for (Production p : m.productions) { mode = Mode.INLINE; if (runtime.test("optimizeChoices2") || AST.isVoid(p.type) || p.getBooleanProperty(Properties.TEXT_ONLY) || p.getBooleanProperty(Properties.TOKEN)) { analyzer.process(p); } } return null; } /** Visit the specified ordered choice. */ public Element visit(OrderedChoice c) { boolean top = isTopLevel; isTopLevel = false; boolean last = isLastElement; isLastElement = false; for (int i=0; i<c.alternatives.size(); i++) { Sequence alternative = c.alternatives.get(i); // Only inline if we are in the correct mode. if (Mode.INLINE == mode) { NonTerminal date = candidate(alternative, top); if (null != date) { Production p = analyzer.lookup(date); MetaData md = (MetaData)p.getProperty(Properties.META_DATA); // If a void, text-only, or token-level production is // transient/inline, is not explicit, does not reference // itself, and does not have the state or reset attribute, // inline a copy of it. If the production is neither void, // text-only, nor token-level, it must have the inline // attribute and the choices2 optimization must be enabled. if ((((! p.isMemoized()) && (! p.hasAttribute(Constants.ATT_NO_INLINE)) && (! p.hasAttribute(Constants.ATT_EXPLICIT)) && (AST.isVoid(p.type) || p.getBooleanProperty(Properties.TEXT_ONLY) || p.getBooleanProperty(Properties.TOKEN))) || (p.hasAttribute(Constants.ATT_INLINE) && runtime.test("optimizeChoices2"))) && (0 == md.selfCount) && (! (hasState && (p.hasAttribute(Constants.ATT_STATEFUL) || p.hasAttribute(Constants.ATT_RESETTING))))) { OrderedChoice choice = analyzer.copy(p.choice); // If the choice about to be inlined has only one // alternative (i.e., one alternative replaces another), // then we remember the source location of the original // alternative. if (1 == choice.alternatives.size()) { choice.alternatives.get(0).setLocation(alternative); } // Fix any value elements for the larger expression about // to be inlined. if ((! top) && (! last)) { // Embedded choices do not need any value elements. mode = Mode.RM_VALUES; choice = (OrderedChoice)dispatch(choice); mode = Mode.INLINE; } else if (AST.isVoid(analyzer.current().type)) { // Void productions need null value elements. mode = Mode.VOID_VALUES; choice = (OrderedChoice)dispatch(choice); mode = Mode.INLINE; } else if (analyzer.current(). getBooleanProperty(Properties.TEXT_ONLY) && (! top)) { // Embedded choices in text-only productions need text // value elements (but not string value elements). mode = Mode.TEXT_VALUES; choice = (OrderedChoice)dispatch(choice); mode = Mode.INLINE; } else if (analyzer.current().getBooleanProperty(Properties.TOKEN)) { // Token-level productions need token value elements. mode = Mode.TOKEN_VALUES; choice = (OrderedChoice)dispatch(choice); mode = Mode.INLINE; } c.alternatives.remove(i); c.alternatives.addAll(i, choice.alternatives); // Tell the user. inlined(p); // Process the inlined alternative(s). i--; continue; } } } // Process the unmodified alternative. if (top || last) isLastElement = true; c.alternatives.set(i, (Sequence)dispatch(alternative)); } return c; } /** Visit the specified sequence. */ public Element visit(Sequence s) { isTopLevel = false; boolean last = isLastElement; int size = s.size(); // If we are in the remove value elements mode, check whether the // last element of the sequence is a value element and, if so, // remove it. if ((Mode.RM_VALUES == mode) && (s.get(size-1) instanceof ValueElement)) { s.elements.remove(size-1); size--; } // Process the sequence's elements. for (int i=0; i<size; i++) { isLastElement = last && (i == size-1); s.elements.set(i, (Element)dispatch(s.get(i))); } isLastElement = false; // Done. return s; } /** Visit the specified value element. */ public Element visit(ValueElement v) { isTopLevel = false; isLastElement = false; if (Mode.VOID_VALUES == mode) { return NullValue.VALUE; } else if (Mode.TEXT_VALUES == mode) { return StringValue.VALUE; } else if (Mode.TOKEN_VALUES == mode) { return TokenValue.VALUE; } else { return v; } } }