summaryrefslogtreecommitdiff
path: root/web/c_cpp/cweb/examples/extex.w
blob: b060d7431a69197451661f040d8af9671d2defd2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
\datethis
@* Introduction. This program is a simple filter that inputs \TEX/ or \.{CWEB}
files and outputs its best guess at the ``words'' they contain. The word
list can then be passed to a spelling check routine such as {\tt wordtest}.

If this program is invoked with the name `{\tt excweb}', it will apply
special rules based on the syntax of \.{CWEB} files. Otherwise it will
use only the \TEX/ conventions. (Note that \UNIX/'s {\tt ln} command
allows a program to be invoked with more than one name although it
appears only once in the computer's memory.)

The \TEX/ conventions adopted here say that words are what remain
after you remove nonletters, control sequences,
comments triggered by \.\% marks, and material enclosed
within \.{\$...\$} or \.{\$\$...\$\$}. However, an apostrophe within
a word will be retained. The plain \TEX/ control
sequences for accented characters and special text characters, namely
$$\vbox{\halign{&\.{\\#}\hfil\qquad\cr
'&`&\relax\^&"&\relax\~&=&.&u&v\cr
H&t&c&d&b&oe&OE&ae&AE\cr
aa&AA&o&O&l&L&ss&i&j\cr}}$$
will also be retained, so that users can treat them as parts of words.
A blank space
following any of the alphabetic control sequences in this list will be carried
along too. If any of these control sequences is followed by \.\{, everything
up to the next \.\} will also be retained. Thus, for example, the
construction `\.{m\\=\{\\i\}n\\u\ us}' will be considered a single word,
in spite of the control sequences and the space between the two u's.
Discretionary hyphens `\.{\char`\\-}' are treated in the same way as accents.

The \.{CWEB} conventions are essentially the same as the \TEX/ conventions,
in the \TEX/ parts of a \.{CWEB} file. The \CEE/ parts of the file
are blanked out.

No attempt is made to reach a high level of artificial intelligence,
which would be able to truly understand the input file. Tricky users can
 confuse us. But we claim that devious tricks are their problem, not ours.

@ So here goes. The main idea is to keep a one-character lookahead
buffer, called |c|, which is set to zero when the character has been
processed. A giant switch to various cases, depending on the value of~|c|,
keeps everything moving.

If you don't like |goto| statements, don't read this. (And don't read
any other programs that simulate finite-state automata.)

@c
#include <stdio.h>
#include <ctype.h>
#include <string.h>
extern void exit(); /* system routine that terminates execution */
@#
@<Global variables@>@;
@<Procedures@>@;
@#
int main(argc,argv)
  int argc; /* the number of arguments (should be 1, but this isn't checked) */
  char *argv[]; /* the arguments (|*argv| is the program name) */
{
  @<Local variables@>;
  if (strlen(*argv)>=6 && strcmp(*argv+strlen(*argv)-6,"excweb")==0) {
    web=1;
    @<Adjust tables for \.{CWEB} mode@>;
  } else web=0;
  comment=skipping=c=0;
  main_cycle: if (c) goto big_switch;
  restart: c=get();
  big_switch: switch(c) {
     @<Special cases of the giant switch where we don't just discard |c|@>@;
    case EOF: exit(0);
    default: goto restart;
    }
    @<Labeled code segments, which exit by explicit |goto|@>;
}

@ @<Global variables@>=
int c; /* one-character look-see buffer */

@ @<Local variables@>=
int web; /* are we looking for \.{CWEB} constructs? */
int comment; /* are we inside a \CEE/ comment in a \.{CWEB} document? */
int skipping; /* are we skipping \CEE/ code in a \.{CWEB} document? */
int save_skipping; /* value of |skipping| outside current \CEE/ mode */
register int cc; /* temporary buffer */

@* Simple cases.
Let's do some of the easiest things first, in order to get the hang of
this program. Several special characters will cause us to ignore everything
until the first appearance of something else.

@d discard_to(x) {@+while (get()!=x) ;@+}
@d discard_to_dol {@+for (cc=c,c=get();c!='$' || cc=='\\';cc=c,c=get())
     if (cc=='\\' && c==cc) c='\0';@+}

@<Special cases...@>=
case '%': discard_to('\n');@+goto restart;
case '$': c=getchar();
  if (c!='$') discard_to_dol@;
  else { /* after \.{\$\$} we discard everything to the next \.{\$\$} */
    do discard_to_dol@;
    while (getchar()!='$');
  }
  goto restart;

@ The `|get|' procedure in the code above is like \Cee's standard
`|getchar|', except that it immediately terminates execution at the end of
the input file. Otherwise malformed input files could lead to
infinite loops.

@<Procedures@>=
int get()
{@+register int x;
  x=getchar();
  if (x==EOF) exit(0);
  return x;
}
 
@ More complex behavior is handled by jumping out of the |switch| statement
to one of the routines following it. None of the cases say |break|, so
the code following the switch statement is accessible only via |goto|.

@<Special cases...@>=
case 'a': case 'A':
case 'b': case 'B':
case 'c': case 'C':
case 'd': case 'D':
case 'e': case 'E':
case 'f': case 'F':
case 'g': case 'G':
case 'h': case 'H':
case 'i': case 'I':
case 'j': case 'J':
case 'k': case 'K':
case 'l': case 'L':
case 'm': case 'M':
case 'n': case 'N':
case 'o': case 'O':
case 'p': case 'P':
case 'q': case 'Q':
case 'r': case 'R':
case 's': case 'S':
case 't': case 'T':
case 'u': case 'U':
case 'v': case 'V':
case 'w': case 'W':
case 'x': case 'X':
case 'y': case 'Y':
case 'z': case 'Z':
goto out_word;

@ When letters appear in |stdin|, we pass them immediately through to |stdout|
with little further ado.
An apostrophe is rejected unless it is immediately followed by a letter.

@<Labeled code...@>=
out_word: putchar(c);
continue_word: c=getchar();
checkout_word:
if (isalpha(c)) goto out_word;
if (c=='\'') {
  c=getchar();
  if (isalpha(c)) {
    putchar('\'');@+goto out_word;
  }
  goto end_word;
}
if (c=='\\' && controlseq()) goto control_seq_in_word;
end_word: putchar('\n');
  goto main_cycle;

@* Control sequences.  The |controlseq()| function is the only
delicate part of this program.  After a backslash has been scanned,
|controlseq| looks to see if the next characters define one of the
special plain \TEX/ macros listed above. If so, the control sequence
and its immediately following argument (if any) are output and
|controlseq| returns a nonzero value. If not, nothing is output and
|controlseq| returns zero. In both cases the value of |c| will be
nonzero if and only if |controlseq| has had to look ahead at a
character it decided not to process.

@ @<Labeled code...@>=
control_seq_in_word: if (!c) goto continue_word;
goto checkout_word;

@ @<Special cases...@>=
case '\\': if (controlseq()) goto control_seq_in_word;
goto main_cycle;

@ @<Procedures@>=
int controlseq()
{
  int l; /* number of letters in the control sequence */
  char a,b; /* the first two characters after `\.\\' */
  l=0;
  a=c=getchar();
  while (isalpha(c)) {
    l++;
    c=getchar();
    if (l==1) b=c;
  }
  if (l==0) c=getchar();
  @<Check for special plain \TEX/ control sequences;
    output them and |return 1| if found@>;
  return 0;
}

