/*
* xtc - The eXTensible Compiler
* Copyright (C) 2004 Robert Grimm
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* 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, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package xtc.parser;
import java.util.Iterator;
import java.util.List;
import xtc.Constants;
import xtc.util.Utilities;
import xtc.tree.Printer;
import xtc.tree.Visitor;
/**
* The grammar pretty printer.
*
* @author Robert Grimm
* @version $Revision: 1.1 $
*/
public class PrettyPrinter extends Visitor {
/** Flag for whether to explicitly mark choices. */
public static final boolean MARK_CHOICE = true;
/** The printer for this pretty printer. */
protected final Printer printer;
/** 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;
/**
* Create a new pretty printer.
*
* @param printer The printer for the new pretty printer.
*/
public PrettyPrinter(Printer printer) {
this.printer = printer;
printer.register(this);
}
/** Print the specified grammar. */
public void visit(Grammar g) {
// Emit header.
printer.sep();
printer.indent().p("// xtc ").pln(Constants.VERSION);
printer.indent().pln("// Packrat grammar pretty printer");
printer.sep();
printer.pln();
boolean printed = false;
// Emit package name.
if (null != g.pName) {
printed = true;
printer.indent().p("package ").p(g.pName).pln(';');
printer.pln();
}
// Emit header.
if (null != g.header) {
printed = true;
printer.indent().p("header ");
g.header.accept(this);
printer.pln();
}
// Emit class name.
if (! Grammar.DEFAULT_NAME.equals(g.cName)) {
printed = true;
printer.indent().p("class ").p(g.cName).pln(';');
printer.pln();
}
// Emit main action.
if (null != g.body) {
printed = true;
printer.indent().p("body ");
g.body.accept(this);
printer.pln();
}
// Emit footer.
if (null != g.footer) {
printed = true;
printer.indent().p("footer ");
g.footer.accept(this);
printer.pln();
}
if (printed) {
printer.sep();
printer.pln();
}
// Emit the top-level nonterminals. */
printer.indent().p("top ");
Iterator iter = g.topLevel.iterator();
boolean first = true;
while (iter.hasNext()) {
if (first) {
first = false;
} else {
printer.p(", ");
}
((NonTerminal)iter.next()).accept(this);
}
printer.pln(';').pln();
// Emit the productions.
iter = g.productions.iterator();
while (iter.hasNext()) {
((Production)iter.next()).accept(this);
}
printer.sep().pln();
}
/** Print the specified production. */
public void visit(Production p) {
if (p.hasProperty(DuplicateProductionFolder.DUPLICATES)) {
List sources = (List)p.getProperty(DuplicateProductionFolder.DUPLICATES);
printer.indent().pln("/*");
printer.indent().p(" * The following production is the result of ").
pln("folding duplicates");
printer.indent().p(" * ").pln(Utilities.format(sources));
printer.indent().pln(" */");
}
printer.indent();
if (p.isTransient) {
printer.p("transient ");
}
printer.p(p.type).p(' ');
p.nonTerminal.accept(this);
printer.p(" = ");
parenChoice = false;
parenSequence = false;
p.element.accept(this);
printer.incr().indent().pln(';').decr();
printer.pln();
}
/** Print the specified ordered choice. */
public void visit(OrderedChoice c) {
boolean choice = parenChoice;
boolean sequence = parenSequence;
if (choice) {
printer.p("( ");
}
if (MARK_CHOICE) {
printer.p("/* Choice */ ");
}
printer.pln().incr();
Iterator iter = c.options.iterator();
boolean firstOption = true;
while (iter.hasNext()) {
if (firstOption) {
firstOption = false;
printer.indent();
} else {
printer.indent().p("/ ");
}
parenChoice = true;
parenSequence = false;
newline = false;
((Element)iter.next()).accept(this);
if (! newline) {
printer.pln();
}
}
printer.decr();
if (choice) {
printer.indent().p(')');
}
parenChoice = choice;
parenSequence = sequence;
newline = false;
}
/** 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;
r.element.accept(this);
if (r.once) {
printer.p('+');
} else {
printer.p('*');
}
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;
o.element.accept(this);
printer.p('?');
parenChoice = choice;
parenSequence = sequence;
}
/** 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;
Iterator iter = s.elements.iterator();
boolean first = true;
while (iter.hasNext()) {
if (first) {
first = false;
if (sequence) {
printer.p('(');
}
} else {
printer.p(' ');
}
((Element)iter.next()).accept(this);
}
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.p('&');
p.element.accept(this);
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.p('!');
p.element.accept(this);
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.p('&');
p.element.accept(this);
parenChoice = choice;
parenSequence = sequence;
}
/** Print the specified binding. */
public void visit(Binding b) {
if (newline) printer.indent();
newline = false;
printer.p(b.name).p(':');
b.element.accept(this);
}
/** Print the specified string match. */
public void visit(StringMatch m) {
if (newline) printer.indent();
newline = false;
printer.p('\"').escape(m.text, Utilities.JAVA_ESCAPES).p("\":");
m.element.accept(this);
}
/** Print the specified nonterminal. */
public void visit(NonTerminal nt) {
if (newline) printer.indent();
newline = false;
printer.p(nt.name);
}
/** Print the specified string literal. */
public void visit(StringLiteral l) {
if (newline) printer.indent();
newline = false;
printer.p('\"').escape(l.text, Utilities.JAVA_ESCAPES).p('\"');
}
/** Print the specified any character element. */
public void visit(AnyChar a) {
if (newline) printer.indent();
newline = false;
printer.p('.');
}
/** Print the specified character literal. */
public void visit(CharLiteral l) {
if (newline) printer.indent();
newline = false;
printer.p('\'').escape(l.c, Utilities.JAVA_ESCAPES).p('\'');
}
/** 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, Utilities.FULL_ESCAPES);
} else {
printer.escape(r.first, Utilities.FULL_ESCAPES).p('-').
escape(r.last, Utilities.FULL_ESCAPES);
}
}
/** Print the specified character class. */
public void visit(CharClass c) {
if (newline) printer.indent();
newline = false;
if (c.exclusive) {
printer.p("/* Exclusive */ !");
}
printer.p('[');
Iterator iter = c.ranges.iterator();
while (iter.hasNext()) {
((CharRange)iter.next()).accept(this);
}
printer.p(']');
if (c.exclusive) {
printer.p(" .");
}
}
/** Print the specified character case. */
public void visit(CharCase c) {
if (newline) printer.indent();
newline = false;
c.klass.accept(this);
printer.p(' ');
if (null == c.element) {
printer.p("&{ false }");
} else {
c.element.accept(this);
}
}
/** Print the specified character switch. */
public void visit(CharSwitch s) {
boolean choice = parenChoice;
boolean sequence = parenSequence;
printer.pln("( /* Switch */").incr();
Iterator iter = s.cases.iterator();
boolean firstCase = true;
while (iter.hasNext()) {
if (firstCase) {
firstCase = false;
printer.indent();
} else {
printer.indent().p("/ ");
}
parenChoice = true;
parenSequence = false;
newline = false;
((CharCase)iter.next()).accept(this);
if (! newline) {
printer.pln();
}
}
if (null != s.base) {
printer.indent().p("/ . ");
parenChoice = true;
parenSequence = false;
newline = false;
s.base.accept(this);
if (! newline) {
printer.pln();
}
}
printer.decr().indent().p(')');
parenChoice = choice;
parenSequence = sequence;
newline = false;
}
/** Print the specified action. */
public void visit(Action a) {
if (newline) printer.indent();
if (0 == a.code.size()) {
// Nothing to do.
newline = false;
} else if (1 == a.code.size()) {
newline = false;
printer.p("{ ").p((String)a.code.get(0)).p(" }");
} else {
newline = true;
printer.pln('{').incr();
Iterator iter = a.code.iterator();
while (iter.hasNext()) {
printer.indent().pln((String)iter.next());
}
printer.decr().indent().pln('}');
}
}
/** Print the specified null value. */
public void visit(NullValue v) {
if (newline) printer.indent();
newline = false;
printer.p("/* value = null; */");
}
/** Print the specified string value. */
public void visit(StringValue v) {
if (newline) printer.indent();
if (-1 == v.text.indexOf("*/")) {
newline = false;
printer.p("/* value = \"").escape(v.text, Utilities.JAVA_ESCAPES).
p("\"; */");
} else {
// The string value contains the end sequence for a traditional
// C comment. We need to use a C++ comment.
newline = true;
printer.p("// value = \"").escape(v.text, Utilities.JAVA_ESCAPES).
pln("\";");
}
}
/** Print the specified text value. */
public void visit(TextValue v) {
if (newline) printer.indent();
newline = false;
printer.p("/* value = <text>; */");
}
/** Print the specified empty list value. */
public void visit(EmptyListValue v) {
if (newline) printer.indent();
newline = false;
printer.p("/* value = []; */");
}
/** Print the specified singleton list value. */
public void visit(SingletonListValue v) {
if (newline) printer.indent();
newline = false;
printer.p("/* value = [").p(v.value).p("]; */");
}
/** Print the specified list value. */
public void visit(ListValue v) {
if (newline) printer.indent();
newline = false;
printer.p("/* value = ").p(v.value).p(':').p(v.list).p("; */");
}
}