/* * xtc - The eXTensible Compiler * Copyright (C) 2005-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.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import xtc.Constants; import xtc.util.Runtime; import xtc.util.Utilities; import xtc.tree.Attribute; import xtc.tree.Comment; import xtc.tree.Node; import xtc.tree.Printer; import xtc.tree.VisitingException; import xtc.type.AST; /** * A grammar pretty printer producing HTML. * * @author Robert Grimm * @version $Revision: 1.28 $ */ public class HtmlPrinter extends PrettyPrinter { /** The runtime. */ protected final Runtime runtime; /** The analyzer utility. */ protected final Analyzer analyzer; /** The flag for whether we are processing a grammar or single module. */ protected boolean isGrammar; /** The number of the current production. */ protected int pNumber = -1; /** * Create a new HTML printer. * * @param runtime The runtime. * @param analyzer The analyzer. * @param ast The type operations. * @param verbose The verbose flag. */ public HtmlPrinter(Runtime runtime,Analyzer analyzer,AST ast,boolean verbose) { super(ast, verbose); this.runtime = runtime; this.analyzer = analyzer; } protected int stringEscapes() { return Utilities.JAVA_HTML_ESCAPES; } protected int regexEscapes() { return Utilities.FULL_HTML_ESCAPES; } /** * Create a printer for the specified file. This method initializes * this class' {@link #printer printer} to a printer writing to the * file with the specified name in the runtime's output directory. * * @param name The file name. */ protected void open(String name) throws IOException { File file = new File(runtime.getOutputDirectory(), name); printer = new Printer(new PrintWriter(runtime.getWriter(file))); } protected void printDocumentation(Module m) { Comment c = m.documentation; // Make sure we have something to print. if (! verbose || null == c || c.text.isEmpty()) { return; } // Start the containing div. printer.indent().pln("<div class=\"module-documentation\">"); List<String> authors = null; String version = null; for (String s : c.text) { if (s.startsWith("@")) { // Process @ tag. if (s.startsWith("@author ")) { if (null == authors) { authors = new ArrayList<String>(); } authors.add(s.substring(8)); } else if (s.startsWith("@version ")) { version = s.substring(9); } } else { printer.indent().pln(s); } } // Do we have any @ tags? if ((null != authors) || (null != version)) { printer.indent().pln("<dl>"); if (null != authors) { if (1 == authors.size()) { printer.indent().pln("<dt>Author:</dt>").incr(); } else { printer.indent().pln("<dt>Authors:</dt>").incr(); } printer.indent().p("<dd>"); Iterator<String> iter = authors.iterator(); while (iter.hasNext()) { printer.p(iter.next()); if (iter.hasNext()) { printer.p(", "); } } printer.pln("</dd>").decr(); } if (null != version) { printer.indent().pln("<dt>Version:</dt>").incr(); printer.indent().p("<dd>").p(version).pln("</dd>").decr(); } printer.indent().pln("</dl>"); } // Close the div. printer.indent().pln("</div>"); } protected void printOption(Module m) { if ((null != m.attributes) && (0 < m.attributes.size())) { printer.pln().indent().p("option "); boolean isFirst = true; Iterator<Attribute> iter = m.attributes.iterator(); while (iter.hasNext()) { Attribute att = iter.next(); String name = att.getName(); String attText = att.toString(); boolean highlight = (analyzer.isTopLevel(m) || Constants.ATT_STATEFUL.getName().equals(name) || Constants.NAME_STRING_SET.equals(name) || Constants.NAME_FLAG.equals(name)); if (isFirst) { isFirst = false; } else if (printer.column()+attText.length()+1>Constants.LINE_LENGTH) { printer.pln().indentMore(); } if (highlight) { int column = printer.column(); printer.p("<span class=\"highlight\">").column(column); } printer.p(attText); if (highlight) { int column = printer.column(); printer.p("</span>").column(column); } if (iter.hasNext()) { printer.p(", "); } else { printer.p(';'); } } printer.pln(); } } /** * Actually print the specified module. * * @param m The module to print. */ protected void print(Module m) { // Set up the HTML. printer.indent().pln("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\""); printer.indent().p(" "). pln("\"http://www.w3.org/TR/html4/strict.dtd\">"); printer.indent().pln("<html>"); printer.indent().pln("<head>"); printer.indent().p("<!-- Generated by Rats!, version ").p(Constants.VERSION). p(", ").p(Constants.COPY).pln(" -->"); printer.indent().p("<title>Module ").p(m.name).pln("</title>"); printer.indent().p("<link rel=\"stylesheet\" href=\"grammar.css\" "). pln("type=\"text/css\">"); printer.indent().pln("</head>"); printer.indent().pln("<body>"); // Emit module documentation. printDocumentation(m); // Emit module name and parameters. printer.indent().pln("<pre class=\"module-header\">"); 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); printer.indent().pln("</pre>"); // Emit the productions. final int length = m.productions.size(); for (int i=0; i<length; i++) { pNumber = i; printer.p(m.productions.get(i)); } // Finish the HTML. printer.indent().pln("</body>"); } public void visit(Grammar g) { // Initialize the per-grammar state. analyzer.register(this); analyzer.init(g); isGrammar = true; // Iterate over the grammar's modules. for (Module m : g.modules) { try { open(m.name + ".html"); } catch (IOException x) { throw new VisitingException("Unable to access " + m.name + ".html", x); } printer.register(this); analyzer.process(m); print(m); printer.flush(); } } public void visit(Module m) { // Initialize the per-module state. analyzer.register(this); analyzer.init(m); isGrammar = false; try { open(m.name + ".html"); } catch (IOException x) { throw new VisitingException("Unable to access " + m.name + ".html", x); } printer.register(this); // Print the module. print(m); // Flush the printer. printer.flush(); } /** * Print a linked version of the specified module name. * * @param name The module name. * @param resolved The flag for whether the name can be resolved. */ protected void print(ModuleName name, boolean resolved) { if (resolved) { int column = printer.column(); printer.p("<a href=\"").p(name.name).p(".html\">").column(column). p(name.name); } else { int column = printer.column(); printer.p("<a class=\"erroneous\" href=\"#\" title=\"Undefined module\">"). column(column).p(name.name); } int column = printer.column(); printer.p("</a>").column(column); } protected void print(ModuleDependency dep, String name) { Module m = analyzer.lookup(dep.visibleName()); printer.indent().p(name).p(' '); if ((null == dep.target) && ((null != m) || (! "instantiate".equals(name)))) { print(dep.module, null != m); } else { printer.p(dep.module); } if (0 != dep.arguments.size()) { printer.p(dep.arguments); } if ((null != dep.target) && ((null != m) || (! "instantiate".equals(name)))) { print(dep.target, null != m); } else { printer.p(dep.target); } printer.pln(';'); } 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("<div class=\"production-documentation\">"); printer.indent().p("The following production is the result of "). p("folding duplicates "); Iterator<String> iter = sources.iterator(); while (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("</div>"); } // Get ready to print the attributes, type, and name. printer.indent().pln("<pre class=\"production-body\">"); printer.indent(); // The production's anchor is the nonterminal's name for full // productions and the name, followed by a minus sign, followed by // the production's number (in the module's list of productions) // for partial productions. if (p.isFull()) { printer.p("<a name=\"").p(p.name.name).p("\"></a>"); } else { printer.p("<a name=\"").p(p.name.name).p('-').p(pNumber).p("\"></a>"); } // Print the attributes and type as is. 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(' '); } // If the production is partial, we currently link to the modified // production. if (p.isPartial()) { boolean duplicate = false; Production base = null; try { base = analyzer.lookup(p.name.qualify(analyzer.currentModule().name.name)); } catch (IllegalArgumentException x) { duplicate = true; } if (null == base) { printer.p("<a class=\"erroneous\" href=\"#\" title=\""); if (duplicate) { printer.p("Ambiguous nonterminal"); } else { printer.p("Undefined nonterminal"); } } else { Module m = analyzer.currentModule(); String q = base.qName.getQualifier(); if (q.equals(m.name.name)) { printer.p("<a href=\"#").p(p.name.name); } else { printer.p("<a href=\"").p(q).p(".html#").p(p.name.name); } } printer.p("\">").p(p.name.name).p("</a>"); } else { printer.p(p.name.name); } // Set the internal state. parenChoice = false; parenSequence = false; } protected void exit(Production p) { printer.indent().pln("</pre>"); } public void visit(SequenceName n) { printer.p("<").p(n.name).p(">"); printer.column(printer.column() - 6); } public void visit(NonTerminal nt) { if (newline) printer.indent(); newline = false; printer.buffer(); // Look up the corresponding production. int column = printer.column(); boolean duplicate = false; Production p = null; try { p = analyzer.lookup(nt); } catch (IllegalArgumentException x) { duplicate = true; } // Print the corresponding link. if (null == p) { printer.p("<a class=\"erroneous\" href=\"#\" title=\""); if (duplicate) { printer.p("Ambiguous nonterminal"); } else { printer.p("Undefined nonterminal"); } } else { if (isGrammar) { // We need to distinguish between links within the same module // and links across modules. Module m = analyzer.currentModule(); String q = p.qName.getQualifier(); if (q.equals(m.name.name)) { printer.p("<a href=\"#").p(p.name.name); } else { printer.p("<a href=\"").p(q).p(".html#").p(p.name.name); } } else { printer.p("<a href=\"#").p(nt.name); } } printer.p("\">").column(column).p(nt.name); column = printer.column(); printer.p("</a>").column(column).fit(); } protected void print(String s) { int column = printer.column(); int length = s.length(); for (int i=0; i<length; i++) { char c = s.charAt(i); if ('<' == c) { printer.p("<"); } else if ('>' == c) { printer.p(">"); } else { printer.p(c); } } printer.column(column + length); } public void visit(StringValue v) { if (null == v.text) { if (newline) printer.indent(); newline = false; printer.buffer().p("/* value = <text>; */"). column(printer.column()-6).fit(); } else { format(false, v.text); } } public void visit(TokenValue v) { if (null == v.text) { if (newline) printer.indent(); newline = false; printer.buffer().p("/* value = Token(<text>); */"). column(printer.column()-6).fit(); } else { format(true, v.text); } } }