@ @d pair(x,y) (a==x && b==y)

@<Check for special...@>=
if ((a>='"' && a<='~' && ptab[a-'"']==l) ||
 (l==2 && (pair('a','e') || pair('A','E')@|
           || pair('o','e') || pair('O','E')@|
           || pair('a','a') || pair('A','A') || pair('s','s')))) {
  putchar('\\');
  putchar(a);
  if (l==2) putchar(b);
  if (l && c==' ') {
    putchar(' '); /* optional space after alphabetic control sequence */
    c=getchar();
  }
  if (c=='{') {
    do@+{putchar(c);
      c=get();
    }@+while (c!='}'); /* optional argument after special control sequence */
    putchar(c);
    c=0;
  }
  return 1;
}

@ The |ptab| entries for nonletters are 0 when the control sequence is
special, otherwise~1; the conventions for letters are reversed.

@<Global...@>=
char ptab[]={0,1,1,1,1,0, /* \.{\\"} and \.{\\'} */
  1,1,1,1,1,0,0,1, /* \.{\\-} and \.{\\.} */
  1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1, /* \.{\\=} */
  1,0,0,0,0,0,0,0,1,0,0,0,1,0,0,1, /* \.{\\H}, \.{\\L}, \.{\\O} */
  0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1, /* \.{\\\^} */
  0,0,1,1,1,0,0,0, /* \.{\\`}, \.{\\b}, \.{\\c}, \.{\\d} */
  0,1,1,0,1,0,0,1, /* \.{\\i}, \.{\\j}, \.{\\l}, \.{\\o} */
  0,0,0,0,1,1,1,0, /* \.{\\t}, \.{\\u}, \.{\\v} */
  0,0,0,1,1,1,0}; /* \.{\\\~} */

