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