/*
* 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 java.util.HashSet;
import java.util.Set;
import xtc.tree.Visitor;
import xtc.util.Runtime;
/**
* Visitor to detect left-recursion in a grammar.
*
* <p />This visitor requires that text-only productions {@link
* TextTester have been marked} as such.
*
* @author Robert Grimm
* @version $Revision: 1.49 $
*/
public class LeftRecurser extends Visitor {
/** The runtime. */
protected final Runtime runtime;
/** The analyzer utility. */
protected final Analyzer analyzer;
/** The flag for whether we have seen a terminal. */
protected boolean terminated;
/**
* Create a new left-recurser.
*
* @param runtime The runtime.
* @param analyzer The analyzer utility.
*/
public LeftRecurser(Runtime runtime, Analyzer analyzer) {
this.runtime = runtime;
this.analyzer = analyzer;
}
/**
* Get the set of nonterminals corresponding to left-recursive
* productions. Note that this method must only be called after
* visiting the corresponding grammar with this visitor.
*
* @return The set of left-recursive nonterminals.
*/
public Set<NonTerminal> recursive() {
return new HashSet<NonTerminal>(analyzer.marked());
}
/** Visit the specified grammar. */
public void visit(Grammar g) {
// Reset the per-grammar state.
analyzer.register(this);
analyzer.init(g);
for (Module m : g.modules) {
analyzer.process(m);
for (Production p : m.productions) {
// Only process full productions that have not been processed.
if ((! p.isFull()) || analyzer.isProcessed(p.qName)) continue;
// Reset the per-production state.
terminated = false;
// Process the production.
analyzer.process(p);
}
}
}
/** Visit the specified self-contained module. */
public void visit(Module m) {
// Reset the per-grammar state.
analyzer.register(this);
analyzer.init(m);
for (Production p : m.productions) {
// Only process full productions that have not been processed.
if (analyzer.isProcessed(p.qName)) continue;
// Reset the per-production state.
terminated = false;
// Process the production.
analyzer.process(p);
}
}
/** Visit the specified production. */
public void visit(FullProduction p) {
Object closure = analyzer.enter(p);
// We only keep a production in the working set while we are
// actively traversing reachable productions. Otherwise, we might
// incorrectly classify a production as left-recursive, for
// example, when traversing productions encoding operator
// precedence.
analyzer.workingOn(p.qName);
if ((runtime.test("optimizeLeftRecursions") ||
runtime.test("optimizeLeftIterations")) &&
DirectLeftRecurser.isTransformable(p)) {
// Directly left-recursive productions get the special treatment
// by skipping the recursive alternatives in the top-level
// choice.
for (Sequence alt : p.choice.alternatives) {
if (DirectLeftRecurser.isBase(alt, p)) {
dispatch(alt);
}
}
} else {
dispatch(p.choice);
}
analyzer.notWorkingOn(p.qName);
analyzer.exit(closure);
analyzer.processed(p.qName);
}
/** Visit the specified ordered choice. */
public void visit(OrderedChoice c) {
boolean more = false;
for (Sequence alt : c.alternatives) {
terminated = false;
dispatch(alt);
if (! terminated) {
more = true;
}
}
if (more) {
terminated = false;
}
}
/** Visit the specified repetition. */
public void visit(Repetition r) {
dispatch(r.element);
if (! r.once) {
terminated = false;
}
}
/** Visit the specified sequence. */
public void visit(Sequence s) {
for (Element e : s.elements) {
dispatch(e);
if (terminated) break;
}
}
/** Visit the specified voided element. */
public void visit(VoidedElement v) {
dispatch(v.element);
}
/** Visit the specified binding. */
public void visit(Binding b) {
dispatch(b.element);
}
/** Visit the specified string match. */
public void visit(StringMatch m) {
dispatch(m.element);
}
/** Visit the specified nonterminal. */
public void visit(NonTerminal nt) {
FullProduction p;
try {
p = analyzer.lookup(nt);
} catch (IllegalArgumentException x) {
terminated = true;
return;
}
if (null != p) {
if (analyzer.isBeingWorkedOn(p.qName)) {
analyzer.mark(p.qName);
p.setProperty(Properties.RECURSIVE, Boolean.TRUE);
terminated = true;
} else if (! analyzer.isProcessed(p.qName)) {
dispatch(p);
} else {
terminated = true;
}
} else {
terminated = true;
}
}
/** Visit the specified terminal. */
public void visit(Terminal t) {
// We can't left-recurse on terminals.
terminated = true;
}
/**
* Visit the specified unary operator. This method provides the
* default implementation for options and predicates.
*/
public void visit(UnaryOperator op) {
dispatch(op.element);
terminated = false;
}
/**
* Visit the specified parser action. Parser actions are assumed to
* always consume some input.
*/
public void visit(ParserAction pa) {
dispatch(pa.element);
terminated = true;
}
/**
* Visit the specified element. This method provides the default
* implementation for node markers, actions, parse tree nodes, null
* literals, and value elements.
*/
public void visit(Element e) {
// Nothing to do.
}
}