[project @ 1996-01-08 20:28:12 by partain]
[ghc-hetmet.git] / ghc / utils / unlit / unlit.c
1 /* unlit.c                                   Wed Dec  5 17:16:24 GMT 1990
2  *
3  * Literate script filter.  In contrast with the format used by most
4  * programming languages, a literate script is a program in which
5  * comments are given the leading role, whilst program text must be
6  * explicitly flagged as such by placing a `>' character in the first
7  * column on each line.  It is hoped that this style of programming will
8  * encourage the writing of accurate and clearly documented programs
9  * in which the writer may include motivating arguments, examples
10  * and explanations.  
11  *
12  * Unlit is a filter that can be used to strip all of the comment lines
13  * out of a literate script file.  The command format for unlit is:
14  *              unlit [-n] [-q] ifile ofile
15  * where ifile and ofile are the names of the input (literate script) and
16  * output (raw program) files respectively.  Either of these names may
17  * be `-' representing the standard input or the standard output resp.
18  * A number of rules are used in an attempt to guard against the most
19  * common errors that are made when writing literate scripts:
20  * 1) Empty script files are not permitted.  A file in which no lines
21  *    begin with `>' usually indicates a file in which the programmer
22  *    has forgotten about the literate script convention.
23  * 2) A line containing part of program definition (i.e. preceeded by `>')
24  *    cannot be used immediately before or after a comment line unless
25  *    the comment line is blank.  This error usually indicates that
26  *    the `>' character has been omitted from a line in a section of
27  *    program spread over a number of lines.
28  * Using the -q (quiet) flag suppresses the signalling of these error
29  * conditions.  The default behaviour can be selected explicitly using
30  * the -n (noisy) option so that any potential errors in the script file
31  * are reported.
32  *
33  * The original idea for the use of literate scripts is due to Richard
34  * Bird of the programming Research Group, Oxford and was initially
35  * adopted for use in the implementation of the functional programming
36  * language Orwell used for teaching in Oxford.  This idea has subsequently
37  * been borrowed in a number of other language implementations.
38  *
39  * Modified to understand \begin{code} ... \end{code} used in Glasgow.  -- LA
40  * And \begin{pseudocode} ... \end{pseudocode}.  -- LA
41  */
42
43 #include <stdio.h>
44 #include <ctype.h>
45
46 #define NULLSTR       ((char *)0)
47 #define DEFNCHAR      '>'
48 #define MISSINGBLANK  "unlit: Program line next to comment"
49 #define EMPTYSCRIPT   "unlit: No definitions in file (perhaps you forgot the '>'s?)"
50 #define USAGE         "usage: unlit [-q] [-n] [-c] file1 file2\n"
51 #define CANNOTOPEN    "unlit: cannot open \"%s\"\n"
52 #define DISTINCTNAMES "unlit: input and output filenames must differ\n"
53 #define MISSINGCODE   "unlit: missing %s\n"
54
55 #define BEGINCODE "\\begin{code}"
56 #define LENBEGINCODE 12
57 #define ENDCODE "\\end{code}"
58 #define LENENDCODE 10
59 #ifdef PSEUDOCODE
60 /* According to Will Partain, the inventor of pseudocode, this gone now. */
61 #define BEGINPSEUDOCODE "\\begin{pseudocode}"
62 #define LENBEGINPSEUDOCODE 18
63 #define ENDPSEUDOCODE "\\end{pseudocode}"
64 #define LENENDPSEUDOCODE 16
65 #endif
66
67 typedef enum { START, BLANK, TEXT, DEFN, BEGIN, /*PSEUDO,*/ END, HASH } line;
68 #define isWhitespace(c)  (c==' '  || c=='\t')
69 #define isLineTerm(c)    (c=='\n' || c==EOF)
70
71 static int noisy  = 1;   /* 0 => keep quiet about errors, 1 => report errors */
72 static int errors = 0;   /* count the number of errors reported              */
73 static int crunchnl = 0; /* don't print \n for removed lines                 */
74 static int leavecpp = 1; /* leave preprocessor lines */
75
76 /* complain(file,line,what)
77  *
78  * print error message `what' for `file' at `line'.  The error is suppressed
79  * if noisy is not set.
80  */
81
82 complain(file, lin, what)
83 char *file;
84 char *what;
85 int lin; {
86     if (noisy) {
87         if (file)
88             fprintf(stderr, "%s ", file);
89         fprintf(stderr,"line %d: %s\n",lin,what);
90         errors++;
91     }
92 }
93
94 #define TABPOS 8
95
96 /* As getc, but does TAB expansion */
97 int
98 egetc(istream)
99 FILE *istream;
100 {
101     static int spleft = 0;
102     static int linepos = 0;
103     int c;
104
105     if (spleft > 0) {
106         spleft--;
107         linepos++;
108         return ' ';
109     }
110     c = getc(istream);
111     if (c == EOF)
112         return c;
113     else if (c == '\n' || c == '\f') {
114         linepos = 0;
115         return c;
116     } else if (c == '\t') {
117         spleft = TABPOS - linepos % TABPOS;
118         spleft--;
119         linepos++;
120         return ' ';
121     } else {
122         linepos++;
123         return c;
124     }
125
126 }
127
128 /* readline(istream, ostream)
129  *
130  * Read a line from the input stream `istream', and return a value
131  * indicating whether that line was:
132  *     BLANK (whitespace only),
133  *     DEFN  (first character is DEFNCHAR),
134  *     TEXT  (a line of text)
135  *     BEGIN (a \begin{code} line)
136  *     PSEUDO (a \begin{pseodocode} line)
137  *     HASH  (a preprocessor line)
138  * or  END   (indicating an EOF).
139  * Lines of type DEFN are copied to the output stream `ostream'
140  * (without the leading DEFNCHAR).  BLANK and TEXT lines are
141  * replaced by empty (i.e. blank lines) in the output stream, so
142  * that error messages refering to line numbers in the output file
143  * can also be used to locate the corresponding line in the input
144  * stream.
145  */
146
147 line readline(istream,ostream)
148 FILE *istream, *ostream; {
149     int c = egetc(istream);
150     char buf[100];
151     int i;
152
153     if (c==EOF)
154         return END;
155
156     if (leavecpp && c=='#') {
157         putc(c, ostream);
158         while (c=egetc(istream), !isLineTerm(c))
159             putc(c,ostream);
160         putc('\n',ostream);
161         return HASH;
162     }
163
164     if (c==DEFNCHAR) {
165 /*      putc(' ',ostream);*/
166         while (c=egetc(istream), !isLineTerm(c))
167             putc(c,ostream);
168         putc('\n',ostream);
169         return DEFN;
170     }
171
172     if (!crunchnl)
173         putc('\n',ostream);
174
175     while (isWhitespace(c))
176         c=egetc(istream);
177     if (isLineTerm(c))
178         return BLANK;
179
180     i = 0;
181     buf[i++] = c;
182     while (c=egetc(istream), !isLineTerm(c))
183         if (i < sizeof buf - 1)
184             buf[i++] = c;
185     while(i > 0 && isspace(buf[i-1]))
186         i--;
187     buf[i] = 0;
188     if (strcmp(buf, BEGINCODE) == 0)
189         return BEGIN;
190 #ifdef PSEUDOCODE
191     else if (strcmp(buf, BEGINPSEUDOCODE) == 0)
192         return PSEUDO;
193 #endif
194     else
195         return TEXT;
196 }
197
198
199 /* unlit(file,istream,ostream)
200  *
201  * Copy the file named `file', accessed using the input stream `istream'
202  * to the output stream `ostream', removing any comments and checking
203  * for bad use of literate script features:
204  *  - there should be at least one BLANK line between a DEFN and TEXT
205  *  - there should be at least one DEFN line in a script.
206  */
207
208 unlit(file, istream, ostream)
209 char *file;
210 FILE *istream;
211 FILE *ostream; {
212     line last, this=START;
213     int  linesread=0;
214     int  defnsread=0;
215
216     do {
217         last = this;
218         this = readline(istream, ostream);
219         linesread++;
220         if (this==DEFN)
221             defnsread++;
222         if (last==DEFN && this==TEXT)
223             complain(file, linesread-1, MISSINGBLANK);
224         if (last==TEXT && this==DEFN)
225             complain(file, linesread, MISSINGBLANK);
226         if (this == BEGIN) {
227             /* start of code, copy to end */
228             char lineb[1000];
229             for(;;) {
230                 if (fgets(lineb, sizeof lineb, istream) == NULL) {
231                     fprintf(stderr, MISSINGCODE, ENDCODE);
232                     exit(1);
233                 }
234                 linesread++;
235                 if (strncmp(lineb,ENDCODE,LENENDCODE) == 0) {
236                     putc('\n', ostream);
237                     break;
238                 }
239                 fputs(lineb, ostream);
240             }
241             defnsread++;
242         }
243 #ifdef PSEUDOCODE
244         if (this == PSEUDO) {
245             char lineb[1000];
246             for(;;) {
247                 if (fgets(lineb, sizeof lineb, istream) == NULL) {
248                     fprintf(stderr, MISSINGCODE, ENDPSEUDOCODE);
249                     exit(1);
250                 }
251                 linesread++;
252                 putc('\n', ostream);
253                 if (strncmp(lineb,ENDPSEUDOCODE,LENENDPSEUDOCODE) == 0) {
254                     break;
255                 }
256             }
257         }
258 #endif
259     } while(this!=END);
260
261     if (defnsread==0)
262         complain(file,linesread,EMPTYSCRIPT);
263 }
264
265 /* main(argc, argv)
266  *
267  * Main program.  Processes command line arguments, looking for leading:
268  *  -q  quiet mode - do not complain about bad literate script files
269  *  -n  noisy mpde - complain about bad literate script files.
270  * Expects two additional arguments, a file name for the input and a file
271  * name for the output file.  These two names must normally be distinct.
272  * An exception is made for the special name "-" which can be used in either
273  * position to specify the standard input or the standard output respectively.
274  */
275
276 main(argc,argv)
277 int argc;
278 char **argv; {
279     FILE *istream, *ostream;
280     char *file;
281
282     for (argc--, argv++; argc > 0; argc--, argv++)
283         if (strcmp(*argv,"-n")==0)
284             noisy = 1;
285         else if (strcmp(*argv,"-q")==0)
286             noisy = 0;
287         else if (strcmp(*argv,"-c")==0)
288             crunchnl = 1;
289         else
290             break;
291
292     if (argc!=2) {
293         fprintf(stderr, USAGE);
294         exit(1);
295     }
296
297     if (strcmp(argv[0],argv[1])==0 && strcmp(argv[0],"-")!=0) {
298         fprintf(stderr, DISTINCTNAMES);
299         exit(1);
300     }
301
302     file = argv[0];
303     if (strcmp(argv[0], "-")==0) {
304         istream = stdin;
305         file    = "stdin";
306     }
307     else
308         if ((istream=fopen(argv[0], "r")) == NULL) {
309             fprintf(stderr, CANNOTOPEN, argv[0]);
310             exit(1);
311         }
312
313     if (strcmp(argv[1], "-")==0) 
314         ostream = stdout; 
315     else
316         if ((ostream=fopen(argv[1], "w")) == NULL)  {
317             fprintf(stderr, CANNOTOPEN, argv[1]);
318             exit(1);
319         }
320
321     unlit(file, istream, ostream);
322
323     fclose(istream);
324     fclose(ostream);
325
326     exit(errors==0 ? 0 : 1);
327 }