/*
* xtc - The eXTensible Compiler
* Copyright (C) 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 xtc.tree.Visitor;
import xtc.type.AST;
import xtc.util.Runtime;
/**
* Visitor to pear down a grammar to the structure of its abstract
* syntax tree. This visitor assumes that the entire grammar is
* contained in a single module. It also assumes that directly
* left-recursive generic productions have <em>not</em> been
* transformed by {@link DirectLeftRecurser}. Note that, after this
* visitor has processed the grammar, the grammar violates the code
* generator's requirements. Also note that this visitor internally
* uses {@link Simplifier} and {@link DeadProductionEliminator}.
*
* @author Robert Grimm
* @version $Revision: 1.11 $
*/
public class TreeExtractor extends Visitor {
/** The runtime. */
protected final Runtime runtime;
/** The analyzer utility. */
protected final Analyzer analyzer;
/** The common type operations. */
protected final AST ast;
/** The flag for removing all elements from lexical productions. */
protected final boolean keepLexical;
/** The flag for whether the current production is generic. */
protected boolean isGeneric;
/** The flag for whether the current production is list-valued. */
protected boolean isList;
/** The flag for whether the current production is text-only. */
protected boolean isTextOnly;
/** The flag for whether the current production is token-level. */
protected boolean isToken;
/**
* The flag for whether the current production defines the semantic
* value in an explicit action.
*/
protected boolean setsValue;
/**
* Create a new tree extractor.
*
* @param runtime The runtime.
* @param analyzer The analyzer utility.
* @param ast The type operations.
* @param keepLexical The flag for keeping elements of text-only
* or token-level productions.
*/
public TreeExtractor(Runtime runtime, Analyzer analyzer, AST ast,
boolean keepLexical) {
this.runtime = runtime;
this.analyzer = analyzer;
this.ast = ast;
this.keepLexical = keepLexical;
}
/** Visit the specified module. */
public void visit(Module m) {
// Initialize the per-grammar state.
analyzer.register(this);
analyzer.init(m);
// Process the productions.
for (Production p : m.productions) {
analyzer.process(p);
}
// Simplify the grammar and eliminate any dead productions.
new Simplifier(runtime, analyzer).dispatch(m);
new DeadProductionEliminator(runtime, analyzer).dispatch(m);
// Eliminate any actions and attributes.
m.header = null;
m.body = null;
m.footer = null;
m.attributes = null;
// Eliminate the production's attributes and internal types.
for (Production p : m.productions) {
p.attributes = null;
p.type = null;
}
}
/** Visit the specified full production. */
public void visit(FullProduction p) {
// Initialize the per-production state.
isGeneric = Generifier.isGeneric(p);
isList = AST.isList(p.type);
isTextOnly = p.getBooleanProperty(Properties.TEXT_ONLY);
isToken = p.getBooleanProperty(Properties.TOKEN);
setsValue = false;
// Preprocess directly left-recursive generic productions.
if (isGeneric && DirectLeftRecurser.isTransformable(p)) {
for (Sequence s : p.choice.alternatives) {
if (! DirectLeftRecurser.isRecursive(s, p)) {
Binding b = analyzer.bind(s.elements);
if (null != b) b.name = CodeGenerator.VALUE;
}
}
}
// Process the choice.
if ((isTextOnly || isToken) && (! keepLexical)) {
p.choice = new OrderedChoice(new Sequence());
p.setProperty(Properties.REDACTED, Boolean.TRUE);
} else {
dispatch(p.choice);
}
// Patch the type.
final String s = ast.extern(p.type);
if (isGeneric) {
p.dType = "define<Node>";
} else if (isTextOnly) {
p.dType = "define<String>";
} else if (isToken) {
p.dType = "define<Token>";
} else if (isList) {
p.dType = "define<" + s + '>';
} else if (setsValue) {
p.dType = "define<" + s + '>';
} else {
p.dType = "expand<" + s + '>';
}
}
/** Visit the specified ordered choice. */
public void visit(OrderedChoice c) {
for (Sequence alt : c.alternatives) dispatch(alt);
}
/** Visit the specified sequence. */
public void visit(Sequence s) {
// Eliminate the sequence name.
s.name = null;
// Process the elements.
for (int i=0; i<s.size(); i++) {
Element e = s.get(i);
// If the element is a trailing choice, process its sequences.
if ((s.size()-1 == i) && (e instanceof OrderedChoice)) {
dispatch(e);
continue;
}
// If the element is a voided element, semantic predicate, or
// value element, remove the element and move on to the next
// element.
if ((e instanceof VoidedElement) ||
(e instanceof SemanticPredicate) ||
(e instanceof ValueElement)) {
s.elements.remove(i);
i--;
continue;
}
// If the element is an action that sets the semantic value,
// simplify it. Otherwise, remove the action.
if (e instanceof Action) {
Action a = (Action)e;
if (a.setsValue()) {
setsValue = true;
a.code.clear();
a.indent.clear();
a.code.add("yyValue = ...");
a.indent.add(0);
} else {
s.elements.remove(i);
i--;
}
continue;
}
// Process bindings. In generic productions, we get rid of
// bindings unless they bind yyValue (since they indicate a
// passed-through value). In productions that are neither
// generic, text-valued, or text-only, we get rid of elements if
// they are not bound (since they cannot contribute to a
// semantic value).
if (e instanceof Binding) {
Binding b = (Binding)e;
if ((! isGeneric) ||
(isGeneric && (! CodeGenerator.VALUE.equals(b.name)))) {
e = b.element;
s.elements.set(i, e);
}
// Continue processing the element.
} else if ((! isGeneric) && (! isList) && (! isTextOnly) && (! isToken)) {
s.elements.remove(i);
i--;
continue;
}
// Remove any void nonterminals.
if (e instanceof NonTerminal) {
FullProduction p = analyzer.lookup((NonTerminal)e);
if (AST.isVoid(p.type)) {
s.elements.remove(i);
i--;
continue;
}
}
// Process the element.
dispatch(e);
}
}
/** Visit the specified character case. */
public void visit(CharCase c) {
dispatch(c.element);
}
/** Visit the specified character switch. */
public void visit(CharSwitch s) {
for (CharCase c : s.cases) {
dispatch(c);
}
dispatch(s.base);
}
/** Visit the specified unary operator. */
public void visit(UnaryOperator op) {
dispatch(op.element);
// Strip any unnecessary choices and sequences.
op.element = Analyzer.strip(op.element);
}
/** Visit the specified element. */
public void visit(Element e) {
// Nothing to do.
}
}