/*
* xtc - The eXTensible Compiler
* Copyright (C) 2006-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 java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import xtc.Constants;
import xtc.tree.Printer;
import xtc.tree.Visitor;
import xtc.type.AST;
import xtc.type.Type;
import xtc.type.TupleT;
import xtc.type.UnitT;
import xtc.type.VariantT;
import xtc.util.Runtime;
/**
* Visitor to determine a grammar's tree structure. This visitor
* assumes that the entire grammar is contained in a single module and
* has been annotated with appropriate value elements.
*
* @author Robert Grimm
* @version $Revision: 1.26 $
*/
public class TreeTyper extends Visitor {
/** The debug flag. */
private static final boolean DEBUG = false;
/** The runtime. */
protected final Runtime runtime;
/** The analyzer utility. */
protected final Analyzer analyzer;
/** The common type operations. */
protected final AST ast;
/** The flag for strict unification. */
protected boolean strict;
/** The flag for flattening lists. */
protected boolean flatten;
/** The current sequence. */
protected Sequence sequence;
/**
* Create a new tree typer.
*
* @param runtime The runtime.
* @param ast The type operations.
* @param analyzer The analyzer utility.
*/
public TreeTyper(Runtime runtime, Analyzer analyzer, AST ast) {
this.runtime = runtime;
this.analyzer = analyzer;
this.ast = ast;
}
/** Visit the specified module. */
public void visit(Module m) {
// Initialize the per-grammar state.
analyzer.register(this);
analyzer.init(m);
strict = runtime.test("optionVariant");
flatten = m.hasAttribute(Constants.ATT_FLATTEN);
// Process the productions.
for (Production p : m.productions) analyzer.process(p);
// Print the header.
final Printer printer = runtime.console();
printer.sep();
printer.indent().p("// Generated by Rats!, version ").p(Constants.VERSION).
p(", ").p(Constants.COPY).pln('.');
printer.sep();
printer.pln();
printer.indent().p("/** AST structure for grammar ").p(m.name.name).
pln(". */");
// Ensure that all tuples are concrete and then print the result.
if (! runtime.test("optionVariant")) {
// Flat AST definition.
final VariantT node = ast.toVariant("Node", false);
ast.concretizeTuples(node, UnitT.TYPE);
printer.indent().p("module ").p(m.name.name).p("Tree").pln(';');
printer.pln();
ast.print(node, printer, true, false, null);
printer.pln();
} else {
// Hierarchical AST definition.
final VariantT root =
analyzer.lookup((NonTerminal)m.getProperty(Properties.ROOT)).
type.resolve().toVariant();
final AST.MetaData meta = ast.getMetaData(root);
final Set<String> processed = new HashSet<String>();
// Concretize variants.
for (Production p : m.productions) {
if (AST.isStaticNode(p.type)) {
final VariantT variant = p.type.resolve().toVariant();
final String name = variant.getName();
if (meta.reachable.contains(name) && ! processed.contains(name)) {
processed.add(name);
ast.concretizeTuples(variant, UnitT.TYPE);
}
}
}
processed.clear();
// Print variants...
if (! meta.modularize) {
// ... in a single module.
printer.indent().p("module ").p(m.name.name).p("Tree").pln(';');
printer.pln();
for (Production p : m.productions) {
if (AST.isStaticNode(p.type)) {
final VariantT variant = p.type.resolve().toVariant();
final String name = variant.getName();
if (meta.reachable.contains(name) && ! processed.contains(name)) {
processed.add(name);
ast.print(variant, printer, true, false, null);
printer.pln();
}
}
}
} else {
// ... across several modules.
boolean first = true;
String module;
do {
module = null;
for (Production p : m.productions) {
if (AST.isStaticNode(p.type)) {
final VariantT variant = p.type.resolve().toVariant();
final String name = variant.getName();
if (meta.reachable.contains(name) && ! processed.contains(name)) {
final String qualifier = variant.getQualifier();
if (null == module) {
module = qualifier;
if (first) {
first = false;
} else {
printer.sep().pln();
}
printer.indent().p("module ").p(module).pln(';');
printer.pln();
} else if (! module.equals(qualifier)) {
continue;
}
processed.add(name);
ast.print(variant, printer, true, true, module);
printer.pln();
}
}
}
} while (null != module);
}
}
printer.sep().flush();
}
/** Visit the specified production. */
public void visit(Production p) {
dispatch(p.choice);
}
/** 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) {
final Sequence old = sequence;
sequence = s;
for (Element e : s.elements) dispatch(e);
sequence = old;
}
/** Visit the specified unary operator. */
public void visit(UnaryOperator op) {
dispatch(op.element);
}
/** Visit the specified element. */
public void visit(Element e) {
// Nothing to do.
}
/** Visit the specified generic node value. */
public void visit(GenericNodeValue v) {
process(v.name, false, v.children);
}
/** Visit the specified generic action value. */
public void visit(GenericActionValue v) {
process(v.name, true, v.children);
}
/**
* Determine the type of the specified generic node constructor.
*
* @param name The constructor's name.
* @param isAction The flag for whether the constructor appears in an action.
* @param children The list of bindings for the children.
*/
protected void process(String name, boolean isAction, List<Binding> children) {
if (flatten) {
// Combine [ ..., nt, nt*, ... ] into [ ..., nt*, ... ]
boolean isCopy = false;
for (int i=0; i<children.size()-1; i++) {
// Check whether the first binding is for a nonterminal.
Binding b1 = children.get(i);
if (! (b1.element instanceof NonTerminal)) continue;
NonTerminal nt1 = (NonTerminal)b1.element;
// Check whether the second binding is for a repeated nonterminal.
Binding b2 = children.get(i+1);
switch (b2.element.tag()) {
case REPETITION: {
// Check the preserved repetition.
Repetition rep = (Repetition)b2.element;
Binding bdg = Analyzer.getBinding(((Sequence)rep.element).elements);
if (null == bdg) {
throw new IllegalStateException("Malformed repetition " + rep);
}
if (! nt1.equals(bdg.element)) continue;
} break;
case NONTERMINAL: {
// Check the desugared repetition.
NonTerminal non = (NonTerminal)b2.element;
Production prod = analyzer.lookup(non);
if (! nt1.equals(prod.getProperty(Properties.REPEATED))) continue;
} break;
default:
continue;
}
// Success.
if (! isCopy) {
children = new ArrayList<Binding>(children);
isCopy = true;
}
children.remove(i);
i -= 2; // Check the previous binding again.
if (-1 > i) i = -1;
}
}
// Determine the types of the bindings.
final int size = isAction ? children.size() + 1 : children.size();
List<Type> types = new ArrayList<Type>(size);
if (isAction) {
if (runtime.test("optionVariant")) {
// Strip away any list and action types.
Type t = analyzer.current().type;
if (AST.isList(t)) t = AST.getArgument(t);
if (AST.isAction(t)) t = AST.getArgument(t);
types.add(t);
} else {
types.add(AST.NODE);
}
}
for (Binding b : children) {
types.add(analyzer.type(b.element));
}
// Unify the tuple type with any previous tuple type and update
// its variant.
final TupleT t1 = ast.toTuple(name);
final TupleT t2 = new TupleT(name, types);
final boolean fresh = (null == t1.getTypes());
if (fresh && ! runtime.test("optionVariant")) {
ast.add(t1, ast.toVariant("Node", false));
}
if (DEBUG) {
runtime.console().p(name).p(" : ");
ast.print(t2, runtime.console(), false, true, null);
runtime.console().pln();
}
if (! fresh) {
Type result = ast.combine(t1, t2, flatten, strict);
if (result.isError()) {
runtime.error("unable to consistently type tuple '" + name + "'",
sequence);
runtime.errConsole().loc(sequence).p(": error: 1st type is '");
ast.print(t1, runtime.errConsole(), false, true, null);
runtime.errConsole().pln("'");
runtime.errConsole().loc(sequence).p(": error: 2nd type is '");
ast.print(t2, runtime.errConsole(), false, true, null);
runtime.errConsole().pln("'").flush();
} else {
if (DEBUG) {
runtime.console().p(" -> ");
ast.print(result, runtime.console(), false, true, null);
runtime.console().pln();
}
t1.setTypes(result.toTuple().getTypes());
}
} else if (flatten) {
Type result = ast.flatten(t2, strict);
if (result.isError()) {
runtime.error("unable to flatten tuple '" + name + "'", sequence);
runtime.errConsole().loc(sequence).p(": error: type is '");
ast.print(t2, runtime.errConsole(), false, true, null);
runtime.errConsole().pln("'").flush();
} else {
t1.setTypes(result.toTuple().getTypes());
}
} else {
t1.setTypes(types);
}
}
}