\section{Prettyprinting} \subsection{API for prettyprinting} Here is a suitable API for a prettyprinter for noweb. Input to be prettyprinted is a sequence of strings, newlines, and chunks used. A [[STRING]] is always free of newlines. <>= typedef enum parttype { START_OF_CHUNK=1, END_OF_CHUNK, STRING, USE_CHUNK, NEWLINE, LITERAL, DEFINITION, WHATSIT } Parttype; @ The prettyprinter calls back into the application as follows: <>= typedef void (*PrettyPrinterCallback) (void *closure, Parttype type, char *contents); @ The [[contents]] must be \begin{quote} \begin{tabular}{ll} [[START_OF_CHUNK]]&The name of the chunk being prettyprinted\\ [[END_OF_CHUNK]]&[[NULL]]\\ [[STRING]]&A string with no newlines\\ [[LITERAL]]${}^*$&A string with no newlines, to get no further formatting\\ [[USE_CHUNK]]&The name of the chunk referred to\\ [[NEWLINE]]&[[NULL]]\\ [[DEFINITION]]${}^*$&An identifier whose definition is in this chunk.\\ [[WHATSIT]]&An uninterpreted string passed to the callback function \end{tabular} \end{quote} Items marked with a star ($*$) are legal only when the prettyprinter is calling back into the application. The identifier given in [[DEFINITION]] isn't printed, so if the prettyprinter wants to print it, it has to send it twice. @ The prettyprinter exports two calls to the application. <>= typedef struct prettyprinter *PrettyPrinter; extern PrettyPrinter new_prettyprinter(PrettyPrinterCallback callback, void *closure); extern void prettyprint(PrettyPrinter pp, Parttype type, char *contents); @ The prettyprinter accepts calls to the [[prettyprint]] procedure. Any call to [[prettyprint]] may result in one or more callbacks to the procedure registered when [[new_prettyprinter]] is called. For any given [[PrettyPrinter]], the application must call [[prettyprint]] with [[type==END_OF_CHUNK]] exactly once, and that call must be the last call. <
>= <> @ \subsection{Calling the prettyprinter} The assumption here is that we have zero or more prettyprinters satisfying the interface above---what are the mechanics of getting something prettyprinted. We select a prettyprinter based on the name of the root chunk. Because we want to support either a two-pass or one-pass algorithm, we don't say how lines will come in or go out, but we do say that we will strip all the trailing newlines. <>= void pretty (char *getline(void *in), void *in, void putline(void *out, char *line), void *out, PrettyPrinter make_pp(void *cl, char *name), void *cl) { char *line = NULL; /* buffer for input */ PrettyPrinter pp; Location loc; while ((line = getline(in)) != NULL) { <> <> putline(out, line); for (line = getline(in); line && !is_keyword(line,"defn") && !is_keyword(line,"text"); line = getline(in) ) putline(out, line); insist(line,"defn","code chunk had no definition line"); putline(out, line); pp = make_pp(cl, line+6); /* use chunk name */ if (!pp) { <>; continue; } for (; line && !is_keyword(line, "nl") ;) { line = getline(in); putline(out, line); } insist(line,"nl","definition line not followed by newline"); loc.lineno++; for (line = getline(in); line != NULL && !is_end(line,"code"); line = getline(in)) { <> if (is_keyword(line,"nl")) { prettyprint(pp, NEWLINE, NULL); } else if (is_keyword(line,"text")) { prettyprint(pp, STRING, line+1+4+1); } else if (is_keyword(line,"use")) { prettyprint(pp, USE_CHUNK, line+1+3+1); } else { prettyprint(pp, WHATSIT, line); } } prettyprint(pp, END_OF_CHUNK, NULL); <> putline(out, line); } } <>= if (!is_begin(line, "code")) { putline(out, line); continue; } <>= while ((line = getline(in)) != NULL) { putline(out, line); if (is_end(line, "code")) break; } @ <>= { if (is_keyword(line, "nl") || is_index(line, "nl")) { loc.lineno++; } else if (is_keyword(line,"file")) { loc.filename = strsave(line + 6); loc.lineno = 1; } else if (is_keyword(line, "line")) { <> loc.lineno--; } } @ <>= { char *temp = line+6; <> loc.lineno = atoi(temp); } <>= { char *p; for (p = temp; *p; p++) if (!isdigit(*p)) errormsg(Error, "non-numeric line number in `@line %s'", temp); } <>= void insist(char *line, char *keyword, char *msg) { <> if (!is_keyword(line,keyword)) impossible(msg); } <>= if (line==NULL) { impossible("End of file occurred in mid-module"); } <>= void insist(char *line, char *keyword, char *msg); @ \subsection{Simple callback procedure} We've got a callback to do I/O <>= typedef struct io { FILE *out; void (*putline)(void *out, char *line); } *IO; static void write_pretty(void *closure, Parttype type, char *contents) { IO io = (IO) closure; switch(type) { case START_OF_CHUNK: break; case END_OF_CHUNK: break; case STRING: fprintf(io->out, "@text %s\n", contents); break; case LITERAL: fprintf(io->out, "@literal %s\n", contents); break; case USE_CHUNK: fprintf(io->out, "@use %s\n", contents); break; case NEWLINE: io->putline(io->out, "@nl"); break; case DEFINITION: fprintf(io->out, "@index defn %s\n", contents); break; case WHATSIT: io->putline(io->out, contents); break; default: assert(0); } } @ \subsection{Sketch for a two-pass driver} The best plan would be to use a two-pass driver. On the first pass it would \begin{itemize} \item Read in all lines and builds a def-use tree. \item Decide on a prettyprinter for each root, either using the root name or a suffix (and probably based on info from the command line as well as defaults). \item Propagate info from parents to children. Inconsistent children (distinct prettyprinters) become new roots. \end{itemize} Then on the second pass it could use the propagated info to select a prettyprinter for each chunk. \subsection{One-pass driver} This just uses a single prettyprinter. This means get and put are just I/O. <>= static char *get_stripped(void *in) { FILE *fp = (FILE *)in; char *line = getline_nw(fp); if (line) { int i = strlen(line) - 1; if (i >= 0 && line[i] == '\n') line[i] = 0; } return line; } static void put_stripped(void *out, char *line) { FILE *fp = (FILE *)out; fputs(line, fp); fputc('\n', fp); } @ If we only ever use a single prettyprinter, we make the closure the I/O closure. As a trick, we return no prettyprinter if the chunk name has a blank. <>= static PrettyPrinter make_pp(void *cl, char *name) { if (strchr(name, ' ')) return NULL; return new_prettyprinter(write_pretty, cl); } @ The program is always a filter, so it uses stdin and stdout. <>= main (int argc, char *argv[]) { struct io io; io.out = stdout; io.putline = put_stripped; pretty(get_stripped, stdin, put_stripped, stdout, make_pp, &io); return 0; } @ \subsection{Boilerplate} <<*>>= static char rcsid[] = "$Id: pretty.nw,v 1.19 2008/10/06 01:03:05 nr Exp nr $"; static char rcsname[] = "$Name: v2_12 $"; #include #include #include #include #include #include "strsave.h" #include "getline.h" #include "match.h" #include "errors.h" #include "pretty.h" typedef struct location { /* identify lines of source */ char *filename; int lineno; } Location; <> <> <>