@ In \.{CWEB} the \TEX/ control sequence `\.{\\.}' denotes the typewriter
font used for strings, not the dot-over accent. We must modify
|ptab| to reflect this unfortunate (but too-late-too-change) design decision.

@<Adjust tables for \.{CWEB} mode@>=
ptab[12]=1;

@* CWEB considerations.
We're finished now with all that would be needed if we only wanted to
handle \TEX/. For \.{CWEB} a bit more should be done.

The \.{CWEB} escape character is \.{@@}, and the character following
it tells us what mode we should enter. In \TEX/ mode we should not
only do what we normally do for \TEX/ files, we should also ignore
material delimited by \.{\char"7C...\char"7C}, being careful to recognize when
|'|'| is part of a string (as it just was). And we should stop \TEX/
mode if we scan \.{*/} while in a \CEE/ comment.

@<Special cases...@>=
case '@@': if (web) goto do_web;
  goto restart;
case '|': if (web>1) {
     save_skipping=skipping;
     goto skip_C_prime;
   }
   goto restart;
case '*': if (!comment) goto restart;
  c=getchar();
  if (c=='/') {
    comment=0;
    goto skip_C;
  }
  goto big_switch;

@ The characters that follow \.@@ in a \.{CWEB} file can be classified into
a few types that have distinct implications for {\tt excweb}.

@d nop 0 /* control code that doesn't matter to us */
@d start_section 1 /* control code that begins a \.{CWEB} section */
@d start_C 2 /* control code that begins \CEE/ code */
@d start_name 3 /* control code that begins a section name */
@d start_index 4 /* control code for \.{CWEB} index entry */
@d start_insert 5 /* control code for \CEE/ material ended by `\.{@@>}' */
@d end_item 6 /* `\.{@@>}' */
@d ignore_line 7 /* `\.{@@i}' or `\.{@@l}' */

@<Global...@>=
char wtab[]={start_section,nop,nop,nop,nop,nop,nop,nop, /* \.{\ !"\#\$\%\&'} */
 start_name,nop,start_section,nop,nop,nop,start_index,nop, /* \.{()*+,-./} */
 nop,nop,nop,nop,nop,nop,nop,nop, /* \.{01234567} */
 nop,nop,start_index,nop,start_name,start_insert,end_item,nop,
    /* \.{89:;<=>?} */
 nop,nop,nop,start_C,start_C,nop,start_C,nop, /* \.{@@ABCDEFG} */
 nop,ignore_line,nop,nop,ignore_line,nop,nop,nop, /* \.{HIJKLMNO} */
 start_C,start_insert,nop,start_C,start_insert,nop,nop,nop, /* \.{PQRSTUVW} */
 nop,nop,nop,nop,nop,nop,start_index,nop, /* \.{XYZ[\\]\^\_} */
 nop,nop,nop,start_C,start_C,nop,start_C,nop, /* \.{`abcdefg} */
 nop,ignore_line,nop,nop,ignore_line,nop,nop,nop, /* \.{hijklmno} */
 start_C,start_insert,nop,start_C,start_insert}; /* \.{pqrst} */

@ We do not leave \TEX/ mode until the first \.{WEB} section has begun.

@<Labeled code...@>=
do_web: c=getchar();
  if (c<' ' || c>'t') goto restart;
  switch(wtab[c-' ']){
 case nop: case start_index: goto restart;
 case start_section: web++; /* out of ``limbo'' */
  comment=skipping=0;@+goto restart;
 case start_C: if (web>1) goto skip_C;
  goto restart;
 case start_name: case start_insert: if (web>1) skipping=1;
  goto restart;
 case end_item: if (skipping) goto skip_C;
   goto restart;
 case ignore_line: discard_to('\n');
   goto restart;
  }

@ The final piece of program we need is a sub-automaton to pass over
the \CEE/ parts of a \.{CWEB} document. The main subtlety here is that
we don't want to get out of synch while scanning over a supposed
string constant or verbatim insert.

@<Labeled code...@>=
skip_C: save_skipping=2;
skip_C_prime: skipping=1;
  while (1) {
    c=get();
  C_switch: switch(c) {
   case '/': c=get();
     if (c!='*') goto C_switch;
     comment=1; /* fall through to the next case, returning to \TEX/ mode */
   case '|': if (save_skipping==2) continue; /* |'|'| as \CEE/ operator */
     skipping=save_skipping;@+goto restart; /* |'|'| as \.{CWEB} delimiter */
   case '@@': c=getchar();
    inner_switch: if (c<' ' || c>'t') continue;
     switch(wtab[c-' ']) {
    case nop: case start_C: case end_item: continue;
    case start_section: web++;
     comment=skipping=0;@+goto restart;
    case start_name: case start_index: goto restart;
    case start_insert: do@+discard_to('@@')@;@+while ((c=getchar())=='@@');
      goto inner_switch; /* now |c| should equal |'>'| */
    case ignore_line: discard_to('\n');
     continue;
     }
   case '\'': case '"':    
     while ((cc=get())!=c && cc!='\n')
       if (cc=='\\') getchar();
       else if (cc=='@@') {
         cc=getchar();
         if (cc!='@@') { /* syntax error, we try to recover */
           c=cc; goto inner_switch;
         }
       };
     continue; /* \.{CWEB} strings do not extend past one line */
    default: continue;
    }
  }

@* Index.