/* * 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.type.AST; import xtc.util.Runtime; /** * Visitor to inline productions. If cost-based inlining is enabled, * this visitor inlines productions with a {@link CostEstimator cost * estimate} less or equal to the {@link #MAX_COST maximum cost}. * However, to avoid changing a production's semantic value, * cost-based inlining is limited to void, text-only, and token-level * productions. Independent of whether cost-based inlining is enabled * or not, this visitor inlines productions that are referenced from a * production consisting solely of a nonterminal (which, optionally, * may be bound to {@link CodeGenerator#VALUE}). Note that generic * productions are never inlined; neither are productions with the * state attribute. * * <p />Note that, to be effective, this visitor requires that * text-only productions have been {@link TextTester marked} as such. * Similarly, token-level productions need to have beeen {@link * Tokenizer marked} as such. Further note that, if this visitor * inlines productions, the resulting grammar needs to be {@link * Simplifier simplified} before further processing. Also note that * this visitor may create new opportunities for the {@link * DeadProductionEliminator elimination of dead productions}. This * visitor assumes that the entire grammar is contained in a single * module. * * @author Robert Grimm * @version $Revision: 1.53 $ */ public class Inliner extends GrammarVisitor { /** The maximum cost for inlining productions at arbitrary positions. */ public static final int MAX_COST = 1; /** The flag for whether to inline non-transient productions. */ public static final boolean INLINE_PERSISTENT = true; /** Flag for whether the grammar has the state attribute. */ protected boolean attributeState; /** Flag for whether this production inliner has inlined a production. */ protected boolean inlined; /** * Create a new production inliner. * * @param runtime The runtime. * @param analyzer The analyzer utility. */ public Inliner(Runtime runtime, Analyzer analyzer) { super(runtime, analyzer); } /** * Determine whether the specified production is void, text-only, or * token-level. * * @param p The production. * @return <code>true</code> if the production is either text-only * or void. */ protected boolean isBasic(final Production p) { return (AST.isVoid(p.type) || p.getBooleanProperty(Properties.TEXT_ONLY) || p.getBooleanProperty(Properties.TOKEN)); } /** * 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) { inlined = true; if (runtime.test("optionVerbose")) { System.err.println("[Inlining " + p.qName + " into " + analyzer.current().qName + "]"); } } /** * Process the specified grammar. * * @param m The grammar module. * @return <code>Boolean.TRUE</code> if the grammar has been modified, * otherwise <code>Boolean.FALSE</code>. */ public Object visit(Module m) { CostEstimator cost = null; boolean changed = false; if (runtime.test("optimizeCost")) { cost = new CostEstimator(analyzer); } // Perform inlining until we reach a fixed-point. We are // guaranteed to reach a fixed-point because the cost estimator's // estimate for a production includes the costs of any referenced // productions, thus marking all recursive productions with an // unlimited cost. do { // Annotate productions with their cost estimates. if (runtime.test("optimizeCost")) { cost.dispatch(m); } // Initialize the per-grammar state. analyzer.register(this); analyzer.init(m); attributeState = m.hasAttribute(Constants.ATT_STATEFUL.getName()); inlined = false; // Process the productions. for (Production p : m.productions) analyzer.process(p); // Did the grammar change? if (inlined) { changed = true; } } while (inlined); return (changed)? Boolean.TRUE : Boolean.FALSE; } /** Visit the specified nonterminal. */ public Element visit(NonTerminal nt) { boolean bound = isBound; isBound = false; // Get the referenced production. FullProduction p = analyzer.lookup(nt); if (Generifier.isGeneric(p) || AST.isList(p.type) || p.hasAttribute(Constants.ATT_EXPLICIT) || (attributeState && (p.hasAttribute(Constants.ATT_STATEFUL) || p.hasAttribute(Constants.ATT_RESETTING)))) { // Never inline generic or list-valued productions, nor // productions with the explicit, state, or reset attribute. return nt; } // Get the referenced production's expression. Element e = Analyzer.strip(p.choice); if (e instanceof NonTerminal) { // We only inline a lone nonterminal if the production is // transient. That way, a lone nonterminal can be used to force // memoization of a transient production. if (! p.isMemoized() && ! p.hasAttribute(Constants.ATT_NO_INLINE)) { inlined(p); // Copy the nonterminal being inlined and update the copy's // location to the original nonterminal's location, so that // errors point to the right location. NonTerminal copy = new NonTerminal((NonTerminal)e); copy.setLocation(nt); return copy; } else { return nt; } } else if (e instanceof Binding) { Binding b = (Binding)e; Element e2 = Analyzer.strip(b.element); if (CodeGenerator.VALUE.equals(b.name) && (e2 instanceof NonTerminal)) { // We only inline a lone nonterminal if the production is // transient. That way, a lone nonterminal can be used to // force memoization of a transient production. if (! p.isMemoized() && ! p.hasAttribute(Constants.ATT_NO_INLINE)) { inlined(p); // Copy the nonterminal being inlined and update the copy's // location to the original nonterminal's location, so that // errors point to the right location. NonTerminal copy = new NonTerminal((NonTerminal)e2); copy.setLocation(nt); return copy; } else { return nt; } } } if ((! (isBasic(analyzer.current()) && (INLINE_PERSISTENT || (! analyzer.current().isMemoized())))) || bound) { // If the current production is not void or text-only or if the // nonterminal is bound, we don't do any other inlining. return nt; } else if (runtime.test("optimizeCost") && (MAX_COST >= (Integer)p.getProperty(Properties.COST)) && ! p.hasAttribute(Constants.ATT_NO_INLINE) && (INLINE_PERSISTENT || (! p.isMemoized()))) { // If the referenced production's cost estimate is low enough, // inline the referenced production. inlined(p); return analyzer.copy(p.choice); } else { return nt; } } }