diff options
Diffstat (limited to 'web/c_cpp/cweb')
-rw-r--r-- | web/c_cpp/cweb/ctang-man.ch | 2 | ||||
-rw-r--r-- | web/c_cpp/cweb/cweav-man.ch | 2 | ||||
-rw-r--r-- | web/c_cpp/cweb/cwebman.pdf | bin | 0 -> 335909 bytes | |||
-rw-r--r-- | web/c_cpp/cweb/examples/Makefile | 57 | ||||
-rw-r--r-- | web/c_cpp/cweb/examples/README | 55 | ||||
-rw-r--r-- | web/c_cpp/cweb/examples/extex.ch | 23 | ||||
-rw-r--r-- | web/c_cpp/cweb/examples/extex.w | 366 | ||||
-rw-r--r-- | web/c_cpp/cweb/examples/kspell.el | 228 | ||||
-rw-r--r-- | web/c_cpp/cweb/examples/oemacs.el | 176 | ||||
-rw-r--r-- | web/c_cpp/cweb/examples/oemacs.w | 896 | ||||
-rw-r--r-- | web/c_cpp/cweb/examples/treeprint.w | 248 | ||||
-rw-r--r-- | web/c_cpp/cweb/examples/wc-dos.ch | 8 | ||||
-rw-r--r-- | web/c_cpp/cweb/examples/wc.w | 254 | ||||
-rw-r--r-- | web/c_cpp/cweb/examples/wmer-os2.ch | 21 | ||||
-rw-r--r-- | web/c_cpp/cweb/examples/wmerg-pc.ch | 22 | ||||
-rw-r--r-- | web/c_cpp/cweb/examples/wmerge.w | 717 | ||||
-rw-r--r-- | web/c_cpp/cweb/examples/wordtest.w | 540 | ||||
-rw-r--r-- | web/c_cpp/cweb/examples/xlib_types.w | 92 | ||||
-rw-r--r-- | web/c_cpp/cweb/examples/xview_types.w | 274 |
19 files changed, 3979 insertions, 2 deletions
diff --git a/web/c_cpp/cweb/ctang-man.ch b/web/c_cpp/cweb/ctang-man.ch index 12e9d3248e..5b4d06c88b 100644 --- a/web/c_cpp/cweb/ctang-man.ch +++ b/web/c_cpp/cweb/ctang-man.ch @@ -16,7 +16,7 @@ under the terms of a permission notice identical to this one. } @y \def\botofcontents{\vfill\titlefalse} -\def\contentspagenumber{63} +\def\contentspagenumber{66} \def\title{APPENDIX E: CTANGLE} \let\K=\leftarrow @z diff --git a/web/c_cpp/cweb/cweav-man.ch b/web/c_cpp/cweb/cweav-man.ch index 6cfcc8fe2d..b72360944a 100644 --- a/web/c_cpp/cweb/cweav-man.ch +++ b/web/c_cpp/cweb/cweav-man.ch @@ -16,7 +16,7 @@ under the terms of a permission notice identical to this one. } @y \def\botofcontents{\vfill\titlefalse} -\def\contentspagenumber{109} +\def\contentspagenumber{111} \def\title{APPENDIX F: CWEAVE} \let\K=\leftarrow @z diff --git a/web/c_cpp/cweb/cwebman.pdf b/web/c_cpp/cweb/cwebman.pdf Binary files differnew file mode 100644 index 0000000000..034674005d --- /dev/null +++ b/web/c_cpp/cweb/cwebman.pdf diff --git a/web/c_cpp/cweb/examples/Makefile b/web/c_cpp/cweb/examples/Makefile new file mode 100644 index 0000000000..c029d805c0 --- /dev/null +++ b/web/c_cpp/cweb/examples/Makefile @@ -0,0 +1,57 @@ +# +# generic Makefile to use when you've got CWEB +# (add it to, or add to it, whatever you like) +# +#====================================================================== + +CFLAGS = -g +MLIB = -lm +RM = /bin/rm -f +WFILES = extex.w treeprint.w wc.w wmerge.w wordtest.w oemacs.w + +#====================================================================== + +.SUFFIXES: .tex .dvi .w + +.w.tex: + cweave $* + +.tex.dvi: + tex $< + +.w.dvi: + make $*.tex + make $*.dvi + +.w.c: + ctangle $* + +.w.o: + make $*.c + make $*.o + +.c.o: + cc $(CFLAGS) -c $*.c + +.w: + make $*.c + cc $(CFLAGS) $*.c -o $* + +#====================================================================== + +all: $(WFILES:.w=) excweb + +excweb: extex + ln extex excweb + +oemacs: oemacs.c + cc $(CFLAGS) -I/usr/openwin/include oemacs.c \ + -o oemacs -lxview -lolgx -lX11 + +doc: $(WFILES:.w=.dvi) + +clean: + $(RM) *~ \#~ .\#* *.o *.log *.dvi *.toc core a.out + +clobber: clean + $(RM) $(WFILES:.w=) $(WFILES:.w=.c) $(WFILES:.w=.tex) diff --git a/web/c_cpp/cweb/examples/README b/web/c_cpp/cweb/examples/README new file mode 100644 index 0000000000..ca21cf075c --- /dev/null +++ b/web/c_cpp/cweb/examples/README @@ -0,0 +1,55 @@ +This directory contains several files for programs that are tutorial +and/or useful. If you are new to CWEB, read "wc.w" first; it's a +typical example of CWEB applied to system programming. + +The Makefile on this directory is a generic way to build applications +written in CWEB. Say, for example, "make wc" to get an executable +program "wc" from wc.w. Say "make wc.dvi" to get a printable listing +of its documentation. The program "wc" is a slight improvement over +UNIX's standard word-count utility. (Try it, for example, with "wc *".) + +The treeprint program, by Norman Ramsey, generates tree-structured +representations of file hierarchies. To use it, say "make treeprint.run" +and then + find dir -type p -print | sort | treeprint +(on a UNIX system), where dir is a directory with interesting subdirectories. + +The wmerge program, by Silvio Levy, produces a single CWEB file from a +given CWEB file and a change file. It expands any "web include" (@i) +specifications in the original CWEB file. To use it, say "make wmerge.run" +and then, e.g., "wmerge foo.w bar.ch > foobar.w". Both CTANGLE and +CWEAVE already accept change files, so you need not merge anything yourself +in normal day-to-day operations; but wmerge can be useful in connection with +the spelling checker described below. + +If you intend to read the source code of CTANGLE and CWEAVE, you may +well wish to read wmerge.w first, or the hardcopy version obtained from +"make wmerge.dvi"; it is similar but much simpler. + +The extex and wordtest programs, by Don Knuth, are useful spellcheckers. +After "make extex.run" say also "ln extex excweb"; this gives +you two filter programs, extex and excweb, that remove extraneous +material from TeX and CWEB source files, respectively. To use them, +you can say for example + cat foo.w | excweb | spell +(or replace "cat foo.w" with "wmerge foo.w bar.ch"). A similar pipeline +for TeX files would be + cat foo.tex | extex | spell +Even better is to replace "spell" by "wordtest /usr/local/lib/dict/words", +if you have a suitable dictionary. One such dictionary in the public domain +can be found in directory ~ftp/pub/dict at ftp.cs.stanford.edu, available +via anonymous ftp. To make your own dictionary from given ones, + cat dict1 dict2 dict3 dict4 | wordtest > words +is quick and effective. See the documentation of wordtest for more info. + +A file kspell.el that provides an emacs user interface to extex+excweb+wordtest +also appears in this directory. (Somebody should write ELWEB...) + +The files xview_types.w and xlib_types.w are examples of @i (include) files +that can be used when writing XView programs. They are based on the header +files in OpenWindows Version 3.0. + +An example XView application appears in Knuth's program oemacs.w, which +he uses for heavy emacsing. It's not very portable, but if you have +OpenWindows3 and a Sun Type-4 keyboard you may like it as much as he does. +(It replaces "emacstool".) The file oemacs.el goes with it. diff --git a/web/c_cpp/cweb/examples/extex.ch b/web/c_cpp/cweb/examples/extex.ch new file mode 100644 index 0000000000..1b3f0cebd1 --- /dev/null +++ b/web/c_cpp/cweb/examples/extex.ch @@ -0,0 +1,23 @@ +@x In my work (mainly TAOCP) I use \: (not \.) for the dot accent +'&`&\relax\^&"&\relax\~&=&.&u&v\cr +@y +'&`&\relax\^&"&\relax\~&=&:&u&v\cr +@z +@x + 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, /* \.{\\=} */ +@y + 1,1,1,1,1,0,1,1, /* \.{\\-} */ + 1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1, /* \.{:} and \.{\\=} */ +@z +@x and I use |...xref-tag...| for cross-references +case '|': if (web>1) { + save_skipping=skipping; + goto skip_C_prime; + } +@y +case '|': if (web>1) { + save_skipping=skipping; + goto skip_C_prime; + } else discard_to('|'); +@z diff --git a/web/c_cpp/cweb/examples/extex.w b/web/c_cpp/cweb/examples/extex.w new file mode 100644 index 0000000000..b060d7431a --- /dev/null +++ b/web/c_cpp/cweb/examples/extex.w @@ -0,0 +1,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. diff --git a/web/c_cpp/cweb/examples/kspell.el b/web/c_cpp/cweb/examples/kspell.el new file mode 100644 index 0000000000..1825021280 --- /dev/null +++ b/web/c_cpp/cweb/examples/kspell.el @@ -0,0 +1,228 @@ +;; Alternative spelling enterface for Emacs, contributed by Don Knuth +;; (Uses the wordtest and extex programs, which are +;; supplied as examples with the CWEB distribution, +;; available by anonymous ftp from ftp.cs.stanford.edu) + +;; Based in part on "spell.el" from GNU Emacs; redistributed under +;; the terms of the GNU General Public License; NO WARRANTY implied. + +;; To install this, using the default directories defined below, +;; install wordtest and extex in /usr/local/bin, then say +;; "ln /usr/local/bin/extex /usr/local/bin/excweb", then install +;; a suitable dictionary in /usr/local/lib/dict/words; one such +;; dictionary can be found in ~ftp/pub/dict/words at ftp.cs.stanford.edu. +;; Also create an empty file called .words in your home directory. +;; Finally, add (load-library "kspell") to your .emacs file, or +;; include such a line in site-init.el if kspell is to be used by everybody. +;; If you get a message like "Checking spelling of buffer...not correct" +;; and nothing else, the probable cause is that the wordtest program +;; could not open /usr/local/lib/dict/words or ~/.words for reading. + +;; Note (10 Feb 1998): The spell-check logic failed to catch "obso1ete" +;; as a scanner-typo for "obsolete". The reason is that, although "obso" +;; and "ete" are non-words, they couldn't be found in query-replace +;; for the regexps "\bobso\b" and "\bete\b". + +(provide 'kspell) + +(defvar wordtest-command "wordtest" ;; maybe "/usr/local/bin/wordtest" better? + "*Command to run the wordtest program; can include command-line options.") + +;; "wordtest [options] [dictionaries] <infile >outfile" outputs all +;; lines of infile that don't appear in the dictionaries. The options +;; can define arbitrary character code mappings of 8-bit characters. +;; The default mapping takes a-z into A-Z, otherwise is ASCII. + +(defvar wordtest-filter "extex" ;; maybe "/usr/local/bin/extex" is better? + "*Command to run the filter needed by wordtest.") +(make-variable-buffer-local 'wordtest-filter) + +;; The extex filter extracts words from its input and outputs them on +;; separate lines as required by wordtest. It removes TeX control +;; sequences except those used to make accents and special characters. +;; There's a companion filter excweb that also removes C code from CWEBs. +(setq cweb-mode-hook '(lambda () (setq wordtest-filter "excweb"))) + +(defvar wordtest-system-dictionary "/usr/local/lib/dict/words" + "*Sorted dictionary containing all \"correct\" words, +including all variant forms obtained by prefix and suffix transformations.") +;; The standard UNIX dictionary /usr/dict/words is NOT satisfactory. + +(defvar wordtest-personal-dictionary "~/.words" + "*Default dictionary to supplement the words in the system dictionary. +If nil, no supplementary dictionary will be used. +This dictionary must be in alphabetic order as defined by wordtest. +Inserting any word with the + option to kspell-region will sort the file.") +(make-variable-buffer-local 'wordtest-personal-dictionary) + +(defun set-personal-dictionary (filename) + "Defines the supplementary personal dictionary for kspell to use in the +current buffer, overriding the default value of wordtest-personal-dictionary." + (interactive "FPersonal dictionary file name: ") + (setq wordtest-personal-dictionary filename)) + +(defun unset-personal-dictionary () + "Tells kspell not to use personal spelling dictionary with current buffer." + (interactive) + (setq wordtest-personal-dictionary nil)) + +(defun insert-into-personal-dictionary (word) + "Put WORD into user's dictionary and sort that dictionary." + (interactive "sword: ") + (let ((xword (concat word "\n"))) + (if (null wordtest-personal-dictionary) + (setq wordtest-personal-dictionary + (read-string "Personal dictionary file name: " "~/.words"))) + (set-buffer (find-file-noselect wordtest-personal-dictionary)) + (goto-char (point-min)) + (insert xword) + (call-process-region (point-min) (point-max) shell-file-name + t t nil "-c" wordtest-command) + (search-backward xword (point-min) 1) ;; in case the user is watching + (while (not (bolp)) (search-backward xword (point-min) 1)) + (save-buffer))) + +(defun kspell-buffer () + "Check spelling of every word in the buffer. +For each incorrect word, you are asked for the correct spelling +and then put into a query-replace to fix some or all occurrences. + +If you do not want to change a word, just give the same word +as its \"incorrect\" spelling; then the query replace is skipped. +Words are given in lowercase, but they will be Capitalized when +replacing Capitalized words, ALL_CAPS when replacing ALL_CAPS words. +If you type ? after a replacement, your correction will first be +looked up in the dictionary, and the query-replace will occur +only if the replacement is found. If you type + after a replacement, +your replacement will be inserted into the current personal dictionary. + +You can leave the minibuffer to do some other editing and then come +back again to the query-replace loop by typing \\[other-window]." + (interactive) + (save-excursion (kspell-region (point-min) (point-max) "buffer"))) + +(defun kspell-word () + "Check spelling of the word at or before point. +If it is not correct, ask user for the correct spelling and +query-replace the entire buffer to substitute it as with kspell-buffer." + (interactive) + (let (beg end wordtest-filter) + (save-excursion + (if (not (looking-at "\\<")) + (forward-word -1)) + (setq beg (point)) + (forward-word 1) + (setq end (point)) + (kspell-region beg end (buffer-substring beg end))))) + +(defun kspell-region (start end &optional description) + "Like kspell-buffer but checks only words in the current region. +Used in a program, applies from START to END. +DESCRIPTION is an optional string naming the unit being checked: +for example, \"buffer\"." + (interactive "r") + (let (correct + (filter wordtest-filter) + (buf (get-buffer-create " *kspell*")) ;; hidden by list-buffers + (dicts wordtest-system-dictionary)) + (if wordtest-personal-dictionary + (setq dicts (concat dicts " " wordtest-personal-dictionary))) + (save-excursion + (save-excursion + (set-buffer buf) + (widen) + (erase-buffer)) + (message "Checking spelling of %s..." (or description "region")) + (if (and (null filter) + (< end (point-max)) + (= ?\n (char-after end))) + (call-process-region start (1+ end) shell-file-name + nil buf nil "-c" + (concat wordtest-command " " dicts)) + (let ((oldbuf (current-buffer))) + (save-excursion + (set-buffer buf) + (insert-buffer-substring oldbuf start end) + (or (bolp) (insert ?\n)) + (if filter + (call-process-region (point-min) (point-max) shell-file-name + t t nil "-c" filter)) + (call-process-region (point-min) (point-max) shell-file-name + t t nil "-c" + (concat wordtest-command " " dicts))))) + (setq correct (save-excursion (set-buffer buf) (= (buffer-size) 0))) + (message "Checking spelling of %s...%scorrect" + (or description "region") + (if correct "" "not ")) + (if correct t + (let (word newword qtext lastchar + (case-fold-search t) + (case-replace t)) + (while (save-excursion + (set-buffer buf) + (> (buffer-size) 0)) + (save-excursion + (set-buffer buf) + (goto-char (point-min)) + (setq word (downcase + (buffer-substring (point) + (progn (end-of-line) (point))))) + (forward-char 1) ;; pass the newline + (delete-region (point-min) (point)) + (setq qtext (concat "\\b" (regexp-quote word) "\\b"))) + (goto-char (point-min)) + (setq lastchar nil) + (if (re-search-forward qtext nil t) + (while (null lastchar) + (setq newword + (read-string + (concat "edit a replacement for `" word "': ") + word)) + (if (null newword) (setq lastchar 0) + (setq lastchar (string-to-char (substring newword -1))) + (if (memq lastchar '(?? ?+)) + (setq newword (substring newword 0 -1)))) + (cond ((= lastchar ??) + (cond ((or (string= word newword) (string= "" newword)) + (describe-function 'kspell-buffer) + (setq lastchar nil)) + ((not (kspelt-right newword)) + (setq lastchar nil)))) + ((= lastchar ?+) + (save-excursion + (insert-into-personal-dictionary newword)))) + (cond ((string= word newword)) + ((null lastchar)) + (t + (goto-char (point-min)) + (if (or (equal word newword) (null lastchar)) t + (query-replace-regexp qtext newword)))))))))))) + +(defun kspelt-right (word) + "T if WORD is in the system dictionary or user's personal dictionary." + (let ((buf (get-buffer-create " *temp*")) + (pdict wordtest-personal-dictionary)) + (message "Checking spelling of %s..." word) + (save-excursion + (set-buffer buf) + (widen) + (erase-buffer) + (insert word "\n") + (if pdict + (call-process-region (point-min) (point-max) shell-file-name + t t nil "-c" + (concat wordtest-command " " + wordtest-system-dictionary " " + pdict)) + (call-process-region (point-min) (point-max) shell-file-name + t t nil "-c" + (concat wordtest-command " " + wordtest-system-dictionary))) + (= 0 (buffer-size))))) + +(defun kspell-string (string) + "Check spelling of string supplied as argument." + (interactive "sSpell string: ") + (message "%s is %scorrect" string + (if (kspelt-right string) "" "in"))) + diff --git a/web/c_cpp/cweb/examples/oemacs.el b/web/c_cpp/cweb/examples/oemacs.el new file mode 100644 index 0000000000..1f16c51e78 --- /dev/null +++ b/web/c_cpp/cweb/examples/oemacs.el @@ -0,0 +1,176 @@ +;; macros to go with oemacs + +; suntool-map is the map for "\C-x*", the oemacs prefix for function keys +; commented-out lines agree with the settings in $(EMACS)/lisp/term/sun.el + +(setq suntool-map-hooks '( ; not your usual hook list +; (define-key suntool-map "al" 'keyboard-quit) ; Stop +; (define-key suntool-map "bl" 'redraw-display) ; Again L2 +; (define-key suntool-map "b\M-l" 'repeat-complex-command); M-Again = C-X ESC +; (define-key repeat-complex-command-map "\C-x*b\M-l" +; 'previous-complex-command) ; M-Again M-Again... + (define-key suntool-map "bL" 'rerun-prev-command) ; AGAIN (no confirm) +; (define-key suntool-map "b\M-L" 'rerun-prev-command) ; M-AGAIN +; (define-key suntool-map "cl" 'buffer-menu) ; Props +; (define-key suntool-map "dl" 'undo) ; Undo +; (define-key suntool-map "el" 'ignore-key) ; Front +; (define-key suntool-map "fl" 'sun-select-region) ; Copy +; (define-key suntool-map "f," 'copy-region-as-kill) ; C-Copy +; (define-key suntool-map "gl" 'ignore-key) ; Open +; (define-key suntool-map "hl" 'sun-yank-selection) ; Paste +; (define-key suntool-map "h," 'yank) ; C-Paste +; (define-key suntool-map "il" 'research-forward) ; Find (with default) +; (define-key suntool-map "i\M-l" 'research-backward) ; M-Find +; (define-key suntool-map "iL" 'isearch-forward-regexp) ; FIND (incremental) +; (define-key suntool-map "i\M-L" 'isearch-backward-regexp); M-FIND +; (define-key suntool-map "i," 're-search-forward) ; C-Find (ordinary) +; (define-key suntool-map "i\M-," 're-search-backward) ; C-M-Find + (define-key suntool-map "jl" 'sun-cut-region) ; Cut + (define-key suntool-map "j," 'kill-region-and-unmark) ; C-Cut + (define-key suntool-map "jL" 'pop-the-mark) ; CUT +; (define-key suntool-map "j\M-l" 'exchange-point-and-mark); M-Cut + + (define-key suntool-map "at" 'bury-buffer) ; F1 +; (define-key suntool-map "bt" 'toggle-selective-display) ; F2 +; (define-key suntool-map "ct" 'scroll-down-in-place) ; F3 +; (define-key suntool-map "cT" '(lambda(n) (interactive "p") (scroll-down n))) +; (define-key suntool-map "dt" 'scroll-up-in-place) ; F4 +; (define-key suntool-map "dT" '(lambda(n) (interactive "p") (scroll-up n))) + (define-key suntool-map "et" 'jumpup) ; F5 +; (define-key suntool-map "ft" 'shrink-window) ; F6 +; (define-key suntool-map "fT" 'shrink-window-horizontally) ; Shift-F6 +; (define-key suntool-map "gt" 'enlarge-window) ; F7 +; (define-key suntool-map "gT" 'enlarge-window-horizontally) ; Shift-F7 + (define-key suntool-map "ht" 'tags-search) ; F8 + (define-key suntool-map "it" 'tags-query-replace) ; F9 + (define-key suntool-map "jt" 'narrow-to-region) ; F10 + (define-key suntool-map "j\M-t" 'narrow-to-page) ; M-F10 + (define-key suntool-map "kt" 'revert-buffer) ; F11 + (define-key suntool-map "k\M-t" 'quick-revert-other-buffer) ; M-F11 + (define-key suntool-map "lt" 'goto-line) ; F12 + (define-key suntool-map "l\M-t" 'goto-line) ; M-F12 + + (define-key suntool-map "ar" 'redraw-display) ; Pause, R1 + (define-key suntool-map "br" 'call-secondlast-kbd-macro) ; PrSc, R2 + (define-key suntool-map "cr" 'ignore-key) ; Scroll Lock Break, R3 + (define-key suntool-map "dr" 'unbound-key) ; KP=, R4 + (define-key suntool-map "d\M-r" 'bind-last-kbd-macro-to-KP=) ; M-KP=, M-R4 + (define-key suntool-map "er" 'unbound-key) ; KP/, R5 + (define-key suntool-map "e\M-r" 'bind-last-kbd-macro-to-KP/) ; M-KP/, M-R5 + (define-key suntool-map "fr" 'unbound-key) ; KP*, R6 + (define-key suntool-map "f\M-r" 'bind-last-kbd-macro-to-KP*) ; M-KP*, M-R6 +; (define-key suntool-map "gr" 'beginning-of-buffer) ; Home, R7 + +; (define-key suntool-map "hr" 'previous-line) ; Up, R8 +; (define-key suntool-map "ir" 'scroll-down) ; PgUp, R9 +; (define-key suntool-map "iR" 'backward-page) ; Shift-PGUP +; (define-key suntool-map "jr" 'backward-char) ; Lft, R10 + (define-key suntool-map "kr" "\C-u\C-l") ; KP5, R11 +; (define-key suntool-map "lr" 'forward-char) ; Rt, R12 +; (define-key suntool-map "mr" 'end-of-buffer) ; End, R13 +; (define-key suntool-map "nr" 'next-line) ; Dn, R14 +; (define-key suntool-map "or" 'scroll-up) ; PgDn, R15 +; (define-key suntool-map "oR" 'forward-page) ; Shift-PGUP + + (define-key suntool-map "ab" 'apropos) ; Help + (define-key suntool-map "a\M-b" 'mouse-help) ; M-Help + (define-key suntool-map "aB" 'describe-bindings) ; HELP + (define-key suntool-map "a\M-B" 'describe-mouse-bindings); M-HELP + (define-key suntool-map "a\"" 'command-apropos) ; C-Help + (define-key suntool-map "a\C-B" 'apropos) ; C-HELP + (define-key suntool-map "a\M-\C-B" 'help-for-dummies) ; M-C-HELP + (define-key suntool-map "bb" 'ignore-key) ; Alt + (define-key suntool-map "cb" 'ignore-key) ; AltGraph + (define-key suntool-map "db" 'auto-fill-mode) ; Keypad Ins + (define-key suntool-map "eb" 'overwrite-mode) ; Keypad Del + (define-key suntool-map "fb" 'call-last-kbd-macro) ; Keypad Enter + (define-key suntool-map "gb" 'forward-paragraph) ; Keypad + + (define-key suntool-map "hb" 'backward-paragraph) ; Keypad - + )) + + +; redefinition of emacs functions +(defun end-of-buffer () + "Move point to the end of the buffer; leave a mark at previous position. +Scroll so that point is at the bottom of the window, if possible." + (interactive) + (push-mark) + (goto-char (point-max)) + (recenter -1)) + +; new functions +(defun jumpup () + "Scroll so that point is at the top of the window." + (interactive)(recenter 0)) + +(defun quick-revert-buffer () + "Revert the buffer without asking for confirmation." + (interactive) + (revert-buffer t t)) + +(defun quick-revert-other-buffer (n) + "Revert the Nth other buffer without asking for confirmation." + (interactive "p") + (other-window n) + (revert-buffer t t) + (other-window (- n))) + +(defun bind-last-kbd-macro-to-KP= () + "Assign the last keyboard macro to the = key on the numeric keypad." + (interactive) + (define-key suntool-map "dr" last-kbd-macro)) + +(defun bind-last-kbd-macro-to-KP/ () + "Assign the last keyboard macro to the / key on the numeric keypad." + (interactive) + (define-key suntool-map "er" last-kbd-macro)) + +(defun bind-last-kbd-macro-to-KP* () + "Assign the last keyboard macro to the * key on the numeric keypad." + (interactive) + (define-key suntool-map "fr" last-kbd-macro)) + +(defvar secondlast-kbd-macro nil) +(defun pre-end-kbd-macro () + "Preserve last-kbd-macro as secondlast-kbd-macro, then end-kbd-macro." + (interactive) + (setq secondlast-kbd-macro last-kbd-macro) + (end-kbd-macro)) +(substitute-key-definition 'end-kbd-macro 'pre-end-kbd-macro global-map) +(substitute-key-definition 'end-kbd-macro 'pre-end-kbd-macro esc-map) +(substitute-key-definition 'end-kbd-macro 'pre-end-kbd-macro ctl-x-map) + +(defun call-secondlast-kbd-macro (n) + "Like call-last-kbd-macro, but uses the macro defined before that." + (interactive "p") + (let ((last-kbd-macro secondlast-kbd-macro)) + (call-last-kbd-macro n))) + +; The turn-numlock-on/off commands are created automatically by oemacs +; when the NumLock light changes state. +(defun turn-numlock-on () ; numlock is always off when oemacs starts + "Bind keys in keypad area to numeric interpretations." + (interactive) + (setq hb-binding (lookup-key suntool-map "hb")) + (setq gb-binding (lookup-key suntool-map "gb")) + (setq fb-binding (lookup-key suntool-map "fb")) + (setq eb-binding (lookup-key suntool-map "eb")) + (setq db-binding (lookup-key suntool-map "db")) + (define-key suntool-map "hb" "-") + (define-key suntool-map "gb" "+") + (define-key suntool-map "fb" "\C-j") + (define-key suntool-map "eb" ".") + (define-key suntool-map "db" "0")) +(defun turn-numlock-off () + "Rebind keys in keypad area to function-key interpretations." + (interactive) + (define-key suntool-map "hb" hb-binding) + (define-key suntool-map "gb" gb-binding) + (define-key suntool-map "fb" fb-binding) + (define-key suntool-map "eb" eb-binding) + (define-key suntool-map "db" db-binding)) +(defvar hb-binding nil) +(defvar gb-binding nil) +(defvar fb-binding nil) +(defvar eb-binding nil) +(defvar db-binding nil) diff --git a/web/c_cpp/cweb/examples/oemacs.w b/web/c_cpp/cweb/examples/oemacs.w new file mode 100644 index 0000000000..5810b0e7b2 --- /dev/null +++ b/web/c_cpp/cweb/examples/oemacs.w @@ -0,0 +1,896 @@ +\datethis +@i xview_types.w + +@* Introduction. +This program provides an interface between the GNU Emacs editor and the +OpenWindows environment, using the XView toolkit for interactive graphics. +It is based on \.{emacstool}, a SunView interface written by Jeff Peck +of Sun Microsystems in 1986 and adapted by him in 1988 to \.{xvetool}, +an early XView interface. The present code, by Don Knuth, is designed to work +with OpenWindows Version~3 as distributed in 1992, using a Sun Type~4 +keyboard. + +GNU Emacs is a highly portable and versatile program, which does most +of the hard work involved in editing files. Our job is simply twofold: +To get \.{emacs} started in an appropriate window, and to transmit +keyboard and mouse events as sequences of characters that \.{emacs} +can happily consume. These simple tasks do, however, require us to +get a lot of fussy details right. In fact, this program could not have been +written without a good deal more knowledge about XView than can be found +in the manuals. Fortunately Jeff Peck works for Sun, and his +inside knowledge has provided the necessary clues. (All complaints below +about the XView documentation are based on the reference manuals and +programming manuals available from Sun and from O'Reilly \AM\ Associates, in +the summer of 1992. Let us hope that such problems will not persist; +computer programming should be fun, but some of the code below was +written only after a bitter struggle!) + +The command-line arguments to \.{oemacs} are either standard XView +arguments, which specify the font and the size and position of the window, +the icon, the colors, etc.; or they are standard arguments of \.{emacs}, +which typically specify the file to be edited and a position in that file. +If I decide later to make certain things in \.{oemacs} less hardwired, +I will look for options in the X resource database instead of on the +command line. + +An important note about using \.{xmodmap} to change the behavior of +certain keys appears below. It makes \.{emacs} more powerful, unless you +have other good uses for those keys. (See the entry for \.{xmodmap} +in the index.) + +Before using \.{oemacs}, you must compile \.{emacs} with the +\.{sunfns} module (which the \.{Makefile} will do for you if you've +configured it correctly), and you should include the lines +$$\vbox{\halign{\.{#}\hfil\cr +(load-library "sun-mouse")\cr +(load-library "sun-fns")\cr}}$$ +in Emacs's file \.{lisp/site-init.el}. + +Caution: This program was developed and tested with Peck's improved +versions of sun-mouse and sun-fns; these are available from +\.{peck@@sun.com} if not yet part of the GNU distribution. + +@ We follow the traditional structure of XView applications. The |exit| +statement at the end is important, because \.{oemacs} can be invoked by +the \.{system} command in \TEX/ (when the user has typed \.e in response +to an error message); without |exit(0)|, \TEX/ would complain of trouble +executing this program, although we would simply be terminating the program +without returning any particular value. + +@c +@<Include header files@>@; +@# +Frame frame; /* the base frame where we will live */ +@<Global variables@>@; /* additional globals besides |frame| */ +@# +@<Event-handling procedures@>@; +@# +main(argc,argv) + int argc;@+char *argv[]; /* typical \UNIX/ setup */ +{ + @<Special initialization@>; + @<Install the components and controls of |frame|@>; + xv_main_loop(frame); + exit(0); +} + +@ Including the header file \.{<xview/xview.h>} also causes other +basic header files like \.{<xview/frame.h>}, \.{<xview/icon.h>}, etc., +to be loaded. We must call the \CEE/ compiler with the flag +\.{-I\$(OPENWINHOME)/include} so that the \CEE/ preprocessor will +find the OpenWindows header files. + +Some \UNIX/ systems define string functions in \.{<string.h>}, some in +\.{<strings.h>}; the Sun documentation calls for \.{<string.h>}. My +experiments indicate that Sun's compiler and loader work perfectly +well with string functions even when no header files are given, so the +programmer doesn't really have to remember the right name. Similarly, +\.{<stdio.h>} isn't really needed with Sun's \CEE/, unless certain macros +are used. I'll include \.{<string.h>} and \.{<stdio.h>} anyway, in the +spirit of being obedient to the stated rules. + +@<Include...@>= +#include <string.h> +#include <stdio.h> +#include <xview/xview.h> + +@* The icon and frame. +First we attach an icon that will appear if the user closes the \.{oemacs} +window later. + +There are two reasons for doing this first. One is that we might as well +begin with an easy task, in order to get warmed up. The other is that +if we specify the icon {\it after\/} creating the frame, we will unconditionally +override an icon that the user may have specified with the \.{-WI} +option on the command line. (This is one of the little things a +programmer has to turn by trial and error, since the XView documentation +leaves much unsaid.) + +The colors used in the icon will be inherited from the frame, so they will +be reversed if the user asks for reverse video. I~prefer to have the icon +always in black on a yellow background, so I'm making the color explicit. +(I hope this will work on monochrome displays; it works fine on my +grayscale monitor.) + +@<Create a frame with the gnu icon@>= +{@+Server_image icon_image=(Server_image)xv_create(NULL,SERVER_IMAGE,@| + XV_WIDTH,64,XV_HEIGHT,64,SERVER_IMAGE_BITS,icon_bits,NULL); + Server_image mask_image=(Server_image)xv_create(NULL,SERVER_IMAGE,@| + XV_WIDTH,64,XV_HEIGHT,64,SERVER_IMAGE_BITS,mask_bits,NULL); + Cms cms=(Cms)xv_create(NULL,CMS,CMS_SIZE,2,@| + CMS_NAMED_COLORS,"yellow","black",NULL,NULL); + Icon icon=(Icon)xv_create(NULL,ICON,@| + ICON_IMAGE,icon_image,ICON_MASK_IMAGE,mask_image,@| + WIN_CMS,cms,NULL); + frame=xv_create(NULL,FRAME,FRAME_ICON,icon,NULL); +} + +@ @<Include...@>= +#include <xview/cms.h> + +@ If the user hasn't specified a label with the \.{-Wl} option, we turn off +the header line at the top of the frame. That gives us a chance to see +two more lines of the file \.{emacs} is editing. However, we add a +label of our own; it will show up in the virtual window manager display. + +@<Remove the frame header, unless the user has specifically requested it@>= +if (xv_get(frame,XV_LABEL)==NULL) /* no label specified */ + xv_set(frame,FRAME_SHOW_HEADER,FALSE,XV_LABEL,"OEMACS",NULL); + +@ The following icon and mask are derived from the ``what's gnu'' image on the +back cover of the GNU Emacs manual. (This accounts for my black-on-yellow +preference.) + +@<Global...@>= +unsigned short icon_bits[]={@| + 0x0000, 0x0000, 0x0000, 0x1E00, + 0x0000, 0x0000, 0x0000, 0x0900,@| + 0x001E, 0x0000, 0x0000, 0x0880, + 0x0064, 0x0000, 0x0000, 0x0440,@| + 0x0088, 0x0000, 0x0000, 0x0420, + 0x0110, 0x0000, 0x0000, 0x0210,@| + 0x0220, 0x0000, 0x0000, 0x0210, + 0x0420, 0x0FCF, 0x01C0, 0x0108,@| + 0x0840, 0x1030, 0x8620, 0x0088, + 0x1080, 0x00C0, 0x5810, 0x0084,@| + 0x1080, 0x1F00, 0x2008, 0x0044, + 0x2100, 0xE200, 0x1004, 0x0044,@| + 0x4103, 0x0400, 0x0002, 0x0042, + 0x4204, 0x080E, 0x0001, 0x0042,@| + 0x8200, 0x7830, 0x0020, 0x8082, + 0x8203, 0x9040, 0x0018, 0x4102,@| + 0x8204, 0x2080, 0x07C6, 0x3E04, + 0x8108, 0x410C, 0x0021, 0x8004,@| + 0x8080, 0x8210, 0x03D0, 0x6008, + 0x4041, 0x0420, 0x0008, 0x1810,@| + 0x403E, 0x0820, 0x0FFC, 0x0620, + 0x2000, 0x1040, 0x0002, 0x01C0,@| + 0x1000, 0x608C, 0x0FFF, 0x0060, + 0x0801, 0x8110, 0x0080, 0x8118,@| + 0x0406, 0x0220, 0x1FFF, 0x66E0, + 0x0238, 0x044F, 0x0000, 0xD800,@| + 0x01C0, 0x0890, 0x8FFF, 0x4000, + 0x0300, 0x10A6, 0x4041, 0x6000,@| + 0x1C00, 0x2026, 0x4FFF, 0x6000, + 0x60CC, 0x4026, 0x4001, 0x6000,@| + 0x1F33, 0x8010, 0x8FFF, 0x4000, + 0x0012, 0x000F, 0x0040, 0xC000,@| + 0x0022, 0x4000, 0x07FF, 0x4000, + 0x0024, 0x4000, 0x0000, 0x2000,@| + 0x0024, 0x4818, 0x8FFF, 0xE000, + 0x0024, 0x4907, 0x0040, 0x2000,@| + 0x0044, 0x4900, 0x1FFF, 0xE000, + 0x0044, 0x4900, 0x0000, 0x2000,@| + 0x0044, 0x4900, 0x07FF, 0xE000, + 0x0044, 0x4880, 0x0020, 0x2000,@| + 0x0044, 0x4880, 0x07FF, 0xE000, + 0x0044, 0x4840, 0x0000, 0x2000,@| + 0x0044, 0x2A20, 0x07FF, 0xE000, + 0x0044, 0x2410, 0x0020, 0x2000,@| + 0x0042, 0x2448, 0x0FFF, 0xE000, + 0x0042, 0x2948, 0x0000, 0x2000,@| + 0x0041, 0x1144, 0x07FF, 0xA000, + 0x0041, 0x1144, 0x2010, 0x1000,@| + 0x0021, 0x1126, 0x20FA, 0x1000, + 0x0024, 0x8925, 0x2600, 0x1000,@| + 0x0014, 0x8924, 0xA138, 0x7000, + 0x0016, 0x88A4, 0x9090, 0x6000,@| + 0x000A, 0x44A4, 0x4880, 0xA000, + 0x0002, 0x44A2, 0x4401, 0x2000,@| + 0x0003, 0x4492, 0x2001, 0x4000, + 0x0001, 0x2451, 0x3002, 0x8000,@| + 0x0000, 0xA251, 0x1E05, 0x0000, + 0x0000, 0x2248, 0xA1F9, 0x8000,@| + 0x0000, 0x1648, 0x9002, 0x8000, + 0x0000, 0x1A28, 0x4C02, 0x8000,@| + 0x0000, 0x1220, 0x43FC, 0x8000, + 0x0000, 0x0120, 0x2000, 0x8000,@| + 0x0000, 0x0120, 0x2003, 0x0000, + 0x0000, 0x0150, 0x1FFC, 0x0000 +}; +unsigned short mask_bits[]={@| + 0x0000, 0x0000, 0x0000, 0x1E00, + 0x0000, 0x0000, 0x0000, 0x0F00,@| + 0x001E, 0x0000, 0x0000, 0x0F80, + 0x007C, 0x0000, 0x0000, 0x07C0,@| + 0x00F8, 0x0000, 0x0000, 0x07E0, + 0x01F0, 0x0000, 0x0000, 0x03F0,@| + 0x03E0, 0x0000, 0x0000, 0x03F0, + 0x07E0, 0x0FCF, 0x01C0, 0x01F8,@| + 0x0FC0, 0x103F, 0x87F0, 0x00F8, + 0x1F80, 0x00FF, 0xDFF0, 0x00FC,@| + 0x1F80, 0x1FFF, 0xFFF8, 0x007C, + 0x3F00, 0xE3FF, 0xFFFC, 0x007C,@| + 0x7F03, 0x07FF, 0xFFFE, 0x007E, + 0x7E04, 0x0FFF, 0xFFFF, 0x007E,@| + 0xFE00, 0x7FFF, 0xFFFF, 0x80FE, + 0xFE03, 0x9FFF, 0xFFFF, 0xC1FE,@| + 0xFE04, 0x3FFF, 0xFFFF, 0xFFFC, + 0xFF08, 0x7FFF, 0xFFFF, 0xFFFC,@| + 0xFF80, 0xFFFF, 0xFFFF, 0xFFF8, + 0x7FC1, 0xFFFF, 0xFFFF, 0xFFF0,@| + 0x7FFF, 0xFFFF, 0xFFFF, 0xFFE0, + 0x3FFF, 0xFFFF, 0xFFFF, 0xFFC0,@| + 0x1FFF, 0xFFFF, 0xFFFF, 0xFFE0, + 0x0FFF, 0xFFFF, 0xFFFF, 0xFFF8,@| + 0x07FF, 0xFFFF, 0xFFFF, 0xFEE0, + 0x03FF, 0xFFFF, 0xFFFF, 0xF800,@| + 0x01FF, 0xFFFF, 0xFFFF, 0xE000, + 0x03FF, 0xFFFF, 0xFFFF, 0xE000,@| + 0x1FFF, 0xFFFF, 0xFFFF, 0xE000, + 0x7FFF, 0xFFFF, 0xFFFF, 0xE000,@| + 0x1F7F, 0xFFFF, 0xFFFF, 0xC000, + 0x001F, 0xFFFF, 0xFFFF, 0xC000,@| + 0x003F, 0xFFFF, 0xFFFF, 0xC000, + 0x003F, 0xFFFF, 0xFFFF, 0xE000,@| + 0x003F, 0xFFFF, 0xFFFF, 0xE000, + 0x003F, 0xFFFF, 0xFFFF, 0xE000,@| + 0x007F, 0xFFFF, 0xFFFF, 0xE000, + 0x007F, 0xFFFF, 0xFFFF, 0xE000,@| + 0x007F, 0xFFFF, 0xFFFF, 0xE000, + 0x007F, 0xFFFF, 0xFFFF, 0xE000,@| + 0x007F, 0xFFFF, 0xFFFF, 0xE000, + 0x007F, 0xFFFF, 0xFFFF, 0xE000,@| + 0x007F, 0xFFFF, 0xFFFF, 0xE000, + 0x007F, 0xFFFF, 0xFFFF, 0xE000,@| + 0x007F, 0xFFFF, 0xFFFF, 0xE000, + 0x007F, 0xFFFF, 0xFFFF, 0xE000,@| + 0x007F, 0xFFFF, 0xFFFF, 0xE000, + 0x007F, 0xFFFF, 0xFFFF, 0xF000,@| + 0x003F, 0xFFFF, 0xFFFF, 0xF000, + 0x003F, 0xFFFF, 0xFFFF, 0xF000,@| + 0x001F, 0xFFFF, 0xFFFF, 0xF000, + 0x001F, 0xFFFF, 0xFFFF, 0xE000,@| + 0x000B, 0xFFFF, 0xFFFF, 0xE000, + 0x0003, 0xFFFF, 0xFFFF, 0xE000,@| + 0x0003, 0xFFFF, 0xFFFF, 0xC000, + 0x0001, 0xFFFF, 0xFFFF, 0x8000,@| + 0x0000, 0xBFF1, 0xFFFF, 0x0000, + 0x0000, 0x3FF8, 0xFFFF, 0x8000,@| + 0x0000, 0x1FF8, 0xFFFF, 0x8000, + 0x0000, 0x1FF8, 0x7FFF, 0x8000,@| + 0x0000, 0x13E0, 0x7FFF, 0x8000, + 0x0000, 0x01E0, 0x3FFF, 0x8000,@| + 0x0000, 0x01E0, 0x3FFF, 0x0000, + 0x0000, 0x0150, 0x1FFC, 0x0000 +}; + +@* Emulating a terminal. +We will run \.{emacs} in a ``tty subwindow,'' named after the teletype +terminals of ancient yore. + +The |argv| array will be a command line that invokes +\.{emacs} with all arguments not removed by |xv_init|, i.e., all arguments +that remain after generic XView arguments have been removed. + +We have to say |WIN_IS_CLIENT_PANE|, otherwise fonts specified on the +command line will be ignored. (This cryptic instruction is mentioned briefly +in the XView reference manual, but not in the XView programming manual; +I~would never have discovered it without Jeff Peck's help.) + +We also have to set |TTY_QUIT_ON_CHILD_DEATH| to |TRUE|. Otherwise when +\.{emacs} exits (via control-X, control-C) there still will be a terminal +window (into which we can type but not access the shell). + +Before starting \.{emacs} we set the environment variable +\.{TERM} equal to \.{sun}. +This will tell \.{emacs} to initialize itself with the +programs in its source file \.{lisp/term/sun.el}, where special +adaptations for Sun-style terminals have been recorded. + +@<Put a tty subwindow into the frame@>= +argv[0]="emacs"; +putenv("TERM=sun"); +tty=(Tty)xv_create(frame,TTY,WIN_IS_CLIENT_PANE,@| + TTY_QUIT_ON_CHILD_DEATH,TRUE,@| + TTY_ARGV,argv,NULL); + +@ @<Global...@>= +Tty tty; + +@ @<Include...@>= +#include <xview/tty.h> + +@ The XView manual doesn't tell us that tty subwindows have a view part +and a pseudo-terminal part. (The manual does mention briefly that text +subwindows have views---at the beginning of section 6.3, and indirectly in +one of the examples in the chapter on color.) + +The view window of an emulated tty will receive keyboard and +mouse events. We need to know its ``handle,'' because we want to +modify XView's default interpretation of many of those events. + +For example, we want to grab the keyboard focus (i.e., to begin receiving +keyboard input) as soon as the user moves the mouse into the \.{emacs} +window. The normal OpenLook default requires a user to click in the window +first, but that is inefficient in a production book-writing environment. +Us \.{emacs} users would rather type than point. + +A secret incantation makes the view window accessible. Dear reader, +would you have guessed how to do this, from reading the manuals only, +without looking at Jeff Peck's code? Be honest now. + +We don't have to enable the notification of other kinds of events; +tty subwindows already do that. + +@<Prepare to be notified when the mouse enters the window@>= +window=(Xv_window)xv_get(tty,OPENWIN_NTH_VIEW,0); +xv_set(window,WIN_CONSUME_EVENT,LOC_WINENTER,NULL); + +@ @<Global...@>= +Xv_window window; /* the view window of |tty| */ + +@ If the user has specified reverse video with the \.{-rv} option, +we will reverse black and white in the mouse cursor. This will make it +more visible against a black background. + +Changing the cursor is another undocumented reason why we need to know +about the tty's view window; nothing changes if we try to attach +the new cursor to |frame| or to |tty| instead of to |window|. + +@<Change the cursor, to avoid black-on-black@>= +if (rv) {Xv_singlecolor white,black; + Xv_cursor cursor; + white.red=white.green=white.blue=255; + black.red=black.green=black.blue=0; + cursor=(Xv_cursor)xv_create(NULL,CURSOR,@| + CURSOR_SRC_CHAR,OLC_BASIC_PTR,CURSOR_MASK_CHAR,OLC_BASIC_MASK_PTR,@| + CURSOR_FOREGROUND_COLOR,&white,CURSOR_BACKGROUND_COLOR,&black,NULL); + xv_set(window,WIN_CURSOR,cursor,NULL); +} + +@ @<Include...@>= +#include <xview/cursor.h> /* we're using the cursor package */ + +@ What is the variable |rv| that was tested in the code above? Good question. +We have to scan for \.{-rv} before |xv_init| looks at the command arguments. + +@<Scan the command line, setting |rv| nonzero if \.{-rv} is present@>= +rv=0; +{@+int k=argc; + while (--k>0) if (strcmp(argv[k],"-rv")==0 || + strcmp(argv[k],"-reverse")==0) rv=1; +} + +@ @<Global...@>= +int rv; + +@ We need to know the height and width of characters in the font, in order +to convert mouse positions into coordinates that \.{emacs} will like. +If the user has specified a font explicitly, the font will presumably have +a fixed width for all characters; we can learn the relevant dimensions +by calling |xv_get|. But if the user has not specified a font, the +situation is trickier; |xv_get| will report the width of the default +{\it non\/}-fixed-width font, and this will probably differ from the width of +the actual fixed-width font the tty subwindow will choose. + +Curiously, if we call |xv_find(NULL,FONT,FONT_FAMILY, +FONT_FAMILY_DEFAULT_FIXEDWIDTH,NULL)| {\it before\/} calling |xv_init|, +without even doing anything with the result returned by |xv_find|, +|xv_init| will not install any fonts specified on the command line. +The trick we used for icons---installing the default gnu icon on the first +call to |xv_create|---will therefore fail. + +Thus, we have to work around two distinct bugs in XView. The solution +is to discover the effects of the user's command line after |xv_init| +has acted and the frame has been set up; we can determine by brute force what +font will go into the tty window. The program below works correctly +even if the command line specifies \.{-scale} \.{large}, say, instead of +specifying a new font explicitly by something like \.{-font} \.{9x15}. + +While we are cataloguing peculiarities of XView, we might as well mention +that the default character dimensions of the default font (Lucida) are +reported to be $8\times13$ in the 12-point (medium) size, $8\times15$ in the +14-point (large) size, and $12\times20$ in the 19-point (extralarge) size. +The actual character dimensions in the window come, however, from the +default fixed-width font (Lucida typewriter), and they are $7\times13$, +$9\times15$, and $11\times23$ in the same three sizes. No logical progression +is evident in either the variable-width or the fixed-width dimensions, +although Lucida is a ``scalable font family.'' + +@<Compute the height and width of characters in the font@>= +{ + Xv_font font=(Xv_font)xv_get(frame,XV_FONT); + Xv_font dfont=(Xv_font)xv_find(NULL,FONT,FONT_FAMILY, + FONT_FAMILY_DEFAULT,NULL); + if (strcmp((char*)xv_get(font,FONT_NAME), + (char*)xv_get(dfont,FONT_NAME))==0) { + /* the user didn't specify a new font by name */ + dfont=(Xv_font)xv_find(NULL,FONT,FONT_FAMILY, + FONT_FAMILY_DEFAULT_FIXEDWIDTH,NULL); + /* this one will be used by the tty window */ + } else dfont=font; + char_width=(int)xv_get(dfont,FONT_DEFAULT_CHAR_WIDTH); + char_height=(int)xv_get(dfont,FONT_DEFAULT_CHAR_HEIGHT); +} + +@ @<Global...@>= +int char_width, char_height; /* character dimensions in the font */ + +@ @<Include...@>= +#include <xview/font.h> /* header for the font package */ + +@ OK, we've figured out how to install a tty subwindow with the right +event mask and the right cursor, and how to calculate the sizes of the +characters it will contain. All that remains is for us to do these +operations in the proper order, and to specify a filter routine that +will monitor and edit all communications between the keyboard and the +Emacs processor in the window. + +The new ingredient is the filter specification. We tell the XView +notifier to call |filter| when a window event occurs, instead of +letting it call the tty subroutine event handler directly. The parameter +|NOTIFY_SAFE| implies that the window's event handler is +part of XView, not an alien routine. + +@<Install the components...@>= +@<Scan the command line, setting |rv| nonzero if \.{-rv} is present@>; +xv_init(XV_INIT_ARGC_PTR_ARGV,&argc,argv,NULL); + /* start XViewing; strip and remember the OpenWin arguments */ +@<Create a frame with the gnu icon@>; +@<Remove the frame header...@>; +@<Put a tty subwindow into the frame@>; +@<Prepare to be notified when the mouse enters the window@>; +@<Change the cursor, to avoid black-on-black@>; +@<Compute the height and width of characters in the font@>; +notify_interpose_event_func(window,filter,NOTIFY_SAFE); + +@* Keyboard events. +The job of an interposed filter function is to look at an event and +either process it ourselves or pass it through (possibly modified) +to its normal recipient. In the first case we return the code +value |NOTIFY_DONE|, since we aren't reporting any errors; +in the second case we call the normal event handler and return the value +it returns to us. + +An XView event is a data structure that has been partially +interpreted by the XView routines, which add semantic sugar to +the complex union type of X~Window events. We need not look +too far inside an event structure to do the things that concern us. + +And what is our job? We are looking for three different kinds of events: + +\smallskip +\itemitem{(1)} When the mouse enters the window, +we want to grab the keyboard focus. + +\itemitem{(2)} When a mouse button goes up or down, and we have the keyboard +focus, we want to send a coded sequence of characters to \.{emacs}. + +\itemitem{(3)} When a key goes down, and we have the keyboard focus, we +want to send an appropriate sequence of characters to \.{emacs}. + +\itemitem{(4)} When the status of the Num Lock indicator light changes, we +will send emacs the command {\tt turn-numlock-on} or {\tt turn-numlock-off}, +for reasons described below. + +\smallskip\noindent Other events, like instructions to repaint or +resize the window, will be passed through without change to the tty window. + +@<Event-handling...@>= +Notify_value filter(window,event,arg,type) + Xv_window window; /* the ``client'' on whom we interposed */ + Event *event; /* the data we are filtering */ + Notify_arg arg; /* optional communication parameter between clients */ + Notify_event_type type; /* |NOTIFY_SAFE| or |NOTIFY_IMMEDIATE| */ +{@+register int id=event_id(event); +#ifdef DEBUG + printf("event %d%s, action %d, shift %x, mouse(%d,%d)\n", + event_id(event),event_is_up(event)?"UP":"DOWN",event->action, + event_shiftmask(event),event_x(event),event_y(event)); +#endif + @<Update the Num Lock status@>; + if (id==LOC_WINENTER) @<Grab the keyboard focus and return@>; + if (event_is_button(event)) @<Translate a mouse event and return@>; + if (event_is_up(event)) return NOTIFY_DONE; /* key has been released */ + @<Translate a function key into a special escape sequence@>; + @<Sanitize a keypress event so that unusual semantic actions are removed@>; + return notify_next_event_func(window,event,arg,type); /* pass it through */ +} + +@ It's easy to take charge of the keyboard and mouse, as soon as the mouse +enters our territory. + +@<Grab...@>= +{ + win_set_kbd_focus(window,xv_get(window,XV_XID)); + return NOTIFY_DONE; +} + +@ If the event is not related to mouse buttons or special function keys, +we want to pass it on unchanged, unless its |event_id| is less than 128. +In that case, it represents a character code, and we want to nuke any +semantic ``keyboard acceleration'' actions it might have been assigned +by OpenWindows. + +We also make the Meta key add 128 here. An undocumented macro +called |event_set_id|, found in \.{<xview/win\_input.h>}, +clears the |action| code as desired. + +@<Sanitize...@>= +if (id<128) + if (event_meta_is_down(event)) event_set_id(event,id+128); + else event_set_action(event,ACTION_NULL_EVENT); + +@* Function keys. +The Sun Type 4 keyboard has lots of keys, and these can be bound to lots +of useful functions when we are \.{emacs}ing to the max. Besides the +letters and punctuation marks of a normal typewriter, there are ten +``left'' function keys, L1--L10; twelve ``top'' function keys, F1--F12; +fifteen ``right'' function keys, R1--R15; and eight additional keys +labeled Help, Alt, AltGraph, Ins, Del, Enter, $+$, $-$, which we will +pretend have been labeled B1--B8. + +The L5 key, also known as Front, is controlled by the Open Look +window manager; it makes a window rise above any that might overlap it, +or shoves the window underneath in case it already was on top. + +The L7 key, aka Open, is likewise preempted by the +window manager. It closes a frame to an icon, or opens an icon. + +The window manager traps the R2 key and calls it the ``language'' key; +but I have no use for that feature. So I have remapped R2 to the comparatively +useless character $3\over4$, and I will translate it back to R2 below. (The +\.{xmodmap} program allows users to reassign the interpretation of key codes.) +I could have recovered the L5 and L7 keys in the same way, but I like +their functions as they stand. (L5 and L7 still come through if a +Shift, Control, and/or Meta key is down.) + +I can never remember the difference between Delete and BackSpace, so I +have mapped them both into control-?, ASCII code 127. + +There are two Shift keys, one at the left and one at the right, which +are best kept indistinguishable from each other. Similarly, the left +and right Meta keys (`\.{\char27}') are essentially identical. There's a +Control key too. These three types of modifier keys generate keypress +events, but we ignore them; the only thing that matters to us is whether +they are currently up or down, when other keys are pressed. + +\font\ttit=cmitt10 +There also are three special keys that do not generate events, so we +never see them. The CapsLock key toggles the Caps Lock light and +changes lowercase letters to uppercase when the light is on. The +NumLock key toggles the Num Lock light and changes the interpretation +of R4--R15 and B4--B8 when that light is on. The Compose key turns the +Compose light on until you type two characters, then it produces a +special symbol if those two characters match a pattern. For example, +when Compose is followed by either \.{a"} or \.{"a} you get the 8-bit +ISO code for {\tt \"a}; when Compose is followed by either \.{th} or +\.{ht} you get the Icelandic thorn; when it's followed by \.{??} you get {\tt +?`}; \.{c/} and \.{L-} give \rlap{\./}\.c and {\ttit\char`\$} +and so on. (A list of all composition patterns +appears in \.{<X11/Suncompose.h>}, but not in any of the manuals +I've seen.) The light goes off after two characters have been +typed, or if your first character is not composable, or if +you hit Compose again prematurely. If no proper two-character pattern +was specified, only ``up'' events (key releases) are transmitted, and +nothing will pass through to \.{emacs}. + +One other feature is worth noting: The key between F12 and Delete +produces a backslash `\.\\', or a vertical line `\.{\char125}' when +shifted. Everybody knows that, but even more is true. If you hold the +AltGraph key down and press this key, it turns out that you get the +broken-bar character `{\tt\hbox to1em{\hss\vbox{\hrule width 1pt height +3pt\vskip1.5pt\hrule height2pt depth1pt}\hss}}'. This is the only key that the +engineers chose to endow with three different graphic symbols. + +A few other anomalies occur; for example, AltGraph-R1 gives ASCII null, +while AltGraph does not affect R4. But I didn't discover any additional +combinations that are either useful or harmful. + +Once upon a time the Caps Lock key might have affected the |event_shiftmask| +field of an event, but it has no effect now. The shiftmask is always an +even number, contrary to the implications of \.{<xview/win\_input.h>}. + +@ The function keys will be translated into a four-character code. +First comes control-X; then an asterisk; then a letter, \.{a}--\.{o} +for function numbers 1--15, respectively; and then another letter, +identifying left, right, top, or bottom. The final letter is +ordinarily `\.l', `\.r', `\.t', or `\.b', respectively. But it is `\.L', `\.R', +`\.T', or `\.B' if a Shift key is down. Furthermore the Control key +subtracts 64 from the ASCII code, so you get `\.,', `\.2', `\.4', or +`\."' with Control and no Shift. With both Control and Shift you get +\.{\\C-L}, \.{\\C-R}, \.{\\C-T}, \.{\\C-B}. A Meta key adds another 128 +to the code. Thus, each function key leads to eight possibilities. + +For example, if F4 is pressed when Control and Shift are down, but not +Meta, the four-letter code is \.{\\C-X*d\\C-T}. The user could type +that sequence of four characters and get the same effect. + +Shifted function keys sometimes have a nice mnemonic significance. +For example, key R14, also labeled PgDn, is usually bound to the Emacs +operation `\.{scroll-up}', which moves the window down [sic] by one +screenful; we can then bind Shift-R14 to \.{forward-page}, which advances +down to the next page boundary. In \.{cweb-mode}, the next page boundary +is the next \.{@@*}, beginning a major part of the program. Similarly, +it's convenient to bind B7, the keypad `\.+' key, to \.{forward-paragraph}. +Then in \.{cweb-mode}, Shift-B7 goes to the next module (the next +\.{@@\ } or \.{@@*}). + +A Composed character will be preceded by \.{\\C-Q}, the Emacs `quote' +character, to distinguish it from a character that was generated with the +Meta key down. This also applies to the broken-bar character, which +will incidentally be preceded by AltGraph, which is B3; you'll probably +want to bind B3 to a no-op if broken bars are important to you. + +@ This program assumes that several key codes have been rebound from +their normal values. Namely, the commands +$$\vbox{\halign{\.{#}\hfil\cr +keysym R2 = threequarters\cr +keysym KP\_Subtract = onehalf\cr +keysym KP\_Add = onequarter\cr +keysym KP\_Enter = threesuperior\cr +keysym KP\_Decimal = twosuperior\cr +keysym KP\_0 = onesuperior\cr}}$$ +should be executed by \.{xmodmap}, preferably in the user's \.{.xinitrc} file. +This makes the keys act as $3\over4$, $1\over2$, $1\over4$, $^3$, $^2$, and +$^1$, respectively. The corresponding 8-bit codes are respectively +190, 189, 188, 179, 178, 185. (By the way, can anybody explain why the ISO +LATIN-1 code has $^0$, $^2$, and $^3$ in the positions of meta-0, meta-2, +and meta-3, while $^1$ is in the position of meta-9?) +@.xmodmap@> + +We haven't actually bound the keys this way to use them in editing. +We did it to provide linguistically unimportant codes that OpenWindows +wouldn't mess up; its normal conventions make those valuable keys +unusable for editing, except as duplicates for other existing keys. + +We send \.{turn-numlock-on/off} commands so that \.{emacs} can keep in +synch with the keyboard state. Namely, it will rebind the function +keys B4--B8 to their numeric-keypad equivalents while the Num Lock light is on. + +On the other hand, our remapping does make the Num Lock +feature useless in other (non-Emacs) applications. If you don't +rebind the keys as stated, you lose the functionality of R2 and B4--B8, +but \.{oemacs} will still work. + +The Help key is another special case. We don't want to remap it, +because it gives useful help information with other OpenWindows +applications. If Help is pressed without the shift or control key, +the |event_id| is zero and the |event_action| is |ACTION_HELP|. +Control-Help is similar, but with |ACTION_TEXT_HELP|. Shift-Help is +more complicated; it invokes `helpopen: starting new Viewer', after +generating an event that has |event_action=WIN_VISIBILITY_NOTIFY|. The +program below considers the Help key B1 to be characterized by any +event with |event_id=0| and either |event_action!=0| or +|event_shiftmask!=CTRLMASK|. + +@<Translate a function key into a special escape sequence@>= +{@+register int bank='b'; /* |'l'|, |'r'|, |'t'|, or |'b'| */ + register int n; /* function key serial number, |1<=n<=15| */ + if (id>=KEY_LEFT(1)) @<Translate an ordinary function key@>@; + else if (id>=256) @<Look for Alt or AltGraph@>@; + else if (id>=128) + @<Translate a special function key or composed character@>@; + else if (id>0 || + (event_action(event)==0 && event_shiftmask(event)==CTRLMASK)) + goto non_function; + else n=1; /* Help key */ +emit_function_key:@<Emit the code for a function key and |return|@>; +non_function:; +} + +@ I'm assuming here that the event id codes occur in the order left, right, +top, bottom, and that no higher event codes exist. + +@<Translate an ordinary function key@>= +{ + if (id<KEY_RIGHT(1)) { /* on the left bank */ + bank='l';@+n=id-KEY_LEFT(0); + } else if (id<KEY_TOP(1)) { /* on the right bank */ + bank='r';@+n=id-KEY_RIGHT(0); + } else if (id<KEY_BOTTOM(1)) { + bank='t';@+n=id-KEY_TOP(0); + } else n=id-KEY_BOTTOM(0); + goto emit_function_key; +} + +@ The event codes examined here appear in \.{<xview/win\_event.h>} +but not in the XView reference manual. + +@<Look for Alt or AltGraph@>= +if (id==SHIFT_ALT) { + n=2;@+goto emit_function_key; +} else if (id==SHIFT_ALTG) { + n=3;@+goto emit_function_key; +} else goto non_function; + +@ The |ttysw_input| routine sends text to a tty's view window. +The second parameter is a string, not necessarily terminated by +|'\0'| or anything else; the third parameter is the string length. + +@<Emit the code for a function key and |return|@>= +{ + if (event_shift_is_down(event)) bank-=32; + if (event_ctrl_is_down(event)) bank-=64; + if (event_meta_is_down(event)) bank+=128; + buf[2]=n+'a'-1; + buf[3]=bank; + ttysw_input(window,buf,4); + return NOTIFY_DONE; +} + +@ @<Global...@>= +char buf[]="\030*??\021"; /* |030| and |021| give control-X, control-Q */ + +@ @<Translate a special function key or composed character@>= +switch (id) { +case 190: bank='r';@+n=2;@+goto emit_function_key; +case 189: n=8;@+goto emit_function_key; +case 188: n=7;@+goto emit_function_key; +case 179: n=6;@+goto emit_function_key; +case 178: n=5;@+goto emit_function_key; +case 185: n=4;@+goto emit_function_key; +default: buf[5]=id; /* composed character or broken-bar */ + ttysw_input(window,buf+4,2); + return NOTIFY_DONE; +} + +@* The NumLock key. +The global variable |num_lock_state| will be 0 if the Num Lock indicator +light is off, 1 if it is on. Whenever an event occurs, we check to see +if |num_lock_state| should change; if so, we change it and send an +appropriate command to \.{emacs}. + +To read the state of the keyboard LED indicator lights, we need an I/O +control command called the |KIOCGLED| ioctl, described on the +man page for \.{kb(4m)}. + +@<Global...@>= +int num_lock_state; +char turnon[]="\370turn-numlock-on\r", turnoff[]="\370turn-numlock-off\r"; +int keyboard; /* file descriptor of \.{/dev/kbd} */ + +@ @<Include...@>= +#include <sys/file.h> /* definition of |O_RDWR| for |open| */ +#include <sundev/kbio.h> /* definition of |KIOCGLED| for |ioctl| */ + +@ @d LED_NUM_LOCK 0x1 /* the official definition is in \.{<server/sunevq.h>}, + but that header file includes huge gobs of other stuff */ + +@<Update the Num Lock status@>= +{@+char leds; /* binary encoding of LED lights */ + ioctl(keyboard,KIOCGLED,&leds); + if ((leds&LED_NUM_LOCK)!=num_lock_state) { + num_lock_state=leds&LED_NUM_LOCK; + if (num_lock_state) ttysw_input(window,turnon,17); + else ttysw_input(window,turnoff,18); + } +} + +@ Any ordinary user can apparently open the keyboard as a file. I would +have tried read-only access if read-write had failed; but read-write access +gives a sense of power even though I won't be writing anything. + +@<Special initialization@>= +keyboard=open("/dev/kbd",O_RDWR); +if (keyboard<0) { + fprintf(stderr,"%s: Can't open /dev/kbd!\n",argv[0]); + exit(1); +} + +@* Mouse events. +When a mouse button is pressed or released, we send \.{emacs} the +codes control-X and ASCII null, followed by a parenthesized list +of four numbers and carriage-return. +For example, as I was typing this paragraph, I +clicked the left mouse button on the screen just for fun; \.{emacs} +received the characters +$$\.{\\030\\0(1 18 28 9999)\\r\\030\\0(129 18 28 141)\\r}$$ +as a result (according to `\.{view-lossage}'). I would have received +the same response if I had typed these characters myself, instead +of clicking the mouse. + +The first of the four numbers identifies the mouse button itself +as the code number 1, 2, or 4 (for left, middle, right), plus 8 if +a Shift key is down, plus 16 if the Control key is down, plus 32 +if a Meta key is down, plus 128 if the mouse key is being released +instead of pressed. + +The second number is the row number in the frame, the top row being +considered row~0. + +The third number is the column number in the frame, the left column being +considered column~0. + +The fourth number is the elapsed time between this mouse event and the +previous one, in milliseconds. If the elapsed time was 10 seconds or +more, 9999 is substituted. + +Macros inside \.{emacs} can use the second and third numbers to +position the cursor. The fourth number can be used to determine if the +user is ``double clicking'' or using ``chords.'' Examples of such +macros appear in the Emacs source file \.{lisp/sun-mouse.el}. + +Incidentally, the ASCII null character in mouse sequence makes us happy that +the string parameter to |ttysw_input| is not null-terminated. + +@<Translate a mouse event...@>= +{@+register int button_code,elapsed_time; + button_code=(id==MS_LEFT? 1: id==MS_MIDDLE? 2: 4); + if (event_shift_is_down(event)) button_code += 8; + if (event_ctrl_is_down(event)) button_code += 16; + if (event_meta_is_down(event)) button_code += 32; + if (event_is_up(event)) button_code += 128; + @<Compute the time elapsed since the previous mouse event@>; + sprintf(mouse_buf+2,"(%d %d %d %d)\r",button_code,@| + event_x(event)/char_width, event_y(event)/char_height,@| + elapsed_time); + ttysw_input(window,mouse_buf,12+strlen(mouse_buf+12)); /* length is at least 12 */ + return NOTIFY_DONE; +} + +@ @<Global...@>= +char mouse_buf[24]="\030"; + +@ XView's event structure includes |event_time(event)|, which has +type |struct timeval|; this data type is declared in \.{<sys/time.h>}, +which is one of the files included automatically as a result of +including \.{<xview/xview.h>}. +A |timeval| structure consists of two |long| integers, |tv_sec| and |tv_usec|, +denoting clock time in seconds and microseconds, respectively. + +@<Compute the time...@>= +{@+struct timeval now; /* current time */ + long delta_sec, delta_usec; /* difference between current and + previous time */ + now=event_time(event); + delta_sec=now.tv_sec-prev_mouse_time.tv_sec; + delta_usec=now.tv_usec-prev_mouse_time.tv_usec; + if (delta_usec<0) delta_usec+=1000000,delta_sec--; + if (delta_sec>=10) elapsed_time=9999; /* infinity (or close enough) */ + else elapsed_time=(delta_sec*1000)+(delta_usec/1000); + prev_mouse_time=now; +} + +@ @<Global...@>= +struct timeval prev_mouse_time; + +@* Remaining problems. There's a terribly unfortunate bug in the +present implementation of XView, causing characters of tty subwindows +to be badly painted at crucial times; the rightmost column of pixels +in a character is often clobbered. If I could figure out how to +generate repaint events for the tty subwindow, I might build a mechanism +into \.{oemacs} that does this after the keyboard has been idle for 10 +seconds, say. This would blink the screen; maybe I'll get used to that, +or maybe I'll prefer to refresh the window manually by binding +\.{redraw-display} to the L2 and R1 keys. In any case a lot of screen +refreshing is necessary at the moment, alas. + +(Note added later: I doubt if I'll get used to blinking, and the present +method of manual refresh is tolerable so I won't pursue the 10-second +timer idea. I have meanwhile noticed a procedure |wmgr_refreshwindow(window)| +mentioned in \.{<xview/wmgr.h>}; it will presumably refresh any +given window. + +Another bug, much less serious, occurs when the window is resized. +If the window gets smaller, \.{emacs} isn't told to correct its +assumptions; so it puts information in strange places or offscreen. +(Internally, emacs uses the \.{TIOCGWINSZ} or \.{TIOCSWINSZ} ioctl, +described in the man page for \.{termio}.) +You can work around this by first making the window very small, then +making it large. + +@* Index. + + diff --git a/web/c_cpp/cweb/examples/treeprint.w b/web/c_cpp/cweb/examples/treeprint.w new file mode 100644 index 0000000000..d428e26e5e --- /dev/null +++ b/web/c_cpp/cweb/examples/treeprint.w @@ -0,0 +1,248 @@ +\def\covernote{Copyright 1987 Norman Ramsey -- Princeton University} + +\def\vbar{\.{|}} +@*Directory Trees. +Our object is to print out a directory hierarchy in some pleasant way. +The program takes output from {\tt find * -type d -print \vbar\ sort} +@^system dependencies@> +and produces a nicer-looking listing. +More precisely, our input, which is the output of {\tt find} followed +by {\tt sort}, is a list of fully qualified directory names (parent +and child separated by slashes |'/'|); everything has already been +sorted nicely into lexicographic order. + +The {\tt treeprint} routine takes one option, |"-p"|, which tells it +to use the printer's line-drawing set, rather than the terminal's. + +@c +@<Global definitions@>@; +@<Global include files@>@; +@<Global declarations@>@; + +@# +main(argc, argv) + int argc; + char **argv; +{ +@<|main| variable declarations@>; +@<Search for options and set special characters on |"-p"|@>; +@<Read output from find and enter into tree@>; +@<Write tree on standard output@>@; +exit(0); +} + +@ +We make all the siblings of a directory a linked list off of its left child, +and the offspring a linked list off the right side. +Data are just directory names. +@d sibling left +@d child right + +@<Global decl...@>= +typedef struct tnode { + struct tnode *left, *right; + char *data; +} TNODE; +@ @<|main| variable...@>= +struct tnode *root=NULL; + + + +@*Input. +Reading the tree is simple---we read one line at a time, and call on the +recursive |add_tree| procedure. + +@c +read_tree (fp, rootptr) + FILE *fp; + struct tnode **rootptr; +{ + char buf[255], *p; + + while ((fgets(buf, 255, fp))!=NULL) { + @<If |buf| contains a newline, make it end there@>; + add_tree(rootptr, buf); + } + } +@ @<Global include...@>= +#include <stdio.h> + +@ Depending what system you're on, you may or may not get a newline in |buf|. +@<If |buf| contains a newline...@>= + p=buf; while (*p!='\0'&&*p!='\n') p++; +@^system dependencies@> + *p='\0'; + +@ +To add a string, we split off the first part of the name and insert it into +the sibling list. We then do the rest of the string as a child of the new node. + +@c +add_tree(rootptr, p) + struct tnode **rootptr; + char *p; +{ + char *s; + int slashed; + + if (*p=='\0') return; + +@<Break up the string so |p| is the first word, + |s| points at null-begun remainder, + and |slashed| tells whether |*s=='/'| on entry@>; + + if (*rootptr==NULL) { +@<Allocate new node to hold string of size |strlen(p)|@>; + strcpy((*rootptr)->data,p); + } + if (strcmp((*rootptr)->data,p)==0) { + if (slashed) ++s; + add_tree(&((*rootptr)->child),s); + } + else { + if (slashed) *s='/'; + add_tree(&((*rootptr)->sibling),p); + } + } + +@ We perform some nonsense to cut off the string |p| so that |p| just +holds the first word of a multiword name. Variable |s| points at what +was either the end of |p| or a slash delimiting names. In either case +|*s| is made |'\0'|. Later, depending on whether we want to pass the +whole string or the last piece, we will restore the slash or advance +|s| one character to the right. + +@<Break up...@>= + for (s=p;*s!='\0'&&*s!='/';) s++; + if (*s=='/') { + slashed=1; + *s='\0'; + } else slashed=0; + +@ Node allocation is perfectly standard \dots +@<Allocate new node...@>= + *rootptr=(struct tnode *) malloc (sizeof(struct tnode)); + (*rootptr)->left = (*rootptr)->right = NULL; + (*rootptr)->data = malloc (strlen(p)+1); + +@ +@<Global decl...@>= char *malloc(); + +@ In this simple implementation, we just read from standard input. +@<Read...@>= read_tree(stdin,&root); + +@*Output. +We begin by defining some lines, tees, and corners. +The |s| stands for screen and the |p| for printer. +You will have to change this for your line-drawing set. +@^system dependencies@> + +@<Global definitions@>= +#define svert '|' +#define shoriz '-' +#define scross '+' +#define scorner '\\' /* lower left corner */ + +#define pvert '|' +#define phoriz '-' +#define pcross '+' +#define pcorner '\\' /* lower left corner */ + +@ The default is to use the terminal's line drawing set. +@<Global declarations@>= +char vert=svert; +char horiz=shoriz; +char cross=scross; +char corner=scorner; + +@ With option |"-p"| use the printer character set. +@<Search for options...@>= +while (--argc>0) { + if (**++argv=='-') { + switch (*++(*argv)) { + case 'p': + vert=pvert; + horiz=phoriz; + cross=pcross; + corner=pcorner; + break; + default: + fprintf(stderr,"treeprint: bad option -%c\n",**argv); + break; + } + } +} + +@ We play games with a character stack to figure out when to put in vertical +bars. +A vertical bar connects every sibling with its successor, but the last sibling +in a list is followed by blanks, not by vertical bars. The state of +bar-ness or space-ness for each preceding sibling is recorded in the +|indent_string| variable, one character (bar or blank) per sibling. + +@<Global decl...@>= +char indent_string[100]=""; + +@ Children get printed +before siblings. +We don't bother trying to bring children up to the same line as their parents, +because the \UNIX/ filenames are so long. + +We define a predicate telling us when a sibling is the last in a series. +@d is_last(S) (S->sibling==NULL) + +@c +print_node(fp, indent_string, node) + FILE *fp; + char *indent_string; + struct tnode *node; +{ + char string[255]; + int i; + char *p, *is; + + if (node==NULL) { + } + else { + *string='\0'; + for (i=strlen(indent_string); i>0; i--) + strcat(string,@, " | "); + strcat(string,@t\ \ @> " +--"); +@<Replace chars in |string| with chars from + line-drawing set and from |indent_string|@>; + fprintf(fp,"%s%s\n",string,node->data); + +@# + /* Add vertical bar or space for this sibling (claim |*is=='\0'|) */ + *is++ = (is_last(node) ? ' ' : vert); + *is='\0'; + + print_node(fp, indent_string, node->child); /* extended |indent_string| */ + *--is='\0'; + print_node(fp, indent_string, node->sibling); /* original |indent_string| */ + } + +} +@ For simplicity, we originally wrote connecting lines with |'|'|, |'+'|, and +|'-'|. +Now we replace those characters with appropriate characters from the +line-drawing set. +We take the early vertical bars and replace them with characters from +|indent_string|, and we replace the other characters appropriately. +We are sure to put a |corner|, not a |cross|, on the last sibling in +a group. +@<Replace chars...@>= + is=indent_string; + for (p=string; *p!='\0'; p++) switch(*p) { + case '|': *p=*is++; break; + case '+': *p=(is_last(node) ? corner : cross); break; + case '-': *p=horiz; break; + default: break; + } + + +@ For this simple implementation, we just write on standard output. + +@<Write...@>= print_node(stdout, indent_string, root); + +@*Index. diff --git a/web/c_cpp/cweb/examples/wc-dos.ch b/web/c_cpp/cweb/examples/wc-dos.ch new file mode 100644 index 0000000000..b3da3fd3bb --- /dev/null +++ b/web/c_cpp/cweb/examples/wc-dos.ch @@ -0,0 +1,8 @@ +% from j.fitzsimons@genie.geis.com who compiled with Top Speed C +% April 1995 + +@x +@d buf_size BUFSIZ /* \.{stdio.h}'s |BUFSIZ| is chosen for efficiency*/ +@y +@d buf_size 2048 /* \.{stdio.h}'s |BUFSIZ| breaks DOS */ +@z diff --git a/web/c_cpp/cweb/examples/wc.w b/web/c_cpp/cweb/examples/wc.w new file mode 100644 index 0000000000..522cfc6573 --- /dev/null +++ b/web/c_cpp/cweb/examples/wc.w @@ -0,0 +1,254 @@ +% wc: An example of CWEB by Silvio Levy and Donald E. Knuth + +\nocon % omit table of contents +\datethis % print date on listing +\def\SPARC{SPARC\-\kern.1em station} + +@* An example of {\tt CWEB}. This example, based on a program by +Klaus Guntermann and Joachim Schrod [{\sl TUGboat\/ \bf7} (1986), +134--137] presents the ``word count'' program from \UNIX/, rewritten in +\.{CWEB} to demonstrate literate programming in \CEE/. The level of +detail in this document is intentionally high, for didactic purposes; +many of the things spelled out here don't need to be explained in +other programs. + +The purpose of \.{wc} is to count lines, words, and/or characters in a +list of files. The number of lines in a file is the number of newline +characters it contains. The number of characters is the file length in bytes. +A ``word'' is a maximal sequence of consecutive characters other than +newline, space, or tab, containing at least one visible ASCII code. +(We assume that the standard ASCII code is in use.) + +This version of \.{wc} has a nonstandard ``silent'' option (\.{-s}), +which suppresses printing except for the grand totals over all files. + +@ Most \.{CWEB} programs share a common structure. It's probably a +good idea to state the overall structure explicitly at the outset, +even though the various parts could all be introduced in unnamed +sections of the code if we wanted to add them piecemeal. + +Here, then, is an overview of the file \.{wc.c} that is defined +by this \.{CWEB} program \.{wc.w}: + +@c +@<Header files to include@>@/ +@<Global variables@>@/ +@<Functions@>@/ +@<The main program@> + +@ We must include the standard I/O definitions, since we want to send +formatted output to |stdout| and |stderr|. + +@<Header files...@>= +#include <stdio.h> + +@ The |status| variable will tell the operating system if the run was +successful or not, and |prog_name| is used in case there's an error message to +be printed. + +@d OK 0 /* |status| code for successful run */ +@d usage_error 1 /* |status| code for improper syntax */ +@d cannot_open_file 2 /* |status| code for file access error */ + +@<Global variables@>= +int status=OK; /* exit status of command, initially |OK| */ +char *prog_name; /* who we are */ + +@ Now we come to the general layout of the |main| function. + +@<The main...@>= +main (argc,argv) + int argc; /* the number of arguments on the \UNIX/ command line */ + char **argv; /* the arguments themselves, an array of strings */ +{ + @<Variables local to |main|@>@; + prog_name=argv[0]; + @<Set up option selection@>; + @<Process all the files@>; + @<Print the grand totals if there were multiple files @>; + exit(status); +} + +@ If the first argument begins with a `\.{-}', the user is choosing +the desired counts and specifying the order in which they should be +displayed. Each selection is given by the initial character +(lines, words, or characters). For example, `\.{-cl}' would cause +just the number of characters and the number of lines to be printed, +in that order. The default, if no special argument is given, is `\.{-lwc}'. + +We do not process this string now; we simply remember where it is. +It will be used to control the formatting at output time. + +If the `\.{-}' is immediately followed by `\.{s}', only summary totals +are printed. + +@<Var...@>= +int file_count; /* how many files there are */ +char *which; /* which counts to print */ +int silent=0; /* nonzero if the silent option was selected */ + +@ @<Set up o...@>= +which="lwc"; /* if no option is given, print all three values */ +if (argc>1 && *argv[1] == '-') { + argv[1]++; + if (*argv[1]=='s') silent=1,argv[1]++; + if (*argv[1]) which=argv[1]; + argc--; argv++; +} +file_count=argc-1; + +@ Now we scan the remaining arguments and try to open a file, if +possible. The file is processed and its statistics are given. +We use a |do|~\dots~|while| loop because we should read from the +standard input if no file name is given. + +@<Process...@>= +argc--; +do@+{ + @<If a file is given, try to open |*(++argv)|; |continue| if unsuccessful@>; + @<Initialize pointers and counters@>; + @<Scan file@>; + @<Write statistics for file@>; + @<Close file@>; + @<Update grand totals@>; /* even if there is only one file */ +}@+while (--argc>0); + +@ Here's the code to open the file. A special trick allows us to +handle input from |stdin| when no name is given. +Recall that the file descriptor to |stdin| is~0; that's what we +use as the default initial value. + +@<Variabl...@>= +int fd=0; /* file descriptor, initialized to |stdin| */ + +@ @d READ_ONLY 0 /* read access code for system |open| routine */ + +@<If a file...@>= +if (file_count>0 && (fd=open(*(++argv),READ_ONLY))<0) { + fprintf (stderr, "%s: cannot open file %s\n", prog_name, *argv); +@.cannot open file@> + status|=cannot_open_file; + file_count--; + continue; +} + +@ @<Close file@>= +close(fd); + +@ We will do some homemade buffering in order to speed things up: Characters +will be read into the |buffer| array before we process them. +To do this we set up appropriate pointers and counters. + +@d buf_size BUFSIZ /* \.{stdio.h}'s |BUFSIZ| is chosen for efficiency*/ + +@<Var...@>= +char buffer[buf_size]; /* we read the input into this array */ +register char *ptr; /* the first unprocessed character in |buffer| */ +register char *buf_end; /* the first unused position in |buffer| */ +register int c; /* current character, or number of characters just read */ +int in_word; /* are we within a word? */ +long word_count, line_count, char_count; /* number of words, lines, + and characters found in the file so far */ + +@ @<Init...@>= +ptr=buf_end=buffer; line_count=word_count=char_count=0; in_word=0; + +@ The grand totals must be initialized to zero at the beginning of the +program. If we made these variables local to |main|, we would have to +do this initialization explicitly; however, \CEE/'s globals are automatically +zeroed. (Or rather, ``statically zeroed.'') (Get it?) +@^Joke@> + +@<Global var...@>= +long tot_word_count, tot_line_count, tot_char_count; + /* total number of words, lines, and chars */ + +@ The present section, which does the counting that is \.{wc}'s {\it raison +d'\^etre}, was actually one of the simplest to write. We look at each +character and change state if it begins or ends a word. + +@<Scan...@>= +while (1) { + @<Fill |buffer| if it is empty; |break| at end of file@>; + c=*ptr++; + if (c>' ' && c<0177) { /* visible ASCII codes */ + if (!in_word) {word_count++; in_word=1;} + continue; + } + if (c=='\n') line_count++; + else if (c!=' ' && c!='\t') continue; + in_word=0; /* |c| is newline, space, or tab */ +} + +@ Buffered I/O allows us to count the number of characters almost for free. + +@<Fill |buff...@>= +if (ptr>=buf_end) { + ptr=buffer; c=read(fd,ptr,buf_size); + if (c<=0) break; + char_count+=c; buf_end=buffer+c; +} + +@ It's convenient to output the statistics by defining a new function +|wc_print|; then the same function can be used for the totals. +Additionally we must decide here if we know the name of the file +we have processed or if it was just |stdin|. + +@<Write...@>= +if (!silent) { + wc_print(which, char_count, word_count, line_count); + if (file_count) printf (" %s\n", *argv); /* not |stdin| */ + else printf ("\n"); /* |stdin| */ +} + +@ @<Upda...@>= +tot_line_count+=line_count; +tot_word_count+=word_count; +tot_char_count+=char_count; + +@ We might as well improve a bit on \UNIX/'s \.{wc} by displaying the +number of files too. + +@<Print the...@>= +if (file_count>1 || silent) { + wc_print(which, tot_char_count, tot_word_count, tot_line_count); + if (!file_count) printf("\n"); + else printf(" total in %d file%s\n",file_count,file_count>1?"s":""); +} + +@ Here now is the function that prints the values according to the +specified options. The calling routine is supposed to supply a +newline. If an invalid option character is found we inform +the user about proper usage of the command. Counts are printed in +8-digit fields so that they will line up in columns. + +@d print_count(n) printf("%8ld",n) + +@<Fun...@>= +wc_print(which, char_count, word_count, line_count) +char *which; /* which counts to print */ +long char_count, word_count, line_count; /* given totals */ +{ + while (*which) + switch (*which++) { + case 'l': print_count(line_count); break; + case 'w': print_count(word_count); break; + case 'c': print_count(char_count); break; + default: if ((status & usage_error)==0) { + fprintf (stderr, "\nUsage: %s [-lwc] [filename ...]\n", prog_name); +@.Usage: ...@> + status|=usage_error; + } + } +} + +@ Incidentally, a test of this program against the system \.{wc} +command on a \SPARC\ showed that the ``official'' \.{wc} was slightly +slower. Furthermore, although that \.{wc} gave an appropriate error +message for the options `\.{-abc}', it made no complaints about the +options `\.{-labc}'! Dare we suggest that the system routine might have been +better if its programmer had used a more literate approach? + +@* Index. +Here is a list of the identifiers used, and where they appear. Underlined +entries indicate the place of definition. Error messages are also shown. diff --git a/web/c_cpp/cweb/examples/wmer-os2.ch b/web/c_cpp/cweb/examples/wmer-os2.ch new file mode 100644 index 0000000000..ad40dc496f --- /dev/null +++ b/web/c_cpp/cweb/examples/wmer-os2.ch @@ -0,0 +1,21 @@ +Change file for wmerge, for OS/2 and WinNT and MSVC++ 2.2 +contributed May 1996 by Andreas Scherer, Aaxen University of TeXnology +(scherer@physik.rwth-aachen.de) + +@x Line 594 +An omitted change file argument means that |'/dev/null'| should be used, +@y +An omitted change file argument means that |'NUL'| should be used, +@z + +@x Line 620 + else if (*s=='/') dot_pos=NULL,++s; +@y + else if (*s==':' || *s=='\\' || *s=='/') dot_pos=NULL,++s; +@z + +@x Line 630 + if (!found_change) strcpy(change_file_name,"/dev/null"); +@y + if (!found_change) strcpy(change_file_name,"NUL"); +@z diff --git a/web/c_cpp/cweb/examples/wmerg-pc.ch b/web/c_cpp/cweb/examples/wmerg-pc.ch new file mode 100644 index 0000000000..912f46b708 --- /dev/null +++ b/web/c_cpp/cweb/examples/wmerg-pc.ch @@ -0,0 +1,22 @@ +Change file for wmerge, for MSDOS and Borland C++ 3.1. +To compile the C file: bcc -w-pro wmerge.c + +(This file contributed by Barry Schwartz, trashman@crud.mn.org, 28 Jun 94.) +(Updated 17 Jul 06 by Don Knuth.) + +@x Section 19. + cur_file_name[l]='/'; /* \UNIX/ pathname separator */ +@y + cur_file_name[l]='/'; /* A valid {\mc MSDOS} pathname separator */ +@z + + +@x Section 33. + if (*s=='.') dot_pos=s++; + else if (*s=='/') dot_pos=NULL,++s; + else s++; +@y + if (*s=='.') dot_pos=s++; + else if (*s==':' || *s=='\\' || *s=='/') dot_pos=NULL,++s; + else s++; +@z diff --git a/web/c_cpp/cweb/examples/wmerge.w b/web/c_cpp/cweb/examples/wmerge.w new file mode 100644 index 0000000000..616172d042 --- /dev/null +++ b/web/c_cpp/cweb/examples/wmerge.w @@ -0,0 +1,717 @@ +% Modified 16 Jan 2002 to agree with COMMON version 3.64 + +\def\9#1{} % this hack is explained in CWEB manual Appendix F11 + +@* Introduction. This file contains the program \.{wmerge}, +which takes two or more files and merges them according +to the conventions of \.{CWEB}. Namely, it takes an ordinary \.{.w} +file and and optional \.{.ch} file and sends the corresponding +\.{.w}-style file to standard output (or to a named file), +expanding all ``includes'' +that might be specified by \.{@@i} in the original \.{.w} file. +(A more precise description appears in the section on ``command line +arguments'' below.) + +@c +#include <stdio.h> +#include <stdlib.h> /* declaration of |getenv| */ +#include <ctype.h> /* definition of |isalpha|, |isdigit| and so on */ +@<Definitions@>@; +@<Predeclarations of functions@>@; +@<Functions@>@; +main (ac,av) +int ac; char **av; +{ + argc=ac; argv=av; + @<Set the default options@>; + @<Scan arguments and open output file@>; + reset_input(); + while (get_line()) + put_line(); + fflush(out_file); + check_complete(); + fflush(out_file); + return wrap_up(); +} + +@ @<Definitions@>= +typedef short boolean; +typedef unsigned char eight_bits; +typedef char ASCII; /* type of characters inside \.{WEB} */ + +@ We predeclare some standard string-handling functions here instead of +including their system header files, because the names of the header files +are not as standard as the names of the functions. (There's confusion +between \.{<string.h>} and \.{<strings.h>}.) + +@<Predecl...@>= +extern size_t strlen(); /* length of string */ +extern char* strcpy(); /* copy one string to another */ +extern int strncmp(); /* compare up to $n$ string characters */ +extern char* strncpy(); /* copy up to $n$ string characters */ + +@ @<Predec...@>= + +@ The lowest level of input to the \.{WEB} programs +is performed by |input_ln|, which must be told which file to read from. +The return value of |input_ln| is 1 if the read is successful and 0 if +not (generally this means the file has ended). +The characters of the next line of the file +are copied into the |buffer| array, +and the global variable |limit| is set to the first unoccupied position. +Trailing blanks are ignored. The value of |limit| must be strictly less +than |buf_size|, so that |buffer[buf_size-1]| is never filled. + +Some of the routines use the fact that it is safe to refer to +|*(limit+2)| without overstepping the bounds of the array. + +@d buf_size 4096 + +@<Definitions...@>= +ASCII buffer[buf_size]; /* where each line of input goes */ +ASCII *buffer_end=buffer+buf_size-2; /* end of |buffer| */ +ASCII *limit; /* points to the last character in the buffer */ +ASCII *loc; /* points to the next character to be read from the buffer */ + +@ In the unlikely event that your standard I/O library does not +support |feof|, |getc| and |ungetc|, you may have to change things here. +@^system dependencies@> + +Incidentally, here's a curious fact about \.{CWEB} for those of you +who are reading this file as an example of \.{CWEB} programming. +The file \.{stdio.h} includes a typedef for +the identifier |FILE|, which is not, strictly speaking, part of \CEE/. +It turns out \.{CWEAVE} knows that |FILE| is a reserved word (after all, +|FILE| is almost as common as |int|); indeed, \.{CWEAVE} knows all +the types of the ISO standard \CEE/ library. But +if you're using other types like {\bf caddr\_t}, +@:caddr_t}{\bf caddr\_t@> +which is defined in \.{/usr/include/sys/types.h}, you should let +\.{WEAVE} know that this is a type, either by including the \.{.h} file +at \.{WEB} time (saying \.{@@i /usr/include/sys/types.h}), or by +using \.{WEB}'s format command (saying \.{@@f caddr\_t int}). Either of +these will make {\bf caddr\_t} be treated in the same way as |int|. + +@<Func...@>= +input_ln(fp) /* copies a line into |buffer| or returns 0 */ +FILE *fp; /* what file to read from */ +{ + register int c=EOF; /* character read; initialized so some compilers won't complain */ + register char *k; /* where next character goes */ + if (feof(fp)) return(0); /* we have hit end-of-file */ + limit = k = buffer; /* beginning of buffer */ + while (k<=buffer_end && (c=getc(fp)) != EOF && c!='\n') + if ((*(k++) = c) != ' ') limit = k; + if (k>buffer_end) + if ((c=getc(fp))!=EOF && c!='\n') { + ungetc(c,fp); loc=buffer; err_print("! Input line too long"); +@.Input line too long@> + } + if (c==EOF && limit==buffer) return(0); /* there was nothing after + the last newline */ + return(1); +} + +@ Now comes the problem of deciding which file to read from next. +Recall that the actual text that \.{CWEB} should process comes from two +streams: a |web_file|, which can contain possibly nested include +commands \.{@@i}, and a |change_file|, which might also contain +includes. The |web_file| together with the currently open include +files form a stack |file|, whose names are stored in a parallel stack +|file_name|. The boolean |changing| tells whether or not we're reading +from the |change_file|. + +The line number of each open file is also kept for error reporting. + +@f line x /* make |line| an unreserved word */ +@d max_include_depth 10 /* maximum number of source files open + simultaneously, not counting the change file */ +@d max_file_name_length 60 +@d cur_file file[include_depth] /* current file */ +@d cur_file_name file_name[include_depth] /* current file name */ +@d cur_line line[include_depth] /* number of current line in current file */ +@d web_file file[0] /* main source file */ +@d web_file_name file_name[0] /* main source file name */ + +@<Definitions...@>= +int include_depth; /* current level of nesting */ +FILE *file[max_include_depth]; /* stack of non-change files */ +FILE *change_file; /* change file */ +char file_name[max_include_depth][max_file_name_length]; + /* stack of non-change file names */ +char change_file_name[max_file_name_length]; /* name of change file */ +char alt_web_file_name[max_file_name_length]; /* alternate name to try */ +int line[max_include_depth]; /* number of current line in the stacked files */ +int change_line; /* number of current line in change file */ +int change_depth; /* where \.{@@y} originated during a change */ +boolean input_has_ended; /* if there is no more input */ +boolean changing; /* if the current line is from |change_file| */ +boolean web_file_open=0; /* if the web file is being read */ + +@ When |changing=0|, the next line of |change_file| is kept in +|change_buffer|, for purposes of comparison with the next +line of |cur_file|. After the change file has been completely input, we +set |change_limit=change_buffer|, +so that no further matches will be made. + +Here's a shorthand expression for inequality between the two lines: + +@d lines_dont_match (change_limit-change_buffer != limit-buffer || + strncmp(buffer, change_buffer, limit-buffer)) + +@<Def...@>= +char change_buffer[buf_size]; /* next line of |change_file| */ +char *change_limit; /* points to the last character in |change_buffer| */ + +@ Procedure |prime_the_change_buffer| sets |change_buffer| in +preparation for the next matching operation. Since blank lines in the change +file are not used for matching, we have +|(change_limit==change_buffer && !changing)| if and only if +the change file is exhausted. This procedure is called only when +|changing| is 1; hence error messages will be reported correctly. + +@<Func...@>= +void +prime_the_change_buffer() +{ + change_limit=change_buffer; /* this value is used if the change file ends */ + @<Skip over comment lines in the change file; |return| if end of file@>; + @<Skip to the next nonblank line; |return| if end of file@>; + @<Move |buffer| and |limit| to |change_buffer| and |change_limit|@>; +} + +@ While looking for a line that begins with \.{@@x} in the change file, we +allow lines that begin with \.{@@}, as long as they don't begin with \.{@@y}, +\.{@@z} or \.{@@i} (which would probably mean that the change file is fouled up). + +@<Skip over comment lines in the change file...@>= +while(1) { + change_line++; + if (!input_ln(change_file)) return; + if (limit<buffer+2) continue; + if (buffer[0]!='@@') continue; + if (isupper(buffer[1])) buffer[1]=tolower(buffer[1]); + if (buffer[1]=='x') break; + if (buffer[1]=='y' || buffer[1]=='z' || buffer[1]=='i') { + loc=buffer+2; + err_print("! Missing @@x in change file"); +@.Missing @@x...@> + } +} + +@ Here we are looking at lines following the \.{@@x}. + +@<Skip to the next nonblank line...@>= +do { + change_line++; + if (!input_ln(change_file)) { + err_print("! Change file ended after @@x"); +@.Change file ended...@> + return; + } +} while (limit==buffer); + +@ @<Move |buffer| and |limit| to |change_buffer| and |change_limit|@>= +{ + change_limit=change_buffer+(limit-buffer); + strncpy(change_buffer,buffer,limit-buffer+1); +} + +@ The following procedure is used to see if the next change entry should +go into effect; it is called only when |changing| is 0. +The idea is to test whether or not the current +contents of |buffer| matches the current contents of |change_buffer|. +If not, there's nothing more to do; but if so, a change is called for: +All of the text down to the \.{@@y} is supposed to match. An error +message is issued if any discrepancy is found. Then the procedure +prepares to read the next line from |change_file|. + +This procedure is called only when |buffer<limit|, i.e., when the +current line is nonempty. + +@<Func...@>= +void +check_change() /* switches to |change_file| if the buffers match */ +{ + int n=0; /* the number of discrepancies found */ + if (lines_dont_match) return; + while (1) { + changing=1; change_line++; + if (!input_ln(change_file)) { + err_print("! Change file ended before @@y"); +@.Change file ended...@> + change_limit=change_buffer; changing=0; + return; + } + if (limit>buffer+1 && buffer[0]=='@@') { + char xyz_code=isupper(buffer[1])? tolower(buffer[1]): buffer[1]; + @<If the current line starts with \.{@@y}, + report any discrepancies and |return|@>; + } + @<Move |buffer| and |limit|...@>; + changing=0; cur_line++; + while (!input_ln(cur_file)) { /* pop the stack or quit */ + if (include_depth==0) { + err_print("! CWEB file ended during a change"); +@.CWEB file ended...@> + input_has_ended=1; return; + } + include_depth--; cur_line++; + } + if (lines_dont_match) n++; + } +} + +@ @<If the current line starts with \.{@@y}...@>= +if (xyz_code=='x' || xyz_code=='z') { + loc=buffer+2; err_print("! Where is the matching @@y?"); +@.Where is the match...@> + } +else if (xyz_code=='y') { + if (n>0) { + loc=buffer+2; + fprintf(stderr,"\n! Hmm... %d ",n); + err_print("of the preceding lines failed to match"); +@.Hmm... n of the preceding...@> + } + change_depth=include_depth; + return; +} + +@ The |reset_input| procedure gets the program ready to read the +user's \.{WEB} input. + +@<Func...@>= +void +reset_input() +{ + limit=buffer; loc=buffer+1; buffer[0]=' '; + @<Open input files@>; + include_depth=0; cur_line=0; change_line=0; + change_depth=include_depth; + changing=1; prime_the_change_buffer(); changing=!changing; + limit=buffer; loc=buffer+1; buffer[0]=' '; input_has_ended=0; +} + +@ The following code opens the input files. +@^system dependencies@> + +@<Open input files@>= +if ((web_file=fopen(web_file_name,"r"))==NULL) { + strcpy(web_file_name,alt_web_file_name); + if ((web_file=fopen(web_file_name,"r"))==NULL) + fatal("! Cannot open input file ", web_file_name); +} +@.Cannot open input file@> +@.Cannot open change file@> +web_file_open=1; +if ((change_file=fopen(change_file_name,"r"))==NULL) + fatal("! Cannot open change file ", change_file_name); + +@ The |get_line| procedure is called when |loc>limit|; it puts the next +line of merged input into the buffer and updates the other variables +appropriately. A space is placed at the right end of the line. +This procedure returns |!input_has_ended| because we often want to +check the value of that variable after calling the procedure. + +@<Fun...@>= +int get_line() /* inputs the next line */ +{ + restart: + if (changing && include_depth==change_depth) + @<Read from |change_file| and maybe turn off |changing|@>; + if (! changing || include_depth>change_depth) { + @<Read from |cur_file| and maybe turn on |changing|@>; + if (changing && include_depth==change_depth) goto restart; + } + if (input_has_ended) return 0; + loc=buffer; *limit=' '; + if (buffer[0]=='@@' && (buffer[1]=='i' || buffer[1]=='I')) { + loc=buffer+2; *limit='"'; + while (*loc==' '||*loc=='\t') loc++; + if (loc>=limit) { + err_print("! Include file name not given"); +@.Include file name ...@> + goto restart; + } + if (include_depth>=max_include_depth-1) { + err_print("! Too many nested includes"); +@.Too many nested includes@> + goto restart; + } + include_depth++; /* push input stack */ + @<Try to open include file, abort push if unsuccessful, go to |restart|@>; + } + return 1; +} + +void put_line() +{ + char *ptr=buffer; + while (ptr<limit) putc(*ptr++,out_file); + putc('\n',out_file); +} + +@ When an \.{@@i} line is found in the |cur_file|, we must temporarily +stop reading it and start reading from the named include file. The +\.{@@i} line should give a complete file name with or without +double quotes. +If the environment variable \.{CWEBINPUTS} is set, or if the compiler flag +of the same name was defined at compile time, +\.{CWEB} will look for include files in the directory thus named, if +it cannot find them in the current directory. +(Colon-separated paths are not supported.) +The remainder of the \.{@@i} line after the file name is ignored. + +@d too_long() {include_depth--; + err_print("! Include file name too long"); goto restart;} + +@ @<Try to open...@>= { + char temp_file_name[max_file_name_length]; + char *cur_file_name_end=cur_file_name+max_file_name_length-1; + char *k=cur_file_name, *kk; + int l; /* length of file name */ + + if (*loc=='"') { + loc++; + while (*loc!='"' && k<=cur_file_name_end) *k++=*loc++; + if (loc==limit) k=cur_file_name_end+1; /* unmatched quote is `too long' */ + } else + while (*loc!=' '&&*loc!='\t'&&*loc!='"'&&k<=cur_file_name_end) *k++=*loc++; + if (k>cur_file_name_end) too_long(); +@.Include file name ...@> + *k='\0'; + if ((cur_file=fopen(cur_file_name,"r"))!=NULL) { + cur_line=0; + goto restart; /* success */ + } + kk=getenv("CWEBINPUTS"); + if (kk!=NULL) { + if ((l=strlen(kk))>max_file_name_length-2) too_long(); + strcpy(temp_file_name,kk); + } + else { +#ifdef CWEBINPUTS + if ((l=strlen(CWEBINPUTS))>max_file_name_length-2) too_long(); + strcpy(temp_file_name,CWEBINPUTS); +#else + l=0; +#endif /* |CWEBINPUTS| */ + } + if (l>0) { + if (k+l+2>=cur_file_name_end) too_long(); +@.Include file name ...@> + for (; k>= cur_file_name; k--) *(k+l+1)=*k; + strcpy(cur_file_name,temp_file_name); + cur_file_name[l]='/'; /* \UNIX/ pathname separator */ + if ((cur_file=fopen(cur_file_name,"r"))!=NULL) { + cur_line=0; + goto restart; /* success */ + } + } + include_depth--; err_print("! Cannot open include file"); goto restart; +} + +@ @<Read from |cur_file|...@>= { + cur_line++; + while (!input_ln(cur_file)) { /* pop the stack or quit */ + if (include_depth==0) {input_has_ended=1; break;} + else { + fclose(cur_file); include_depth--; + if (changing && include_depth==change_depth) break; + cur_line++; + } + } + if (!changing && !input_has_ended) + if (limit-buffer==change_limit-change_buffer) + if (buffer[0]==change_buffer[0]) + if (change_limit>change_buffer) check_change(); +} + +@ @<Read from |change_file|...@>= { + change_line++; + if (!input_ln(change_file)) { + err_print("! Change file ended without @@z"); +@.Change file ended...@> + buffer[0]='@@'; buffer[1]='z'; limit=buffer+2; + } + if (limit>buffer) { /* check if the change has ended */ + *limit=' '; + if (buffer[0]=='@@') { + if (isupper(buffer[1])) buffer[1]=tolower(buffer[1]); + if (buffer[1]=='x' || buffer[1]=='y') { + loc=buffer+2; + err_print("! Where is the matching @@z?"); +@.Where is the match...@> + } + else if (buffer[1]=='z') { + prime_the_change_buffer(); changing=!changing; + } + } + } +} + +@ At the end of the program, we will tell the user if the change file +had a line that didn't match any relevant line in |web_file|. + +@<Funct...@>= +void +check_complete(){ + if (change_limit!=change_buffer) { /* |changing| is 0 */ + strncpy(buffer,change_buffer,change_limit-change_buffer+1); + limit=buffer+(int)(change_limit-change_buffer); + changing=1; change_depth=include_depth; loc=buffer; + err_print("! Change file entry did not match"); + @.Change file entry did not match@> + } +} + +@* Reporting errors to the user. +A global variable called |history| will contain one of four values +at the end of every run: |spotless| means that no unusual messages were +printed; |harmless_message| means that a message of possible interest +was printed but no serious errors were detected; |error_message| means that +at least one error was found; |fatal_message| means that the program +terminated abnormally. The value of |history| does not influence the +behavior of the program; it is simply computed for the convenience +of systems that might want to use such information. + +@d spotless 0 /* |history| value for normal jobs */ +@d harmless_message 1 /* |history| value when non-serious info was printed */ +@d error_message 2 /* |history| value when an error was noted */ +@d fatal_message 3 /* |history| value when we had to stop prematurely */ +@d mark_harmless {if (history==spotless) history=harmless_message;} +@d mark_error history=error_message + +@<Definit...@>= +int history=spotless; /* indicates how bad this run was */ + +@ The command `|err_print("! Error message")|' will report a syntax error to +the user, by printing the error message at the beginning of a new line and +then giving an indication of where the error was spotted in the source file. +Note that no period follows the error message, since the error routine +will automatically supply a period. A newline is automatically supplied +if the string begins with |"!"|. + +The actual error indications are provided by a procedure called |error|. + +@<Predecl...@>= +void err_print(); + +@ +@<Functions...@>= +void +err_print(s) /* prints `\..' and location of error message */ +char *s; +{ + char *k,*l; /* pointers into |buffer| */ + fprintf(stderr,*s=='!'? "\n%s" : "%s",s); + if(web_file_open) @<Print error location based on input buffer@>@; + else putc('\n',stderr); + update_terminal; mark_error; +} + +@ The error locations can be indicated by using the global variables +|loc|, |cur_line|, |cur_file_name| and |changing|, +which tell respectively the first +unlooked-at position in |buffer|, the current line number, the current +file, and whether the current line is from |change_file| or |cur_file|. +This routine should be modified on systems whose standard text editor +has special line-numbering conventions. +@^system dependencies@> + +@<Print error location based on input buffer@>= +{if (changing && include_depth==change_depth) + fprintf(stderr,". (l. %d of change file)\n", change_line); +else if (include_depth==0) fprintf(stderr,". (l. %d)\n", cur_line); + else fprintf(stderr,". (l. %d of include file %s)\n", cur_line, cur_file_name); +l= (loc>=limit? limit: loc); +if (l>buffer) { + for (k=buffer; k<l; k++) + if (*k=='\t') putc(' ',stderr); + else putc(*k,stderr); /* print the characters already read */ + putc('\n',stderr); + for (k=buffer; k<l; k++) putc(' ',stderr); /* space out the next line */ +} +for (k=l; k<limit; k++) putc(*k,stderr); /* print the part not yet read */ + putc('\n',stderr); +} + +@ When no recovery from some error has been provided, we have to wrap +up and quit as graciously as possible. This is done by calling the +function |wrap_up| at the end of the code. + +@d fatal(s,t) { + fprintf(stderr,s); err_print(t); + history=fatal_message; exit(wrap_up()); +} + +@ Some implementations may wish to pass the |history| value to the +operating system so that it can be used to govern whether or not other +programs are started. Here, for instance, we pass the operating system +a status of 0 if and only if only harmless messages were printed. +@^system dependencies@> + +@<Func...@>= +wrap_up() { + @<Print the job |history|@>; + if (history > harmless_message) return(1); + else return(0); +} + +@ @<Print the job |history|@>= +switch (history) { +case spotless: if (show_happiness) fprintf(stderr,"(No errors were found.)\n"); break; +case harmless_message: + fprintf(stderr,"(Did you see the warning message above?)\n"); break; +case error_message: + fprintf(stderr,"(Pardon me, but I think I spotted something wrong.)\n"); break; +case fatal_message: fprintf(stderr,"(That was a fatal error, my friend.)\n"); +} /* there are no other cases */ + +@* Command line arguments. +The user calls \.{wmerge} with arguments on the command line. +These are either file names or flags to be turned off (beginning with |"-"|) +or flags to be turned on (beginning with |"+"|. +The following globals are for communicating the user's desires to the rest +of the program. The various file name variables contain strings with +the names of those files. Most of the 128 flags are undefined but available +for future extensions. + +@d show_banner flags['b'] /* should the banner line be printed? */ +@d show_happiness flags['h'] /* should lack of errors be announced? */ + +@<Defin...@>= +int argc; /* copy of |ac| parameter to |main| */ +char **argv; /* copy of |av| parameter to |main| */ +char out_file_name[max_file_name_length]; /* name of |out_file| */ +boolean flags[128]; /* an option for each 7-bit code */ + +@ The |flags| will be initially 1. + +@<Set the default options@>= +show_banner=show_happiness=1; + +@ We now must look at the command line arguments and set the file names +accordingly. At least one file name must be present: the \.{WEB} +file. It may have an extension, or it may omit it to get |'.w'| +added. + +If there is another file name present among the arguments, it is the +change file, again either with an extension or without one to get |'.ch'| +An omitted change file argument means that |'/dev/null'| should be used, +when no changes are desired. +@^system dependencies@> + +If there's a third file name, it will be the output file. + +@<Pred...@>= +void scan_args(); + +@ +@<Function...@>= +void +scan_args() +{ + char *dot_pos; /* position of |'.'| in the argument */ + register char *s; /* register for scanning strings */ + boolean found_web=0,found_change=0,found_out=0; + /* have these names have been seen? */ + boolean flag_change; + + while (--argc > 0) { + if (**(++argv)=='-' || **argv=='+') @<Handle flag argument@>@; + else { + s=*argv;@+dot_pos=NULL; + while (*s) { + if (*s=='.') dot_pos=s++; + else if (*s=='/') dot_pos=NULL,++s; + else s++; + } + if (!found_web) @<Make |web_file_name|@>@; + else if (!found_change) @<Make |change_file_name| from |fname|@>@; + else if (!found_out) @<Override output file name@>@; + else @<Print usage error message and quit@>; + } + } + if (!found_web) @<Print usage error message and quit@>; + if (!found_change) strcpy(change_file_name,"/dev/null"); +} + +@ We use all of |*argv| for the |web_file_name| if there is a |'.'| in it, +otherwise we add |".w"|. If this file can't be opened, we prepare an +|alt_web_file_name| by adding |"web"| after the dot. +The other file names come from adding other things +after the dot. We must check that there is enough room in +|web_file_name| and the other arrays for the argument. + +@<Make |web_file_name|...@>= +{ + if (s-*argv > max_file_name_length-5) + @<Complain about argument length@>; + if (dot_pos==NULL) + sprintf(web_file_name,"%s.w",*argv); + else { + strcpy(web_file_name,*argv); + *dot_pos=0; /* string now ends where the dot was */ + } + sprintf(alt_web_file_name,"%s.web",*argv); + *out_file_name='\0'; /* this will print to stdout */ + found_web=1; +} + +@ @<Make |change_file_name|...@>= +{ + if (s-*argv > max_file_name_length-4) + @<Complain about argument length@>; + if (dot_pos==NULL) + sprintf(change_file_name,"%s.ch",*argv); + else strcpy(change_file_name,*argv); + found_change=1; +} + +@ @<Override...@>= +{ + if (s-*argv > max_file_name_length-5) + @<Complain about argument length@>; + if (dot_pos==NULL) sprintf(out_file_name,"%s.out",*argv); + else strcpy(out_file_name,*argv); + found_out=1; +} + +@ @<Handle flag...@>= +{ + if (**argv=='-') flag_change=0; + else flag_change=1; + for(dot_pos=*argv+1;*dot_pos>'\0';dot_pos++) + flags[*dot_pos]=flag_change; +} + +@ @<Print usage error message and quit@>= +{ + fatal("! Usage: wmerge webfile[.w] [changefile[.ch] [outfile[.out]]]\n","")@; +} + +@ @<Complain about arg...@>= fatal("! Filename too long\n", *argv); + +@* Output. Here is the code that opens the output file: +@^system dependencies@> + +@<Defin...@>= +FILE *out_file; /* where output goes */ + +@ @<Scan arguments and open output file@>= +scan_args(); +if (out_file_name[0]=='\0') out_file=stdout; +else if ((out_file=fopen(out_file_name,"w"))==NULL) + fatal("! Cannot open output file ", out_file_name); +@.Cannot open output file@> + +@ The |update_terminal| procedure is called when we want +to make sure that everything we have output to the terminal so far has +actually left the computer's internal buffers and been sent. +@^system dependencies@> + +@d update_terminal fflush(stderr) /* empty the terminal output buffer */ + +@* Index. diff --git a/web/c_cpp/cweb/examples/wordtest.w b/web/c_cpp/cweb/examples/wordtest.w new file mode 100644 index 0000000000..98de5aedc6 --- /dev/null +++ b/web/c_cpp/cweb/examples/wordtest.w @@ -0,0 +1,540 @@ +\datethis +@* Introduction. This program is a simple filter that sorts and outputs all +lines of input that do not appear in a given set of sorted files. It is called +{\tt wordtest} because each line of input is considered to be a `word' and +each of the sorted files is considered to be a 'dictionary'. Words are +output when they don't appear in any given dictionary. + +The character set and alphabetic order are flexible. Every 8-bit +character is mapped into an integer called its {\it ord}. A character +is called a {\it null\/} if its ord is zero; such characters are +discarded from the input. A character is called a {\it break\/} if +its ord is negative; such characters break the input into so-called words. +Otherwise a character's ord is positive, and the character is called a +{\it letter}. One letter precedes another in alphabetic order if and only +if it has a smaller ord. Two letters are considered identical, for +purposes of sorting, if their ords are the same. + +The null character |'\n'| must have ord~0; thus, it must remain null. +Otherwise the ord mapping is arbitrary. If the user doesn't specify +any special mapping, the default ord table simply maps every 8-bit +character code into itself, considering characters to be unsigned char +values in the range 0--255, except that ASCII codes {\tt a-z} are +mapped into the corresponding codes for {\tt A-Z}, and newline is a +break character. Optional command-line arguments, described below, can +change this default mapping to any other desired scheme. + +A word is any nonempty sequence of letters that is immediately preceded +and followed by break characters, when nulls are ignored. Technically +speaking, we pretend that a break character is present at the beginning of a +file but not at the end; thus, all letters following the final break character +of a file are ignored, if any such letters are present. Two words are +{\it equivalent\/} to each other if their letters have the same sequence +of ord values. If two or more words of the input are equivalent, only +the first will be output, and it will be output only if it is not +equivalent to any word in the given dictionary files. Words in each +dictionary are assumed to be in lexicographic order and to contain no +nulls. Words in the output file will satisfy these conditions; therefore +{\tt wordtest} can be used to generate and update the dictionaries it needs. +Notice that if no dictionaries are given, {\tt wordtest} will act as a +sorting routine that simply discards nulls and duplicate lines. + +@ The \UNIX/ command line `{\tt wordtest} {\tt [options]} {\tt [dictionaries]}' +is interpreted by executing option commands from left to right and then by +regarding any remaining arguments as the names of dictionary files. + +Most of the option commands are designed to specify the |ord| table. +Initially |ord[c]=c| for each unsigned char code~|c|. The command +$$\line{\hskip5em\tt-b\it string\hfil}$$ +makes every character in the string a break character. If the string is +empty, {\tt-b} makes every nonnull character a break (i.e., it sets +|ord[c]=-1| for |1<=c<=255|). The command +$$\line{\hskip5em\tt-n\it string\hfil}$$ +makes every character in the string a null character. If the string is +empty, {\tt-n} makes every character null. The command +$$\line{\hskip5em\tt-a\it string\hfil}$$ +sets the ord of the $k$th element of the string equal to $\delta+k$, +where $\delta$ is an offset value (normally zero). The command +$$\line{\hskip5em\tt-d\it offset\hfil}$$ +sets the value of $\delta$; the offset should be a decimal integer between +0 and 255. + +There is also an option that has no effect on the |ord| table: +$$\line{\hskip5em\tt-m\it length\hfil}$$ +defines the length of the longest word. If any word of a file has +more than this many characters, a break is artificially inserted +so that a word of this maximum length is obtained. The default value is 50. +The maximum legal value is 1000. + +If the given options do not specify at least one break character, +{\tt wordtest} applies the option commands +$$\vbox{\line{\hskip5em\.{-b"\\}\hfil} +\line{\.{" -d64 -a"abcdefghijklmnopqrstuvwxyz"}\hfil}}$$ +which generate the default mapping mentioned above (unless other ords were +changed). + +The program is designed to run fastest when there are at most two +dictionary files (usually one large system dictionary and another +personalized one), although it places no limit on the actual number of +dictionaries that can be mentioned on the command line. Users who want +to specify a multitude of dictionaries should ask themselves why they +wouldn't prefer to merge their dictionaries together first (using +{\tt wordtest}). + +@d MAX_LENGTH_DEFAULT 50 +@d MAX_LENGTH_LIMIT 1000 + +@ The general organization of {\tt wordtest} is typical of applications +written in \CEE/, and its approach is quite simple. If any errors are +detected, an indication of the error is sent to the |stderr| file and +a nonzero value is returned. + +@p +#include <stdio.h> +#include <stdlib.h> +@# +@<Typedefs@>@; +int main(argc,argv) + int argc; /* the number of command-line arguments */ + char *argv[]; /* the arguments themselves */ +{ + @<Local variables@>; + @<Scan the command line arguments@>; + @<Sort the input into memory@>; + @<Output all input words that aren't in dictionaries@>; + return 0; +} + +@ @<Typedefs@>= +typedef unsigned char byte; /* our bytes will range from 0 to 255 */ + +@ @<Local variables@>= +int targc; /* temporary modifications to |argc| */ +byte **targv; /* pointer to the current argument of interest */ +unsigned delta; /* the offset used in the \.{-a} and \.{-d} options */ +unsigned max_length=MAX_LENGTH_DEFAULT; /* longest allowable word */ +byte breakchar; /* break character to use in the output */ +int ord[256]; /* table of ord values */ +register int c; /* an all-purpose index */ +register byte *u,*v; /* pointer to current string characters */ + +@ We try to use newline as the output break character, if possible. + +@<Scan the command line arguments@>= +for (c=0;c<256;c++) ord[c]=c; +delta=0; +targc=argc-1;@+targv=(byte**)argv+1; +while (targc && **targv=='-') { + @<Execute the option command |targv|@>; + targc--;@+targv++; +} +if (ord['\n']<0) breakchar='\n'; +else { + breakchar='\0'; + for (c=255;c;c--) if (ord[c]<0) breakchar=c; + if (!breakchar) @<Set up the default ords@>; +} +@<Allocate data structures for a total of |targc| files@>; +for (;targc;targc--,targv++) @<Open the dictionary file named |*targv|@>; + +@ @<Execute the option...@>= +switch((*targv)[1]) { +case 'a': for (c=delta,u=*targv+2;*u;u++) ord[*u]=++c;@+break; +case 'b': if ((*targv)[2]) for (u=*targv+2;*u;u++) ord[*u]=-1; + else for (c=1;c<256;c++) ord[c]=-1; + break; +case 'n': if ((*targv)[2]) for (u=*targv+2;*u;u++) ord[*u]=0; + else for (c=1;c<256;c++) ord[c]=0; + break; +case 'd': if (sscanf((char*)*targv+2,"%u",&delta)==1 && delta<256) break; + goto print_usage; +case 'm': if (sscanf((char*)*targv+2,"%u",&max_length)==1 && + max_length<=MAX_LENGTH_LIMIT) break; + goto print_usage; +default: print_usage: fprintf(stderr, + "Usage: %s {-{{a|b|n}string|{d|m}number}}* dictionaryname*\n",*argv); + return-1; +} + +@ @<Set up the default ords@>= +{ + ord['\n']=-1; /* newline is break character */ + breakchar='\n'; + for (c=1;c<=26;c++) ord['a'-1+c]='A'-1+c; +} + +@*Treaps. The most interesting part of this program is its sorting algorithm, +which is based on the ``treap'' data structure of Aragon and Seidel +[{\sl 30th IEEE Symposium on Foundations of Computer Science\/} (1989), +540--546]. +@^Aragon, Cecilia Rodriguez@>@^Seidel, Raimund@> +A treap is a binary tree whose nodes have two key fields. The primary +key, which in our application is a word from the input, obeys +tree-search order: All descendants of the left child of node~$p$ have +a primary key that is less than the primary key of~$p$, and all descendants +of its right child have a primary key that is greater. The secondary key, +which in our application is a unique pseudorandom integer attached to +each input word, obeys heap order: The secondary key of~$p$'s children +is greater than $p$'s own secondary key. + +A given set of nodes with distinct primary keys and distinct secondary +keys can be made into a treap in exactly one way. This unique treap +can be obtained, for example, by using ordinary tree insertion with +respect to primary keys while inserting nodes in order of their +secondary keys. It follows that, if the secondary keys are random, +the binary tree will almost always be quite well balanced. + +We will compute secondary keys as unsigned long integers, assigning +the key $(cn)\bmod 2^{32}$ to the $n$th node, where $c$ is an odd +number. This will guarantee that the secondary keys are distinct. +By choosing $c$ close to $2^{32}/\phi$, where $\phi$ is the golden +ratio $(1+\sqrt5\,)/2$, we also spread the values out in a fashion that +is unlikely to match any existing order in the data. + +@d PHICLONE 2654435769 /* $\approx 2^{32}/\phi$ */ + +@<Typedefs@>= +typedef struct node_struct { + struct node_struct *left,*right; /* children */ + byte *keyword; /* primary key */ + unsigned long rank; /* secondary key */ +} node; /* node of a treap */ + +@ We want to be able to compare two strings rapidly with respect to +lexicographic order, as defined by the |ord| table. This can be done +if one string is delimited by |'\0'| as usual, while the other is +delimited by a break character. Then we are sure to have an unequal +comparison, and the inner loop is fast. + +Here is a routine that checks to see if a word is already present in the +treap. The word is assumed to be in |buffer|, terminated by |breakchar|. +The words in the treap are terminated by nulls. The +treap is accessed by means of |root|, a pointer to its root node. + +@<Search for |buffer| in the treap; |goto found| if it's there@>= +{@+register node *p=root; + while (p) { + for (u=buffer,v=p->keyword;ord[*u]==ord[*v];u++,v++) ; + if (*v=='\0' && *u==breakchar) goto found; + if (ord[*u]<ord[*v]) p=p->left; + else p=p->right; + } +} + +@ We don't need to insert nodes into the treap as often as we need to +look words up, so we don't mind repeating the comparisons already made +when we discover that insertion is necessary. (Actually a more comprehensive +study of this tradeoff ought to be done. But not today; I am trying +here to keep the program short and sweet.) + +The insertion algorithm proceeds just as the lookup algorithm until +we come to a node whose rank is larger than the rank of the node +to be inserted. We insert the new node in its place, then split the +old node and its descendants into two subtrees that will become the +left and right subtrees of the new node. + +@<Insert the |buffer| word into the treap@>= +{@+register node *p,**q,**qq,*r; + current_rank += PHICLONE; /* unsigned addition mod $2^{32}$ */ + p=root;@+q=&root; + while (p) { + if (p->rank>current_rank) break; /* end of the first phase */ + for (u=buffer,v=p->keyword;ord[*u]==ord[*v];u++,v++) ; + if (ord[*u]<ord[*v]) q=&(p->left), p=*q; + else q=&(p->right), p=*q; + } + @<Set |r| to the address of a new node, and move |buffer| into it@>; + r->rank=current_rank; + *q=r; /* link the new node into the tree */ + @<Split subtree |p| and attach it below node |r|@>; +} + +@ @<Local...@>= +unsigned long current_rank=0; /* pseudorandom number */ + +@ At this point |p| may already be empty. If not, we can hook its +parts together easily. (A formal proof is a bit tricky, but the computer +doesn't slow down like people do when they get to a conceptually harder +part of an algorithm.) + +@<Split subtree |p| and attach it below node |r|@>= +q=&(r->left);@+qq=&(r->right); /* slots to fill in as we split the subtree */ +while (p) { + for (u=buffer,v=p->keyword;ord[*u]==ord[*v];u++,v++) ; + if (ord[*u]<ord[*v]) { + *qq=p; + qq=&(p->left); + p=*qq; + } else { + *q=p; + q=&(p->right); + p=*q; + } +} +*q=*qq=NULL; + +@ We allocate node memory dynamically, in blocks of 100 nodes at a time. +We also allocate string memory dynamically, 1000 characters at once +(in addition to space for the current string). +The variable |l| will be set to the length of the word in |buffer|. + +@d NODES_PER_BLOCK 100 +@d CHARS_PER_BLOCK 1000 +@d out_of_mem(x) {@+fprintf(stderr,"%s: Memory exhausted!\n",*argv); + return x;@+} + +@<Set |r| to the address of a new node, and move |buffer| into it@>= +if (next_node==bad_node) { + next_node=(node*)calloc(NODES_PER_BLOCK,sizeof(node)); + if (next_node==NULL) out_of_mem(-2); + bad_node=next_node+NODES_PER_BLOCK; +} +r=next_node++; +@<Move |buffer| to a new place in the string memory, and make + |r->keyword| point to it@>; + +@ @<Move |buffer| to a new place...@>= +if (next_string+l+1>=bad_string) {@+int block_size=CHARS_PER_BLOCK+l+1; + next_string=(byte*)malloc(block_size); + if (next_string==NULL) out_of_mem(-3); + bad_string=next_string+block_size; +} +r->keyword=next_string; +for (u=buffer,v=next_string;ord[*u]>0;u++,v++) *v=*u; +*v='\0'; +next_string=v+1; + +@ We had better define the variables we've been assuming in these +storage allocation routines. + +@<Local variables@>= +node *next_node=NULL, *bad_node=NULL; +byte *next_string=NULL, *bad_string=NULL; +node *root=NULL; +byte *buffer; +int l; /* length of current string in |buffer| */ + +@ The mechanisms for sorting the input words are now all in place. +We merely need to invoke them at the right times. + +@<Sort the input into memory@>= +buffer=(byte*)malloc(max_length+1); +if (buffer==NULL) out_of_mem(-5); +while (1) { + @<Set |buffer| to the next word from |stdin|; |goto done| if file ends@>; + if (l) { + @<Search for |buffer| in the treap; |goto found| if it's there@>; + @<Insert the |buffer| word into the treap@>; + found:; + } +} +done:; + +@ @<Set |buffer| to the next word from |stdin|...@>= +u=buffer;@+l=0; +while (l<max_length) { + c=getchar(); + if (c==EOF) { + if (ferror(stdin)) { + fprintf(stderr,"%s: File read error on standard input!\n",*argv); + return -6; + } + goto done; /* end of file; the current word, if any, is discarded */ + } + if (ord[c]<=0) { + if (ord[c]<0) break; + } else { + *u++=(byte)c; + l++; + } +} +*u=breakchar; + +@ At the end we want to traverse the treap in symmetric order, so that +we see its words in alphabetic order. We might as well destroy the +treap structure as we do this. During this phase, |root| will point +to a stack of nodes that remain to be visited (followed by traversal +of their right subtrees). + +@<Output all input words that aren't in dictionaries@>= +if (root!=NULL) {@+register node *p,*q; + p=root; + root=NULL; + while (1) { + while (p->left!=NULL) { + q=p->left; + p->left=root; /* |left| links are now used for the stack */ + root=p; + p=q; + } +visit: @<Output |p->keyword|, if it's not in the dictionaries@>; + if (p->right==NULL) { + if (root==NULL) break; /* the stack is empty, we're done */ + p=root; + root=root->left; /* pop the stack */ + goto visit; + } else p=p->right; + } +} + +@* The dictionaries. So now all we have to do is provide a mechanism +for reading the words in the dictionaries. The dictionaries are sorted, +and by now the input words have been sorted too. +So we need only scan through the +dictionaries once; we'll try to zoom through as quickly as possible. + +First we need data structures. There will be an array of pointers to filenodes, +for all dictionary files currently open. Each filenode will contain +a buffer of size |BUFSIZ+1| for raw input bytes not yet scanned, +as well as a buffer of size |MAX_LENGTH_LIMIT+1| for the current word +being considered. + +@<Typedefs@>= +typedef struct filenode_struct { + struct filenode_struct *link; /* pointer to next open file */ + FILE *dfile; /* dictionary file */ + byte buf[BUFSIZ+1], curword[MAX_LENGTH_LIMIT+1]; + byte *pos; /* current position in |buf| */ + byte *limit; /* end of input bytes in |buf| */ + byte *endword; /* the first break character in |curword| */ +} filenode; + +@ @<Allocate data structures...@>= +if (targc) { + curfile=(filenode*)calloc(targc,sizeof(filenode)); + if (curfile==NULL) out_of_mem(-7); + for (f=curfile;f<curfile+targc-1;f++) f->link=f+1; + f->link=curfile; /* circular linking */ +} else curfile=NULL; + +@ @<Local...@>= +filenode *curfile; /* current filenode of interest */ +filenode *f; /* temporary register for filenode list processing */ + +@ @<Open the dictionary file named |*targv|@>= +{ + curfile->dfile=fopen((char*)*targv,"r"); + if (curfile->dfile==NULL) { + fprintf(stderr,"%s: Can't open dictionary file %s!\n",*argv,(char*)*targv); + return -8; + } + curfile->pos=curfile->limit=curfile->buf; /* |buf| is empty */ + curfile->buf[0]='\0'; + curfile->endword=curfile->curword; /* |curword| is empty too */ + curfile->curword[0]=breakchar; + curfile=curfile->link; /* move to next filenode */ +} + +@ We will implicitly merge the dictionaries together by using a brute force +scheme that works fine when there are only a few of them. Namely, +|curfile| will point to a file having the currently smallest +current word. To get to the next word of the merge, we advance to the +next word in that file, comparing it with the current words of the +other files to see if |curfile| should switch to one of them. +When we get to the end of a file, its filenode simply leaves the circular +list. Eventually the list will be empty, and we will set |curfile| to +|NULL|; we will then have seen all the dictionary words in order. + +@ @<Output |p->keyword|, if it's not in the dictionaries@>= +while (curfile!=NULL) { + for (u=p->keyword,v=curfile->curword;ord[*u]==ord[*v];u++,v++) ; + if (*u=='\0' && *v==breakchar) goto word_done; + /* we found it in the dictionary */ + if (ord[*u]<ord[*v]) break; /* we didn't find it */ + @<Advance to the next dictionary word@>; +} +@<Print |p->keyword| and |breakchar| on |stdout|@>@; +word_done:; + +@ @<Print |p->keyword| and |breakchar| on |stdout|@>= +for (u=p->keyword;*u;u++) putchar(*u); +putchar(breakchar); + +@ @<Advance...@>= +@<Read a new word into |curfile->curword|, as fast as you can@>; +@<Adjust |curfile|, if necessary, to point to a file with minimal + |curword|@>; + +@ The dictionaries are supposed to be in order, and they shouldn't +contain nulls. But if they fail to meet these criteria, we don't want +{\tt wordtest} to crash; it should just run more slowly and/or more +peculiarly. + +The logic of the code here removes null characters, at the cost of speed. +If the dictionary contains words out of order, say $\alpha>\beta$ where +$\alpha$ precedes $\beta$ in the file, the effect will be as if $\beta$ +were not present. (In particular, if the dictionary would happen to have a null +word because of a break character inserted by our |max_length| logic, +that null word would cause no harm, because a null word is always less than +any nonnull word.) + +A null character always appears in |curfile->limit|. + +@<Read a new word into |curfile->curword|...@>= +v=curfile->curword; +l=max_length; /* here |l| represents max characters to put in |curword| */ +while (1) {@+register byte *w=curfile->limit; + u=curfile->pos; + if (u+l>=w) + while (ord[*u]>0) *v++=*u++; /* this is the inner loop */ + else { + w=u+l; + c=*w; + *w='\0'; /* temporarily store a null to avoid overlong string */ + while (ord[*u]>0) *v++=*u++; /* this too is the inner loop */ + *w=c; /* restore the damaged byte */ + } + if (ord[*u]<0) { + curfile->pos=u+1; /* good, we found the next break character */ + break; + } + l-=u-curfile->pos; + if (l==0) { /* |max_length| reached */ + curfile->pos=u; + break; + } + if (u==w) { /* we're at |curfile->limit| */ + @<Refill |curfile->buf|; or remove the current file from the + circular list and |goto update_done|, if it has ended@>; + } else curfile->pos=u+1; /* bypass a null character in the dictionary */ +} +curfile->endword=v; +*v=breakchar; +update_done:; + +@ @<Refill |curfile->buf|...@>= +if (ferror(curfile->dfile)) { + fprintf(stderr,"%s: File read error on dictionary file!\n",*argv); + return -9; +} +if (feof(curfile->dfile)) { + f=curfile->link; + if (f==curfile) curfile=NULL; /* the last dictionary file has ended */ + else { + while (f->link!=curfile) f=f->link; + f->link=curfile->link; /* remove a filenode from the circular list */ + curfile=f; /* and point to one of the remaining filenodes */ + } + goto update_done; +} +curfile->limit=curfile->buf+fread(curfile->buf,1,BUFSIZ,curfile->dfile); +*curfile->limit='\0'; +curfile->pos=curfile->buf; + +@ @<Adjust |curfile|, if necessary...@>= +if (curfile!=NULL) {@+filenode *sentinel=curfile; + for (f=curfile->link;f!=sentinel;f=f->link) + @<Change |curfile| to |f| if |f->curword<curfile->curword|@>; +} + +@ @<Change |curfile| to |f| if |f->curword<curfile->curword|@>= +{ + *f->endword='\0'; + for (u=f->curword,v=curfile->curword;ord[*u]==ord[*v];u++,v++) ; + if (ord[*u]<ord[*v]) curfile=f; + *f->endword=breakchar; +} + +@* Index. Here is a list of the identifiers used by {\tt wordtest}, +showing the sections in which they appear, underlined at points +of definition. diff --git a/web/c_cpp/cweb/examples/xlib_types.w b/web/c_cpp/cweb/examples/xlib_types.w new file mode 100644 index 0000000000..c454c6f2ed --- /dev/null +++ b/web/c_cpp/cweb/examples/xlib_types.w @@ -0,0 +1,92 @@ +@q This file tells CWEAVE to treat X11/Xlib types as reserved words. @> + +@s XExtData int +@s XExtCodes int +@s _XExtension int +@s XPixmapFormatValues int +@s XGCValues int +@s GC int +@s Visual int +@s Depth int +@s Screen int +@s ScreenFormat int +@s XSetWindowAttributes int +@s XWindowAttributes int +@s XHostAddress int +@s XImage int +@s XWindowChanges int +@s XColor int +@s XSegment int +@s XPoint int +@s XRectangle int +@s XArc int +@s XKeyboardControl int +@s XKeyboardState int +@s XTimeCoord int +@s XModifierKeymap int +@s Display int +@s XKeyEvent int +@s XKeyPressedEvent int +@s XKeyReleasedEvent int +@s XButtonEvent int +@s XButtonPressedEvent int +@s XButtonReleasedEvent int +@s XMotionEvent int +@s XPointerMovedEvent int +@s XCrossingEvent int +@s XEnterWindowEvent int +@s XLeaveWindowEvent int +@s XFocusChangeEvent int +@s XFocusInEvent int +@s XFocusOutEvent int +@s XKeymapEvent int +@s XExposeEvent int +@s XGraphicsExposeEvent int +@s XNoExposeEvent int +@s XVisibilityEvent int +@s XCreateWindowEvent int +@s XDestroyWindowEvent int +@s XUnmapEvent int +@s XMapEvent int +@s XMapRequestEvent int +@s XReparentEvent int +@s XConfigureEvent int +@s XGravityEvent int +@s XResizeRequestEvent int +@s XConfigureRequestEvent int +@s XCirculateEvent int +@s XCirculateRequestEvent int +@s XPropertyEvent int +@s XSelectionClearEvent int +@s XSelectionRequestEvent int +@s XSelectionEvent int +@s XColormapEvent int +@s XClientMessageEvent int +@s XMappingEvent int +@s XErrorEvent int +@s XAnyEvent int +@s XEvent int +@s _XQEvent int +@s XCharStruct int +@s XFontProp int +@s XFontStruct int +@s XTextItem int +@s XChar2b int +@s XTextItem16 int +@s XEDataObject int +@s XErrorHandler int +@s XIOErrorHandler int +@s XID int +@s Window int +@s Drawable int +@s Font int +@s Pixmap int +@s Cursor int +@s Colormap int +@s GContext int +@s KeySym int +@s Mask int +@s Atom int +@s VisualID int +@s Time int +@s KeyCode int diff --git a/web/c_cpp/cweb/examples/xview_types.w b/web/c_cpp/cweb/examples/xview_types.w new file mode 100644 index 0000000000..168551e884 --- /dev/null +++ b/web/c_cpp/cweb/examples/xview_types.w @@ -0,0 +1,274 @@ +@q This file tells CWEAVE to treat XView types as reserved words. @> + +@s Attr_attribute int +@s Attr_avlist int +@s Attr_base_cardinality int +@s Attr_base_type int +@s Attr_cu_type int +@s Attr_generic int +@s Attr_list_ptr_type int +@s Attr_list_type int +@s Attr_pkg int +@s Attr_union int +@s Canvas int +@s Canvas_attribute int +@s Canvas_paint_attribute int +@s Canvas_paint_window int +@s Canvas_view int +@s Canvas_view_attribute int +@s Cms int +@s Cms_attribute int +@s Cms_type int +@s Cursor_attribute int +@s Cursor_drag_state int +@s Cursor_drag_type int +@s Defaults_pairs int +@s Destroy_status int +@s Dnd int +@s DndDragType int +@s Dnd_attribute int +@s Drag_drop int +@s Drawable_attr int +@s Drawable_attribute int +@s Drop_site_item int +@s Enumerator int +@s Error_attr int +@s Error_attribute int +@s Error_layer int +@s Error_severity int +@s Event int +@s Firm_event int +@s Font_attribute int +@s Font_string_dims int +@s Font_type int +@s Frame int +@s Frame_accelerator int +@s Frame_attribute int +@s Frame_cmd int +@s Frame_cmd_pin_state int +@s Frame_focus_direction int +@s Frame_help int +@s Frame_props int +@s Fullscreen int +@s Fullscreen_attr int +@s Fullscreen_attribute int +@s Fullscreen_grab_mode int +@s Icon int +@s Icon_attribute int +@s Inputmask int +@s Kbd_cmds_value int +@s Menu int +@s Menu_attribute int +@s Menu_class int +@s Menu_feedback int +@s Menu_generate int +@s Menu_item int +@s Notice_attribute int +@s Notify_arg int +@s Notify_client int +@s Notify_copy int +@s Notify_dump_type int +@s Notify_error int +@s Notify_event int +@s Notify_event_type int +@s Notify_func int +@s Notify_release int +@s Notify_signal_mode int +@s Notify_value int +@s Openwin int +@s Openwin_attribute int +@s Openwin_split_direction int +@s Panel int +@s Panel_abbrev_menu_button_item int +@s Panel_attr int +@s Panel_attribute int +@s Panel_attribute_value int +@s Panel_button_item int +@s Panel_choice_item int +@s Panel_drop_target_item int +@s Panel_gauge_item int +@s Panel_item int +@s Panel_item_type int +@s Panel_list_item int +@s Panel_list_mode int +@s Panel_list_op int +@s Panel_message_item int +@s Panel_multiline_text_item int +@s Panel_numeric_text_item int +@s Panel_ops int +@s Panel_paint_window int +@s Panel_setting int +@s Panel_slider_item int +@s Panel_status int +@s Panel_text_item int +@s Panel_view int +@s Pixwin int +@s Pw_pixel_cache int +@s Rect int +@s Rectlist int +@s Rectnode int +@s Screen_attr int +@s Screen_attribute int +@s Scroll_motion int +@s Scrollable_panel int +@s Scrollbar int +@s Scrollbar_attr int +@s Scrollbar_attribute int +@s Scrollbar_motion int +@s Scrollbar_setting int +@s Sel_cmpat_info int +@s Sel_prop_info int +@s Selection int +@s Selection_attr int +@s Selection_attribute int +@s Selection_item int +@s Selection_owner int +@s Selection_requestor int +@s Seln_access int +@s Seln_attribute int +@s Seln_client int +@s Seln_file_info int +@s Seln_function int +@s Seln_function_buffer int +@s Seln_functions_state int +@s Seln_holder int +@s Seln_holders_all int +@s Seln_inform_args int +@s Seln_level int +@s Seln_rank int +@s Seln_replier_data int +@s Seln_request int +@s Seln_requester int +@s Seln_response int +@s Seln_result int +@s Seln_state int +@s Server_attr int +@s Server_attribute int +@s Server_image int +@s Server_image_attribute int +@s Termsw int +@s Termsw_attribute int +@s Termsw_mode int +@s Termsw_view int +@s Text int +@s Textsw int +@s Textsw_action int +@s Textsw_attribute int +@s Textsw_enum int +@s Textsw_expand_status int +@s Textsw_filter_attribute int +@s Textsw_filter_command int +@s Textsw_index int +@s Textsw_mark int +@s Textsw_menu_cmd int +@s Textsw_opaque int +@s Textsw_status int +@s Textsw_view int +@s Tty int +@s Tty_attribute int +@s Tty_view int +@s Ttysubwindow int +@s Vuid_addr_probe int +@s Vuid_device int +@s WM_Direction int +@s Win_enum_node int +@s Win_enum_result int +@s Win_tree_layer int +@s Window_attr int +@s Window_attribute int +@s Window_handle int +@s Window_input_event int +@s Window_layout_op int +@s Window_rescale_rect_obj int +@s Window_rescale_state int +@s Window_type int +@s Ws_scale int +@s Ws_scale_list int +@s Xv_Color_info int +@s Xv_Cursor int +@s Xv_Drawable int +@s Xv_Font int +@s Xv_Notice int +@s Xv_Screen int +@s Xv_Server int +@s Xv_Window int +@s Xv_attr int +@s Xv_attribute int +@s Xv_base int +@s Xv_canvas int +@s Xv_canvas_pw int +@s Xv_canvas_view int +@s Xv_cms_struct int +@s Xv_cmsdata int +@s Xv_cursor int +@s Xv_cursor_struct int +@s Xv_dnd_struct int +@s Xv_drag_drop int +@s Xv_drawable int +@s Xv_drawable_struct int +@s Xv_drop_site int +@s Xv_drop_site_struct int +@s Xv_embedding int +@s Xv_focus_rank int +@s Xv_font int +@s Xv_font_struct int +@s Xv_frame_base int +@s Xv_frame_class int +@s Xv_frame_cmd int +@s Xv_frame_help int +@s Xv_frame_props int +@s Xv_fullscreen int +@s Xv_generic_attr int +@s Xv_generic_attribute int +@s Xv_generic_struct int +@s Xv_icon int +@s Xv_icon_header_info int +@s Xv_item int +@s Xv_menu int +@s Xv_menu_item int +@s Xv_Notice int +@s Xv_notice int +@s Xv_notice_struct int +@s Xv_object int +@s Xv_opaque int +@s Xv_openwin int +@s Xv_panel int +@s Xv_panel_ambtn int +@s Xv_panel_button int +@s Xv_panel_choice int +@s Xv_panel_drop int +@s Xv_panel_extension_item int +@s Xv_panel_gauge int +@s Xv_panel_list int +@s Xv_panel_message int +@s Xv_panel_multiline_text int +@s Xv_panel_num_text int +@s Xv_panel_or_item int +@s Xv_panel_slider int +@s Xv_panel_text int +@s Xv_panel_view int +@s Xv_pkg int +@s Xv_Screen int +@s Xv_screen int +@s Xv_screen_struct int +@s Xv_scrollbar int +@s Xv_sel int +@s Xv_sel_item int +@s Xv_sel_owner int +@s Xv_sel_requestor int +@s Xv_Server int +@s Xv_server int +@s Xv_server_image int +@s Xv_server_struct int +@s Xv_singlecolor int +@s Xv_termsw int +@s Xv_termsw_view int +@s Xv_textsw int +@s Xv_textsw_view int +@s Xv_tty int +@s Xv_tty_view int +@s Xv_window int +@s Xv_window_struct int +@s Xv_xrectlist int +@s icon_header_handle int +@s icon_header_object int |