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