/* * xtc - The eXTensible Compiler * Copyright (C) 2004-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.Iterator; import java.util.List; import xtc.Constants; import xtc.util.Utilities; import xtc.tree.Attribute; import xtc.tree.Comment; import xtc.tree.Node; import xtc.tree.Printer; import xtc.tree.Visitor; import xtc.type.AST; /** * The grammar pretty printer. * * @author Robert Grimm * @version $Revision: 1.117 $ */ public class PrettyPrinter extends Visitor { /** The printer for this pretty printer. */ protected Printer printer; /** The type operations. */ final protected AST ast; /** The flag for whether this pretty printer is verbose. */ final protected boolean verbose; /** Flag for whether the last element ended in a newline. */ protected boolean newline; /** Flag for whether the next ordered choice needs to be parenthesized. */ protected boolean parenChoice; /** Flag for whether the next sequence element needs to be parenthesized. */ protected boolean parenSequence; /** Flag for whether the choice is top-level. */ protected boolean isTopLevel; /** * Create a new pretty printer. Any subclass using this constructor * must explicit set the {@link #printer} field and register this * visitor with the set printer. * * @param ast The type operations. * @param verbose The verbose flag. */ protected PrettyPrinter(AST ast, boolean verbose) { this.ast = ast; this.verbose = verbose; } /** * Create a new pretty printer. * * @param printer The printer. * @param ast The type operations. * @param verbose The verbose flag. */ public PrettyPrinter(Printer printer, AST ast, boolean verbose) { this.printer = printer; this.ast = ast; this.verbose = verbose; printer.register(this); } /** * Get the flags for escaping regular strings. * * @return The flags for escaping strings. */ protected int stringEscapes() { return Utilities.JAVA_ESCAPES; } /** * Get the flags for escaping regex strings. * * @return The flags for escaping regex strings. */ protected int regexEscapes() { return Utilities.FULL_ESCAPES; } /** * Determine whether the specified element represents a prefix * operator. * * @param e The element. * @return <code>true</code> if the specified element represents a * prefix operator. */ protected boolean isPrefix(Element e) { switch (e.tag()) { case VOIDED: case BINDING: case PARSER_ACTION: case FOLLOWED_BY: case NOT_FOLLOWED_BY: case SEMANTIC_PREDICATE: case STRING_MATCH: return true; default: return false; } } /** Flush the underlying printer. */ public void flush() { printer.flush(); } /** Print the specified attribute. */ public void visit(Attribute a) { if (Constants.NAME_VISIBILITY.equals(a.getName())) { printer.p((String)a.getValue()); } else { printer.p(a.getName()); Object value = a.getValue(); if (null != value) { printer.p('(').p(value.toString()).p(')'); } } } /** Print the specified grammar. */ public void visit(Grammar g) { for (Module m : g.modules) dispatch(m); } /** * Print the specified module documentation comment. * * @param m The module. */ protected void printDocumentation(Module m) { if ((! verbose) || (null == m.documentation)) return; Comment c = m.documentation; int size = c.text.size(); if (0 == size) { // Nothing to print. } else if (1 == size) { printer.indent().p("/** ").p(c.text.get(0)).pln(" */"); } else { printer.indent().pln("/**"); for (int i=0; i<size; i++) { printer.indent().p(" * ").pln(c.text.get(i)); } printer.indent().pln(" */"); } } /** * Print the specified module's declaration. * * @param m The module. */ protected void printModule(Module m) { printer.indent().p("module " ).p(m.name); if ((null != m.parameters) && (0 < m.parameters.size())) { printer.p(m.parameters); } if (verbose && (m.name.hasProperty(Constants.ORIGINAL) || m.hasProperty(Constants.ARGUMENTS))) { ModuleName base = m.name.hasProperty(Constants.ORIGINAL) ? (ModuleName)m.name.getProperty(Constants.ORIGINAL) : m.name; ModuleList args = (ModuleList)m.getProperty(Constants.ARGUMENTS); printer.p(" /* = ").p(base); if (null == args) { printer.p("()"); } else { printer.p(args); } printer.p(" */ "); } printer.pln(';'); } /** * Print the specified module's header, body, and footer actions. * * @param m The module. */ protected void printActions(Module m) { // Emit header action. if (null != m.header) { printer.pln().indent().p("header ").p(m.header); if (1 == m.header.code.size()) { printer.pln(); } } // Emit body action. if (null != m.body) { printer.pln().indent().p("body ").p(m.body); if (1 == m.body.code.size()) { printer.pln(); } } // Emit footer action. if (null != m.footer) { printer.pln().indent().p("footer ").p(m.footer); if (1 == m.footer.code.size()) { printer.pln(); } } } /** * Print the specified module's options. * * @param m The module. */ protected void printOption(Module m) { if ((null != m.attributes) && (0 < m.attributes.size())) { printer.pln().indent().p("option "); for (Iterator<?> iter = m.attributes.iterator(); iter.hasNext(); ) { printer.buffer().p(iter.next().toString()); if (iter.hasNext()) { printer.p(", "); } else { printer.p(';'); } printer.fitMore(); } printer.pln(); } } /** Print the specified module. */ public void visit(Module m) { // Emit header. printer.sep(); printer.indent().p("// Generated by Rats!, version ").p(Constants.VERSION). p(", ").p(Constants.COPY).pln('.'); printer.sep(); printer.pln(); // Emit module documentation. printDocumentation(m); // Emit module name and parameters. printModule(m); // Emit module dependencies. if ((null != m.dependencies) && (0 < m.dependencies.size())) { printer.pln(); for (ModuleDependency dep : m.dependencies) { printer.p(dep); } } // Emit header, body, and footer actions. printActions(m); // Emit grammar-wide options. printOption(m); // Emit the productions. printer.pln(); for (Production p : m.productions) { printer.p(p); } printer.sep().pln(); } /** * Print the specified module dependency. * * @param dep The dependency. * @param name The corresponding name. */ protected void print(ModuleDependency dep, String name) { printer.indent().p(name).p(' ').p(dep.module); if (0 != dep.arguments.size()) { printer.p(dep.arguments); } if (null != dep.target) { printer.buffer().p(" as ").p(dep.target).fitMore(); } printer.pln(';'); } /** Print the specified module import. */ public void visit(ModuleImport imp) { print(imp, "import"); } /** Print the specified module instantiation. */ public void visit(ModuleInstantiation ins) { print(ins, "instantiate"); } /** Print the specified module modification. */ public void visit(ModuleModification mod) { print(mod, "modify"); } /** Print the specified module list. */ public void visit(ModuleList list) { printer.p('('); for (Iterator<ModuleName> iter = list.names.iterator(); iter.hasNext(); ) { printer.buffer().p(iter.next()); if (iter.hasNext()) { printer.p(", "); } printer.fitMore(); } printer.p(')'); } /** Print the specified module name. */ public void visit(ModuleName name) { printer.p(name.name); } /** * Enter the specified production. This method prints a comment for * productions folded from duplicates. It then prints the specified * production's attributes, type, and name. Finally, it sets {@link * #parenChoice} and {@link #parenSequence} to <code>false</code>. * * @param p The production. */ protected void enter(Production p) { // Print a comment for productions folded from duplicates. if (verbose && p.hasProperty(Properties.DUPLICATES)) { List<String> sources = Properties.getDuplicates(p); printer.indent().pln("/*"); printer.indent().p(" * The following production is the result of "). p("folding duplicates "); for (Iterator<String> iter = sources.iterator(); iter.hasNext(); ) { String name = iter.next(); printer.buffer(); if ((1 < sources.size()) && (! iter.hasNext())) { printer.p("and "); } printer.p(name); if ((2 == sources.size()) && (iter.hasNext())) { printer.p(' '); } else if (iter.hasNext()) { printer.p(", "); } else { printer.p('.'); } printer.fit(" * "); } printer.pln(); printer.indent().pln(" */"); } // Print the attributes, type, and name. printer.indent(); if ((null != p.attributes) && (0 < p.attributes.size())) { // Declare attributes as nodes so that overload resolution // invokes Printer.p(Node) and therefore visit(Attribute). for (Node att : p.attributes) { printer.p(att).p(' '); } } if (null != p.type) { if (AST.isVoid(p.type)) { printer.p("void "); } else if (AST.isGenericNode(p.type)) { printer.p("generic "); } else { printer.p(ast.extern(p.type)).p(' '); } } else if (null != p.dType) { printer.p(p.dType).p(' '); } printer.p(p.name.name); // Set up the internal state. parenChoice = false; parenSequence = false; } /** * Exit the specified production. The default implementation prints * a new line. * * @param p The production. */ protected void exit(Production p) { printer.pln(); } /** Print the specified alternative addition. */ public void visit(AlternativeAddition p) { enter(p); printer.p(" += "); if (! p.isBefore) { printer.pln().indentMore().p(p.sequence).pln(" ..."); printer.indentMore().p("/ "); } isTopLevel = true; printer.p(p.choice); if (p.isBefore) { printer.indentMore().p("/ ").p(p.sequence).pln(" ..."); } printer.indentMore().pln(';'); exit(p); } /** Print the specified alternative removal. */ public void visit(AlternativeRemoval p) { enter(p); printer.pln(" -=").incr().indent(); for (Iterator<SequenceName> iter = p.sequences.iterator(); iter.hasNext();) { printer.buffer().p(iter.next()); if (iter.hasNext()) { printer.p(", "); } printer.fit(); } printer.pln().indent().pln(';').decr(); exit(p); } /** Print the specified production override. */ public void visit(ProductionOverride p) { enter(p); printer.p(" := "); if (null == p.choice) { printer.pln("... ;"); } else if (p.isComplete) { isTopLevel = true; printer.p(p.choice).indentMore().pln(';'); } else { printer.pln().indentMore().pln("...").indentMore().p("/ "); isTopLevel = true; printer.p(p.choice).indentMore().pln(';'); } exit(p); } /** Print the specified full production. */ public void visit(FullProduction p) { enter(p); printer.p(" = "); if ((1 == p.choice.alternatives.size()) && (0 == p.choice.alternatives.get(0).size())) { if (p.getBooleanProperty(Properties.REDACTED)) { printer.p("... "); } else if (verbose) { printer.p("/* Empty */ "); } printer.pln(';'); } else { isTopLevel = true; printer.p(p.choice).indentMore().pln(';'); } exit(p); } /** * Print the specified alternatives. * * @param alternatives The ordered list of alternatives. * @param mark The mark. */ protected void print(List<Sequence> alternatives, String mark) { boolean choice = parenChoice; boolean sequence = parenSequence; if (choice) { printer.p("( "); } if (isTopLevel) { isTopLevel = false; } else if (verbose) { printer.p("/* ").p(mark).p(" */ "); } printer.pln().incr(); boolean first = true; for (Sequence s : alternatives) { if (first) { first = false; printer.indent(); } else { printer.indent().p("/ "); } parenChoice = true; parenSequence = false; newline = false; printer.p(s); if (! newline) { printer.pln(); } } printer.decr(); if (choice) { printer.indent().p(')'); } parenChoice = choice; parenSequence = sequence; newline = false; } /** Print the specified ordered choice. */ public void visit(OrderedChoice c) { print(c.alternatives, "Choice"); } /** Print the specified repetition. */ public void visit(Repetition r) { if (newline) printer.indent(); boolean choice = parenChoice; boolean sequence = parenSequence; newline = false; parenChoice = true; parenSequence = true; printer.buffer(); if (isPrefix(r.element)) printer.p('('); printer.p(r.element); if (isPrefix(r.element)) printer.p(')'); if (r.once) { printer.p('+'); } else { printer.p('*'); } printer.fit(); parenChoice = choice; parenSequence = sequence; } /** Print the specified option. */ public void visit(Option o) { if (newline) printer.indent(); boolean choice = parenChoice; boolean sequence = parenSequence; newline = false; parenChoice = true; parenSequence = true; printer.buffer(); if (isPrefix(o.element)) printer.p('('); printer.p(o.element); if (isPrefix(o.element)) printer.p(')'); printer.p('?').fit(); parenChoice = choice; parenSequence = sequence; } /** Print the specified sequence name. */ public void visit(SequenceName n) { printer.p('<').p(n.name).p('>'); } /** Print the specified sequence. */ public void visit(Sequence s) { if (newline) printer.indent(); boolean choice = parenChoice; boolean sequence = parenSequence; newline = false; parenChoice = true; parenSequence = true; boolean first = true; for (Element e : s.elements) { if (first) { first = false; if (sequence) { printer.p('('); } if (null != s.name) { printer.p(s.name).p(' '); } } else { printer.p(' '); } printer.p(e); } if (sequence) { printer.p(')'); } parenChoice = choice; parenSequence = sequence; } /** Print the specified followed-by element. */ public void visit(FollowedBy p) { if (newline) printer.indent(); boolean choice = parenChoice; boolean sequence = parenSequence; newline = false; parenChoice = true; parenSequence = true; printer.buffer().p('&').p(p.element).fit(); parenChoice = choice; parenSequence = sequence; } /** Print the specified not-followed-by element. */ public void visit(NotFollowedBy p) { if (newline) printer.indent(); boolean choice = parenChoice; boolean sequence = parenSequence; newline = false; parenChoice = true; parenSequence = true; printer.buffer().p('!').p(p.element).fit(); parenChoice = choice; parenSequence = sequence; } /** Print the specified semantic predicate. */ public void visit(SemanticPredicate p) { if (newline) printer.indent(); boolean choice = parenChoice; boolean sequence = parenSequence; newline = false; parenChoice = true; parenSequence = true; printer.buffer().p('&').p(p.element).fit(); parenChoice = choice; parenSequence = sequence; } /** Print the specified voided element. */ public void visit(VoidedElement v) { if (newline) printer.indent(); newline = false; printer.buffer().p("void:").p(v.element).fit(); } /** Print the specified binding. */ public void visit(Binding b) { if (newline) printer.indent(); newline = false; printer.buffer().p(b.name).p(':').p(b.element).fit(); } /** Print the specified string match. */ public void visit(StringMatch m) { if (newline) printer.indent(); newline = false; printer.buffer().p('\"').escape(m.text, stringEscapes()).p("\":"). p(m.element).fit(); } /** Print the specified nonterminal. */ public void visit(NonTerminal nt) { if (newline) printer.indent(); newline = false; printer.buffer().p(nt.name).fit(); } /** Print the specified string literal. */ public void visit(StringLiteral l) { if (newline) printer.indent(); newline = false; printer.buffer().p('\"').escape(l.text, stringEscapes()).p('\"'). fit(); } /** Print the specified any character element. */ public void visit(AnyChar a) { if (newline) printer.indent(); newline = false; printer.buffer().p('_').fit(); } /** Print the specified character literal. */ public void visit(CharLiteral l) { if (newline) printer.indent(); newline = false; printer.buffer().p('\'').escape(l.c, stringEscapes()).p('\'').fit(); } /** Print the specified character range. */ public void visit(CharRange r) { if (newline) printer.indent(); newline = false; if (r.first == r.last) { printer.escape(r.first, regexEscapes()); } else { printer.escape(r.first, regexEscapes()).p('-'). escape(r.last, regexEscapes()); } } /** Print the specified character class. */ public void visit(CharClass c) { if (newline) printer.indent(); newline = false; printer.buffer(); if (c.exclusive) { if (verbose) printer.p("/* Exclusive */ "); printer.p('!'); } printer.p('['); for (CharRange r : c.ranges) { printer.p(r); } printer.p(']'); if (c.exclusive) { printer.p(" ."); } printer.fit(); } /** Print the specified character case. */ public void visit(CharCase c) { if (newline) printer.indent(); newline = false; printer.p(c.klass).p(' ').p(c.element); } /** Print the specified character switch. */ public void visit(CharSwitch s) { boolean choice = parenChoice; boolean sequence = parenSequence; if (choice) { printer.p("( "); } if (verbose) { printer.pln("/* Switch */").incr(); } else { printer.pln().incr(); } boolean firstCase = true; boolean printed = false; CharClass klass = null; for (CharCase kase : s.cases) { if (null == kase.element) { // If the element of the character case is null, that case // corresponds to a formerly exclusive character class. We do // not print such cases here, but rather collect their // characters and then print them as part of the base. if (null == klass) { klass = new CharClass(new ArrayList<CharRange>()); } klass.ranges.addAll(kase.klass.ranges); } else { if (firstCase) { firstCase = false; printer.indent(); } else { printer.indent().p("/ "); } parenChoice = true; parenSequence = false; newline = false; printer.p(kase); if (! newline) { printer.pln(); } printed = true; } } if ((null != klass) || (null != s.base)) { printer.indent(); if (printed) { printer.p("/ "); } if (null != klass) { newline = false; printer.p('!').p(klass).p(' '); } if (null != s.base) { parenChoice = true; parenSequence = false; newline = false; printer.p("_ ").p(s.base); } if (! newline) { printer.pln(); } } printer.decr(); if (choice) { printer.indent().p(')'); } parenChoice = choice; parenSequence = sequence; newline = false; } /** * Print the specified action string. * * @param s The string. */ protected void print(String s) { printer.p(s); } /** * Print the specified action. * * @param a The action to print. * @param caret Flag for whether to prefix the action with a caret. */ protected void print(Action a, boolean caret) { if (newline) printer.indent(); if (a.code.isEmpty()) { // Nothing to do. newline = false; } else if (1 == a.code.size()) { newline = false; printer.buffer(); if (caret) printer.p('^'); printer.p("{ "); print(a.code.get(0)); printer.p(" }").fit(); } else { newline = true; if (caret) printer.p('^'); int baseLevel = printer.level(); printer.pln('{').incr(); int level = 0; Iterator<String> codeIter = a.code.iterator(); Iterator<Integer> indentIter = a.indent.iterator(); while (codeIter.hasNext()) { int newLevel = indentIter.next(); int diff = newLevel - level; level = newLevel; if (0 < diff) { for (int i=0; i<diff; i++) { printer.incr(); } } else { for (int i=0; i>diff; i--) { printer.decr(); } } printer.indent(); print(codeIter.next()); printer.pln(); } // Restore the base indentation level. We need to do this // explicitly because the removal of empty lines at the end of // an action may also remove the corresponding indentation // level. printer.setLevel(baseLevel).indent().pln('}'); } } /** Print the specified node marker. */ public void visit(NodeMarker m) { if (newline) printer.indent(); newline = false; printer.buffer().p('@').p(m.name).fit(); } /** Print the specified action. */ public void visit(Action a) { print(a, false); } /** Print the specified parser action. */ public void visit(ParserAction pa) { print((Action)pa.element, true); } /** Print the specified parse tree node. */ public void visit(ParseTreeNode n) { if (newline) printer.indent(); newline = false; printer.buffer().p("Formatting(["); for (Iterator<Binding> iter = n.predecessors.iterator(); iter.hasNext(); ) { printer.p(iter.next().name); if (iter.hasNext()) printer.p(", "); } printer.p("], "); if (null == n.node) { printer.p("null"); } else { printer.p(n.node.name); } printer.p(", ["); for (Iterator<Binding> iter = n.successors.iterator(); iter.hasNext(); ) { printer.p(iter.next().name); if (iter.hasNext()) printer.p(", "); } printer.p("])").fit(); } /** Print the specified null literal. */ public void visit(NullLiteral l) { if (newline) printer.indent(); newline = false; printer.buffer().p("null").fit(); } /** Print the specified null value. */ public void visit(NullValue v) { if (newline) printer.indent(); newline = false; printer.buffer().p("/* value = null; */").fit(); } /** * Format the specified statically known text value. * * @param isToken Flag for whether the value is a token. * @param text The text. */ protected void format(boolean isToken, String text) { if (newline) printer.indent(); // If the text contains the end sequence for a traditional C // comment, we use a C++ comment. boolean hasComment = (-1 != text.indexOf("*/")); printer.buffer(); if (hasComment) { printer.p("//"); newline = true; } else { printer.p("/*"); newline = false; } printer.p(" value = "); if (isToken) printer.p("Token("); printer.p('"').escape(text, stringEscapes()).p('"'); if (isToken) printer.p(')'); printer.p(';'); if (! hasComment) printer.p(" */"); printer.fit(); if (hasComment) printer.pln(); } /** Print the specified string value. */ public void visit(StringValue v) { if (null == v.text) { if (newline) printer.indent(); newline = false; printer.buffer().p("/* value = <text>; */").fit(); } else { format(false, v.text); } } /** Print the specified token value. */ public void visit(TokenValue v) { if (null == v.text) { if (newline) printer.indent(); newline = false; printer.buffer().p("/* value = Token(<text>); */").fit(); } else { format(true, v.text); } } /** Print the specified binding value. */ public void visit(BindingValue v) { if (newline) printer.indent(); newline = false; printer.buffer().p("/* value = ").p(v.binding.name).p("; */").fit(); } /** Print the specified empty list value. */ public void visit(EmptyListValue v) { if (newline) printer.indent(); newline = false; printer.buffer().p("/* value = []; */").fit(); } /** Print the specified proper list value. */ public void visit(ProperListValue v) { if (newline) printer.indent(); newline = false; printer.buffer().p("/* value = "); if (null == v.tail) { printer.p('['); boolean first = true; for (Binding b : v.elements) { if (first) { first = false; } else { printer.p(", "); } printer.p(b.name); } printer.p(']'); } else { for (Binding b : v.elements) { printer.p(b.name).p(':'); } printer.p(v.tail.name); } printer.p("; */").fit(); } /** Print the specified action base value. */ public void visit(ActionBaseValue v) { if (newline) printer.indent(); newline = false; printer.buffer().p("/* value = Action.run(").p(v.list.name).p(", "). p(v.seed.name).p("); */").fit(); } /** Print the specified generic node value. */ public void visit(GenericNodeValue v) { if (newline) printer.indent(); newline = false; printer.buffer().p("/* value = "); if (0 < v.formatting.size()) printer.p("Formatting([], "); printer.p("GNode(").p(Utilities.unqualify(v.name)).p(", ["); for (Iterator<Binding> iter = v.children.iterator(); iter.hasNext(); ) { printer.p(iter.next().name); if (iter.hasNext()) { printer.p(", "); } } printer.p("])"); if (0 < v.formatting.size()) { printer.p(", ["); boolean first = true; for (Binding b : v.formatting) { if (first) { first = false; } else { printer.p(", "); } printer.p(b.name); } printer.p("])"); } printer.p("; */").fit(); } /** Print the specified generic action value. */ public void visit(GenericActionValue v) { if (newline) printer.indent(); newline = false; printer.buffer().p("/* value = Action->"); if (0 < v.formatting.size()) printer.p("Formatting([], "); printer.p("GNode(").p(Utilities.unqualify(v.name)).p(", ["); Iterator<Binding> iter = v.children.iterator(); printer.p(v.first); if (iter.hasNext()) { printer.p(", "); } while (iter.hasNext()) { printer.p(iter.next().name); if (iter.hasNext()) { printer.p(", "); } } printer.p("])"); if (0 < v.formatting.size()) { printer.p(", ["); boolean first = true; for (Binding b : v.formatting) { if (first) { first = false; } else { printer.p(", "); } printer.p(b.name); } printer.p("])"); } printer.p("; */").fit(); } /** Print the specified generic recursion value. */ public void visit(GenericRecursionValue v) { if (newline) printer.indent(); newline = false; printer.buffer().p("/* value = Action->"); if (0 < v.formatting.size()) printer.p("Formatting([], "); printer.p("GNode(").p(Utilities.unqualify(v.name)).p(", ["); Iterator<Binding> iter = v.children.iterator(); printer.p(v.first); if (iter.hasNext()) { printer.p(", "); } while (iter.hasNext()) { printer.p(iter.next().name); if (iter.hasNext()) { printer.p(", "); } } printer.p("])"); if (0 < v.formatting.size()) { printer.p(", ["); boolean first = true; for (Binding b : v.formatting) { if (first) { first = false; } else { printer.p(", "); } printer.p(b.name); } printer.p("])"); } printer.p(':').p(v.list.name).p("; */").fit(); } }