001: /*
002: * xtc - The eXTensible Compiler
003: * Copyright (C) 2005-2007 Robert Grimm
004: *
005: * This program is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU General Public License
007: * version 2 as published by the Free Software Foundation.
008: *
009: * This program is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: * GNU General Public License for more details.
013: *
014: * You should have received a copy of the GNU General Public License
015: * along with this program; if not, write to the Free Software
016: * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
017: * USA.
018: */
019: package xtc.parser;
020:
021: import java.io.File;
022: import java.io.IOException;
023: import java.io.PrintWriter;
024:
025: import java.util.ArrayList;
026: import java.util.Iterator;
027: import java.util.List;
028:
029: import xtc.Constants;
030:
031: import xtc.util.Runtime;
032: import xtc.util.Utilities;
033:
034: import xtc.tree.Attribute;
035: import xtc.tree.Comment;
036: import xtc.tree.Node;
037: import xtc.tree.Printer;
038: import xtc.tree.VisitingException;
039:
040: import xtc.type.AST;
041:
042: /**
043: * A grammar pretty printer producing HTML.
044: *
045: * @author Robert Grimm
046: * @version $Revision: 1.27 $
047: */
048: public class HtmlPrinter extends PrettyPrinter {
049:
050: /** The runtime. */
051: protected final Runtime runtime;
052:
053: /** The analyzer utility. */
054: protected final Analyzer analyzer;
055:
056: /** The flag for whether we are processing a grammar or single module. */
057: protected boolean isGrammar;
058:
059: /** The number of the current production. */
060: protected int pNumber = -1;
061:
062: /**
063: * Create a new HTML printer.
064: *
065: * @param runtime The runtime.
066: * @param analyzer The analyzer.
067: * @param ast The type operations.
068: * @param verbose The verbose flag.
069: */
070: public HtmlPrinter(Runtime runtime, Analyzer analyzer, AST ast,
071: boolean verbose) {
072: super (ast, verbose);
073: this .runtime = runtime;
074: this .analyzer = analyzer;
075: }
076:
077: protected int stringEscapes() {
078: return Utilities.JAVA_HTML_ESCAPES;
079: }
080:
081: protected int regexEscapes() {
082: return Utilities.FULL_HTML_ESCAPES;
083: }
084:
085: /**
086: * Create a printer for the specified file. This method initializes
087: * this class' {@link #printer printer} to a printer writing to the
088: * file with the specified name in the runtime's output directory.
089: *
090: * @param name The file name.
091: */
092: protected void open(String name) throws IOException {
093: File file = new File(runtime.getOutputDirectory(), name);
094: printer = new Printer(new PrintWriter(runtime.getWriter(file)));
095: }
096:
097: protected void printDocumentation(Module m) {
098: Comment c = m.documentation;
099:
100: // Make sure we have something to print.
101: if ((!verbose) || (null == c) || (0 == c.text.size())) {
102: return;
103: }
104:
105: // Start the containing div.
106: printer.indent().pln("<div class=\"module-documentation\">");
107:
108: List<String> authors = null;
109: String version = null;
110: for (String s : c.text) {
111: if (s.startsWith("@")) {
112: // Process @ tag.
113: if (s.startsWith("@author ")) {
114: if (null == authors) {
115: authors = new ArrayList<String>();
116: }
117: authors.add(s.substring(8));
118:
119: } else if (s.startsWith("@version ")) {
120: version = s.substring(9);
121: }
122:
123: } else {
124: printer.indent().pln(s);
125: }
126: }
127:
128: // Do we have any @ tags?
129: if ((null != authors) || (null != version)) {
130: printer.indent().pln("<dl>");
131:
132: if (null != authors) {
133: if (1 == authors.size()) {
134: printer.indent().pln("<dt>Author:</dt>").incr();
135: } else {
136: printer.indent().pln("<dt>Authors:</dt>").incr();
137: }
138: printer.indent().p("<dd>");
139: Iterator<String> iter = authors.iterator();
140: while (iter.hasNext()) {
141: printer.p(iter.next());
142: if (iter.hasNext()) {
143: printer.p(", ");
144: }
145: }
146: printer.pln("</dd>").decr();
147: }
148:
149: if (null != version) {
150: printer.indent().pln("<dt>Version:</dt>").incr();
151: printer.indent().p("<dd>").p(version).pln("</dd>")
152: .decr();
153: }
154:
155: printer.indent().pln("</dl>");
156: }
157:
158: // Close the div.
159: printer.indent().pln("</div>");
160: }
161:
162: protected void printOption(Module m) {
163: if ((null != m.attributes) && (0 < m.attributes.size())) {
164: printer.pln().indent().p("option ");
165:
166: boolean isFirst = true;
167: Iterator<Attribute> iter = m.attributes.iterator();
168: while (iter.hasNext()) {
169: Attribute att = iter.next();
170: String name = att.getName();
171: String attText = att.toString();
172: boolean highlight = (analyzer.isTopLevel(m)
173: || Constants.ATT_STATEFUL.getName()
174: .equals(name)
175: || Constants.NAME_STRING_SET.equals(name) || Constants.NAME_FLAG
176: .equals(name));
177:
178: if (isFirst) {
179: isFirst = false;
180: } else if (printer.column() + attText.length() + 1 > Constants.LINE_LENGTH) {
181: printer.pln().indentMore();
182: }
183:
184: if (highlight) {
185: int column = printer.column();
186: printer.p("<span class=\"highlight\">").column(
187: column);
188: }
189: printer.p(attText);
190: if (highlight) {
191: int column = printer.column();
192: printer.p("</span>").column(column);
193: }
194:
195: if (iter.hasNext()) {
196: printer.p(", ");
197: } else {
198: printer.p(';');
199: }
200: }
201: printer.pln();
202: }
203: }
204:
205: /**
206: * Actually print the specified module.
207: *
208: * @param m The module to print.
209: */
210: protected void print(Module m) {
211: // Set up the HTML.
212: printer.indent().pln(
213: "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"");
214: printer.indent().p(" ").pln(
215: "\"http://www.w3.org/TR/html4/strict.dtd\">");
216: printer.indent().pln("<html>");
217: printer.indent().pln("<head>");
218: printer.indent().p("<!-- Generated by Rats!, version ").p(
219: Constants.VERSION).p(", ").p(Constants.COPY)
220: .pln(" -->");
221: printer.indent().p("<title>Module ").p(m.name).pln("</title>");
222: printer.indent().p(
223: "<link rel=\"stylesheet\" href=\"grammar.css\" ").pln(
224: "type=\"text/css\">");
225: printer.indent().pln("</head>");
226: printer.indent().pln("<body>");
227:
228: // Emit module documentation.
229: printDocumentation(m);
230:
231: // Emit module name and parameters.
232: printer.indent().pln("<pre class=\"module-header\">");
233: printModule(m);
234:
235: // Emit module dependencies.
236: if ((null != m.dependencies) && (0 < m.dependencies.size())) {
237: printer.pln();
238: for (ModuleDependency dep : m.dependencies) {
239: printer.p(dep);
240: }
241: }
242:
243: // Emit header, body, and footer actions.
244: printActions(m);
245:
246: // Emit grammar-wide options.
247: printOption(m);
248: printer.indent().pln("</pre>");
249:
250: // Emit the productions.
251: final int length = m.productions.size();
252: for (int i = 0; i < length; i++) {
253: pNumber = i;
254: printer.p(m.productions.get(i));
255: }
256:
257: // Finish the HTML.
258: printer.indent().pln("</body>");
259: }
260:
261: public void visit(Grammar g) {
262: // Initialize the per-grammar state.
263: analyzer.register(this );
264: analyzer.init(g);
265: isGrammar = true;
266:
267: // Iterate over the grammar's modules.
268: for (Module m : g.modules) {
269: try {
270: open(m.name + ".html");
271: } catch (IOException x) {
272: throw new VisitingException("Unable to access "
273: + m.name + ".html", x);
274: }
275: printer.register(this );
276: analyzer.process(m);
277: print(m);
278: printer.flush();
279: }
280: }
281:
282: public void visit(Module m) {
283: // Initialize the per-module state.
284: analyzer.register(this );
285: analyzer.init(m);
286: isGrammar = false;
287:
288: try {
289: open(m.name + ".html");
290: } catch (IOException x) {
291: throw new VisitingException("Unable to access " + m.name
292: + ".html", x);
293: }
294: printer.register(this );
295:
296: // Print the module.
297: print(m);
298:
299: // Flush the printer.
300: printer.flush();
301: }
302:
303: /**
304: * Print a linked version of the specified module name.
305: *
306: * @param name The module name.
307: * @param resolved The flag for whether the name can be resolved.
308: */
309: protected void print(ModuleName name, boolean resolved) {
310: if (resolved) {
311: int column = printer.column();
312: printer.p("<a href=\"").p(name.name).p(".html\">").column(
313: column).p(name.name);
314: } else {
315: int column = printer.column();
316: printer
317: .p(
318: "<a class=\"erroneous\" href=\"#\" title=\"Undefined module\">")
319: .column(column).p(name.name);
320: }
321: int column = printer.column();
322: printer.p("</a>").column(column);
323: }
324:
325: protected void print(ModuleDependency dep, String name) {
326: Module m = analyzer.lookup(dep.visibleName());
327: printer.indent().p(name).p(' ');
328: if ((null == dep.target)
329: && ((null != m) || (!"instantiate".equals(name)))) {
330: print(dep.module, null != m);
331: } else {
332: printer.p(dep.module);
333: }
334: if (0 != dep.arguments.size()) {
335: printer.p(dep.arguments);
336: }
337: if ((null != dep.target)
338: && ((null != m) || (!"instantiate".equals(name)))) {
339: print(dep.target, null != m);
340: } else {
341: printer.p(dep.target);
342: }
343: printer.pln(';');
344: }
345:
346: protected void enter(Production p) {
347: // Print a comment for productions folded from duplicates.
348: if (verbose && p.hasProperty(Properties.DUPLICATES)) {
349: List<String> sources = Properties.getDuplicates(p);
350: printer.indent().pln(
351: "<div class=\"production-documentation\">");
352: printer.indent().p(
353: "The following production is the result of ").p(
354: "folding duplicates ");
355: Iterator<String> iter = sources.iterator();
356: while (iter.hasNext()) {
357: String name = iter.next();
358:
359: printer.buffer();
360: if ((1 < sources.size()) && (!iter.hasNext())) {
361: printer.p("and ");
362: }
363: printer.p(name);
364: if ((2 == sources.size()) && (iter.hasNext())) {
365: printer.p(' ');
366: } else if (iter.hasNext()) {
367: printer.p(", ");
368: } else {
369: printer.p('.');
370: }
371: printer.fit();
372: }
373: printer.pln();
374: printer.indent().pln("</div>");
375: }
376:
377: // Get ready to print the attributes, type, and name.
378: printer.indent().pln("<pre class=\"production-body\">");
379: printer.indent();
380:
381: // The production's anchor is the nonterminal's name for full
382: // productions and the name, followed by a minus sign, followed by
383: // the production's number (in the module's list of productions)
384: // for partial productions.
385: if (p.isFull()) {
386: printer.p("<a name=\"").p(p.name.name).p("\"></a>");
387: } else {
388: printer.p("<a name=\"").p(p.name.name).p('-').p(pNumber).p(
389: "\"></a>");
390: }
391:
392: // Print the attributes and type as is.
393: if ((null != p.attributes) && (0 < p.attributes.size())) {
394: // Declare attributes as nodes so that overload resolution
395: // invokes Printer.p(Node) and therefore visit(Attribute).
396: for (Node att : p.attributes) {
397: printer.p(att).p(' ');
398: }
399: }
400:
401: if (null != p.type) {
402: if (AST.isVoid(p.type)) {
403: printer.p("void ");
404: } else if (AST.isGenericNode(p.type)) {
405: printer.p("generic ");
406: } else {
407: printer.p(ast.extern(p.type)).p(' ');
408: }
409: } else if (null != p.dType) {
410: printer.p(p.dType).p(' ');
411: }
412:
413: // If the production is partial, we currently link to the modified
414: // production.
415: if (p.isPartial()) {
416: boolean duplicate = false;
417: Production base = null;
418: try {
419: base = analyzer.lookup(p.name.qualify(analyzer
420: .currentModule().name.name));
421: } catch (IllegalArgumentException x) {
422: duplicate = true;
423: }
424: if (null == base) {
425: printer.p("<a class=\"erroneous\" href=\"#\" title=\"");
426: if (duplicate) {
427: printer.p("Ambiguous nonterminal");
428: } else {
429: printer.p("Undefined nonterminal");
430: }
431: } else {
432: Module m = analyzer.currentModule();
433: String q = base.qName.getQualifier();
434: if (q.equals(m.name.name)) {
435: printer.p("<a href=\"#").p(p.name.name);
436: } else {
437: printer.p("<a href=\"").p(q).p(".html#").p(
438: p.name.name);
439: }
440: }
441: printer.p("\">").p(p.name.name).p("</a>");
442:
443: } else {
444: printer.p(p.name.name);
445: }
446:
447: // Set the internal state.
448: parenChoice = false;
449: parenSequence = false;
450: }
451:
452: protected void exit(Production p) {
453: printer.indent().pln("</pre>");
454: }
455:
456: public void visit(SequenceName n) {
457: printer.p("<").p(n.name).p(">");
458: printer.column(printer.column() - 6);
459: }
460:
461: public void visit(NonTerminal nt) {
462: if (newline)
463: printer.indent();
464: newline = false;
465: printer.buffer();
466:
467: // Look up the corresponding production.
468: int column = printer.column();
469: boolean duplicate = false;
470: Production p = null;
471: try {
472: p = analyzer.lookup(nt);
473: } catch (IllegalArgumentException x) {
474: duplicate = true;
475: }
476:
477: // Print the corresponding link.
478: if (null == p) {
479: printer.p("<a class=\"erroneous\" href=\"#\" title=\"");
480: if (duplicate) {
481: printer.p("Ambiguous nonterminal");
482: } else {
483: printer.p("Undefined nonterminal");
484: }
485: } else {
486: if (isGrammar) {
487: // We need to distinguish between links within the same module
488: // and links across modules.
489: Module m = analyzer.currentModule();
490: String q = p.qName.getQualifier();
491: if (q.equals(m.name.name)) {
492: printer.p("<a href=\"#").p(p.name.name);
493: } else {
494: printer.p("<a href=\"").p(q).p(".html#").p(
495: p.name.name);
496: }
497: } else {
498: printer.p("<a href=\"#").p(nt.name);
499: }
500: }
501: printer.p("\">").column(column).p(nt.name);
502: column = printer.column();
503: printer.p("</a>").column(column).fit();
504: }
505:
506: protected void print(String s) {
507: int column = printer.column();
508: int length = s.length();
509: for (int i = 0; i < length; i++) {
510: char c = s.charAt(i);
511: if ('<' == c) {
512: printer.p("<");
513: } else if ('>' == c) {
514: printer.p(">");
515: } else {
516: printer.p(c);
517: }
518: }
519: printer.column(column + length);
520: }
521:
522: public void visit(StringValue v) {
523: if (null == v.text) {
524: if (newline)
525: printer.indent();
526: newline = false;
527: printer.buffer().p("/* value = <text>; */").column(
528: printer.column() - 6).fit();
529: } else {
530: format(false, v.text);
531: }
532: }
533:
534: public void visit(TokenValue v) {
535: if (null == v.text) {
536: if (newline)
537: printer.indent();
538: newline = false;
539: printer.buffer().p("/* value = Token(<text>); */")
540: .column(printer.column() - 6).fit();
541: } else {
542: format(true, v.text);
543: }
544: }
545:
546: }
|