diff options
author | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
commit | e0c6872cf40896c7be36b11dcc744620f10adf1d (patch) | |
tree | 60335e10d2f4354b0674ec22d7b53f0f8abee672 /web/knit |
Initial commit
Diffstat (limited to 'web/knit')
-rw-r--r-- | web/knit/README | 98 | ||||
-rw-r--r-- | web/knit/demo.ch1 | 13 | ||||
-rw-r--r-- | web/knit/demo.ch2 | 12 | ||||
-rw-r--r-- | web/knit/demo.ch3 | 20 | ||||
-rw-r--r-- | web/knit/demo.web | 31 | ||||
-rw-r--r-- | web/knit/knit.ch | 909 | ||||
-rw-r--r-- | web/knit/origin | 15 | ||||
-rw-r--r-- | web/knit/twist.ch | 1138 |
8 files changed, 2236 insertions, 0 deletions
diff --git a/web/knit/README b/web/knit/README new file mode 100644 index 0000000000..c56397aa5a --- /dev/null +++ b/web/knit/README @@ -0,0 +1,98 @@ +% This is README +The PATCHWORK system consists of two programs + KNIT a version of TANGLE that can handle multiple changefiles + TWIST a version of WEAVE that can handle multiple changefile +and + PATHCHMAC.TEX a modified version of WEBMAC.TEX which must be used + for TeX'ing the output of TWIST. + +To install the system you should proceed as follows. (The description +is for SUN UNIX but an implementation on other UNIX systems or even +non-UNIX systems should be possible without major problems.) + +1. Make a new directory with the files + knit.ch twist.ch patchmac.tex + demo.web demo.ch1 demo.ch2 demo.ch3 + and copy (temporarily) also + tangle.web weave.web + which you will surely have already. + +2. Tangle tangle.web with knit.ch giving you tangle.p: + tangle tangle.web knit.ch + +3. Rename tangle.p to knit.p and compile it. + mv tangle.p. knit.p; pc -o knit knit.p + +4. If your compiler complains you should modify knit.ch (after + you have saved the original version). All the necessary modifications + should be found in your local tangle.ch. Then start at step 2 again. + (You should record all changes to knit.ch in a separate changefile + which you might call knit.ch.local. If you have successfully installed + knit and twist later on, you can reproduce knit from tangle with + the original (system-independent) knit.ch and your (system-dependent) + knit.ch.local, e.g., + knit tangle.web knit.ch knit.ch.local + For obvious reasons, the orinal knit.ch is not completely system- + independent, but it is a version with works for SUN UNIX. However, + the system-dependencies are rather small.) + +5. Repeat steps 2 to 4 correspondingly for weave and twist: + tangle weave.web twist.ch + mv weave.p twist.p; pc -o twist twist.p + +6. You now have a first version of the PATCHMAC system and might want + to test how it works: + ln demo.web webfile + ln demo.ch1 chfile1 + ln demo.ch2 chfile2 + ln demo.ch3 chfile3 + knit + mv Pascalfile demo.p; pc -o demo demo.p + demo + and you should see on your terminal + I am line1 from changefile1 + I am line2 from changefile1 + I am line3 from changefile1 + I am line4 from changefile1 + I am line3 + I am line2 from changefile3 + I am line3 from changefile3 + I am line7 + I am line8 + I am line9 + I am line2 from changefile2 + I am line3 from changefile2 + I am line4 from changefile3 + I am line12 + Afterwards you could run twist and TeX it's output: + twist + mv texfile demo.tex; tex demo [print or preview it] + +7. As you will have noticed in step 6, these versions of twist + and knit are rather simple and do not allow the usual parsing + of the command line as your tangle and weave will probably do, + i.e., the file names for twist and knit have to be + webfile chfile1 chfile2 chfile3 + Since you will probably not like this you could write, as a first + exercise, two changefiles, say + knit.ch.args twist.ch.args + which implement the parsing of the arguments in the command line. + With + ln tangle.web webfile + ln knit.ch chfile1 + ln knit.ch.args chfile2 + knit + mv Pascalfile knit.p + pc -o knit knit.p + (and a similar procedure for twist) you then will get a version + of knit (and twist) which allows the usual usuage: + knit name_of_webfile name_of_first_cf name_of_second_cf ... + +Wolfgang Appelt +GMD +Schlo\ss Birlinghoven +Postfach 1240 +D-5205 Sankt Augustin +Germany FR + +19 October 1988 diff --git a/web/knit/demo.ch1 b/web/knit/demo.ch1 new file mode 100644 index 0000000000..ccb55e36cc --- /dev/null +++ b/web/knit/demo.ch1 @@ -0,0 +1,13 @@ +% This is demo.ch1 +@x + writeln('I am line1'); +@y + writeln('I am line1 from changefile1'); + writeln('I am line2 from changefile1'); + writeln('I am line3 from changefile1'); +@z +@x + writeln('I am line2'); +@y + writeln('I am line4 from changefile1'); +@z diff --git a/web/knit/demo.ch2 b/web/knit/demo.ch2 new file mode 100644 index 0000000000..07844284c5 --- /dev/null +++ b/web/knit/demo.ch2 @@ -0,0 +1,12 @@ +% This is demo.ch2 +@x + writeln('I am line5'); +@y + writeln('I am line1 from changefile2'); +@z +@x + writeln('I am line10'); +@y + writeln('I am line2 from changefile2'); + writeln('I am line3 from changefile2'); +@z diff --git a/web/knit/demo.ch3 b/web/knit/demo.ch3 new file mode 100644 index 0000000000..94f014547b --- /dev/null +++ b/web/knit/demo.ch3 @@ -0,0 +1,20 @@ +% This is demo.ch3 +@x + writeln('I am line2'); + writeln('I am line3'); +@y + writeln('I am line1 from changefile3'); +@z +@x + writeln('I am line4'); + writeln('I am line5'); + writeln('I am line6'); +@y + writeln('I am line2 from changefile3'); + writeln('I am line3 from changefile3'); +@z +@x + writeln('I am line11'); +@y + writeln('I am line4 from changefile3'); +@z diff --git a/web/knit/demo.web b/web/knit/demo.web new file mode 100644 index 0000000000..d5096b6df7 --- /dev/null +++ b/web/knit/demo.web @@ -0,0 +1,31 @@ +% This is demo.web +@* Demo. This is just a short example to demonstrate the KNIT and TWIST +program. +@p +program demo(input,output); +begin + @<Write some output@> + @<And some more@> + @<And more lines@> +end. + +@ This is the second module. +@<Write some output@>== + writeln('I am line1'); + writeln('I am line2'); + writeln('I am line3'); + +@ This is the third module. +@<And some more@>= + writeln('I am line4'); + writeln('I am line5'); + writeln('I am line6'); + writeln('I am line7'); + writeln('I am line8'); + +@ And this the last one. +@<And more lines@>= + writeln('I am line9'); + writeln('I am line10'); + writeln('I am line11'); + writeln('I am line12'); diff --git a/web/knit/knit.ch b/web/knit/knit.ch new file mode 100644 index 0000000000..95288221fa --- /dev/null +++ b/web/knit/knit.ch @@ -0,0 +1,909 @@ +% This is knit.ch +This is a changefile for TANGLE which produces KNIT, a version of TANGLE +which allows multiple changefiles (see TUGBoat Vol.7, No. 1, pg. 20-21). +@x We modify the text +@* Introduction. +This program converts a \.{WEB} file to a \PASCAL\ file. It was written +by D. E. Knuth in September, 1981; a somewhat similar {\mc SAIL} program had +been developed in March, 1979. Since this program describes itself, a +bootstrapping process involving hand-translation had to be used to get started. +@y +\def\title{KNIT} +\def\contentspagenumber{1} % should be odd +\def\topofcontents{\null\vfill + \titlefalse % include headline on the contents page + \def\rheader{\mainfont Appendix E\hfil \contentspagenumber} + \centerline{\titlefont The {\ttitlefont KNIT} processor} + \vskip 15pt + \centerline{(Version 2.6)} + \vfill} +\pageno=\contentspagenumber \advance\pageno by 1 +@* Introduction. +This program converts a \.{WEB} file to a \PASCAL\ file. Several +changes have been made by GMD, to allow \.{TANGLE} to handle more +than one |change_file|. It is basically the original \.{TANGLE} written by +D.E. Knuth, but we'll name this \.{TANGLE}--Version \.{KNIT} to distinguish it +from the original one. The program was created by writing a |change_file| +modifying |tangle.web|. To keep that |change_file| as small as possible we +didn't change the name ``\.{TANGLE}'' througout the documentation. + +\bigskip +If you find a bug in the program you should send a message to: + +{\obeylines\parskip=0pt + W.Appelt, K.Horn + GMD Gesellschaft f\"ur Mathematik und Datenverarbeitung + Schlo\ss\ Birlinghoven + Postfach 1240 + D-5205 Sankt Augustin + + West-Germany + +} +\bigskip + +The original version was written +by D. E. Knuth in September, 1981; a somewhat similar {\mc SAIL} program had +been developed in March, 1979. Since this program describes itself, a +bootstrapping process involving hand-translation had to be used to get started. +@z + +@x (1) we modify the banner line +The ``banner line'' defined here should be changed whenever \.{TANGLE} +is modified. + +@d banner=='This is TANGLE, Version 2.6' +@y +The ``banner line'' defined here should be changed whenever \.{KNIT} +is modified. +@d banner=='This is KNIT based on TANGLE, Version 2.6, knitted by GMD' +@d term_in==input +@d term_out==output +@z + +@x (2) Now we allow three change-files +@ The program begins with a fairly normal header, made up of pieces that +@^system dependencies@> +will mostly be filled in later. The \.{WEB} input comes from files |web_file| +and |change_file|, the \PASCAL\ output goes to file |Pascal_file|, +and the string pool output goes to file |pool|. +@y +@ Changes have been made to allow more than one |change_file|. +In this \.{KNIT}--version three |change_files| are used. The necessary new +definitions are inserted quite below this explanation, so don't be +astonished to find a macro-definition in this module. +Changing the number of |change_files| only requires updates in this +module. + +The |change_files| have descending priorities. That means, if two +|change_files| want to modify the same line in a |web_file| , there will be a +warning and the |change_file| with the lower priority will be advanced +to the next section without influencing the input. If a |change_file| wants +to modify lines within the |web_file_| which are currently already changed +by another |change_file| these changes are ignored. +A warning will be generated. + +First you should examine this little example of \.{KNIT} with three +|change_files|: + +\bigskip +Input files: +$$\vbox{{\tt +\halign{& +\quad#\hfill\quad&\quad#\hfill\quad&\quad#\hfill\quad&\quad#\hfill\quad\cr +|web_file| & |ch_file_1| & |ch_file_2| & |ch_file_3| \cr +line1 & @@x & @@x & @@x \cr +line2 & line1 & line5 & line1 \cr +line3 & @@y & @@y & line2 \cr +line4 & line1from1 & line5from2 & @@y \cr +line5 & line2from1 & @@z & line1from3 \cr +line6 & line3from1 & @@x & @@z \cr +line7 & @@z & line10 & @@x \cr +line8 & *EOF* & @@y & line4 \cr +line9 & & line10from2 & line5 \cr +line10 & & line11from2 & line6 \cr +line11 & & @@z & @@y \cr +line12 & & *EOF* & line4from3 \cr +*EOF* & & & line5from3 \cr + & & & @@z \cr + & & & *EOF* \cr +}} +} +$$ + +The result will be that the program will process the following lines for +output: + +{\tt\obeylines +***WARNING... (ch\_file\_3 will be advanced) +line1from1 +line2from1 +line3from1 +line2 +line3 +***WARNING... (ch\_file\_2 will be advanced) +line4from3 +line5from3 +line7 +line8 +line9 +line10from2 +line11from2 +line11 +line12 +*EOF* +} + +Now we want to explain the macros defined in this module. They serve to +avoid arrays of files since some PASCAL-compilers can't +handle arrays of files. Behind this we find the main-program's skeleton. + +The program begins with a fairly normal header, made up of pieces that +@^system dependencies@> +will mostly be filled in later. The \.{WEB} input comes from |web_file| +and the |change_files|, the \PASCAL\ output goes to file |pascal_file|, +and the string pool output goes to file |pool|. +@z + +@x we have to split the change file to allow system-dependent changes +@d end_of_TANGLE = 9999 {go here to wrap it up} +@y +@d end_of_TANGLE = 9999 {go here to wrap it up} + +@d ch_max==3 {indicates the maximum number of |change_files| with descending +priority. If this number is modified the following three definitions and the +program statement must be updated as well} + +@d input_ch(#) == case # of + 1: ch_not_eof:=input_ln(ch_file_1); + 2: ch_not_eof:=input_ln(ch_file_2); + 3: ch_not_eof:=input_ln(ch_file_3);end + +@d reset_ch(#) == case # of + 1: reset(ch_file_1); + 2: reset(ch_file_2); + 3: reset(ch_file_3);end + +@d declaration_ch_files ==@/ + @! ch_file_1:text_file; + @! ch_file_2:text_file; + @! ch_file_3:text_file +@z + +@x (2) we modify the program header and define term_in and term_out +program TANGLE(@!web_file,@!change_file,@!Pascal_file,@!pool); +@y +program KNIT(@!term_in,@!term_out,@!web_file,@!ch_file_1,@!ch_file_2, + @!ch_file_3,@!Pascal_file,@!pool); +@z +@x Default in case statement is otherwise. (7) +@d othercases == others: {default for cases not listed explicitly} +@y +@d othercases == otherwise {default for cases not listed explicitly} +@z +@x term_out (output) must not be redefined. (20) +@!term_out:text_file; {the terminal as an output file} +@y +@z + +@x term_out (output) must not be opened. (21) +rewrite(term_out,'TTY:'); {send |term_out| output to the terminal} +@y +@z + +@x break is replaced by writeln (22) +@d update_terminal == break(term_out) {empty the terminal output buffer} +@y +@d update_terminal == write_ln(term_out) {empty the terminal output buffer} +@z + +@x (23) we declare the maximum number (ch_max) of change-files +@ The main input comes from |web_file|; this input may be overridden +by changes in |change_file|. (If |change_file| is empty, there are no changes.) + +@<Globals...@>= +@!web_file:text_file; {primary input} +@!change_file:text_file; {updates} +@y +@ The main input comes from |web_file|; this input may be overridden +by changes in the |change_files|. The |change_files| have descending +priority. Only one change may be done to the same text in the |web_file|. +(If all |change_files| are empty, there are no changes.) +Whenever the number of |change_files| is increased the correspondent +file-declarations have to be modified. Since we have written a macro +for this in module~2 of \.{KNIT}, no more changes are necessary here. + +@<Globals...@>= +@!web_file:text_file; {primary input} +declaration_ch_files; {look for the macro definitions at the beginning} +@z + +@x (24) we have to reset all change-files explicitely +@ The following code opens the input files. Since these files were listed +in the program header, we assume that the \PASCAL\ runtime system has +already checked that suitable file names have been given; therefore no +additional error checking needs to be done. +@^system dependencies@> + +@p procedure open_input; {prepare to read |web_file| and |change_file|} +begin reset(web_file); reset(change_file); +end; +@y +@ The following code opens the input files. Since these files were listed +in the program header, we assume that the \PASCAL\ runtime system has +already checked that suitable file names have been given; therefore no +additional error checking needs to be done. +@^system dependencies@> +The macro |reset_ch(i)| resets the |i-th change_file|. +This is done by a macro, +so changing the number of |change_files| only makes necessary a modification +in the macro written in module~2 of the \.{KNIT}--program. + +@p procedure open_input; {prepare to read |web_file| and |change_file|} +var i:integer; {loop variable for processing the |change_files|} +begin reset(web_file); for i:=1 to ch_max do reset_ch(i);end; +@z + +@x (32) We want to specify the current change-file +@ The error locations during Phase I can be indicated by using the global +variables |loc|, |line|, and |changing|, which tell respectively the first +unlooked-at position in |buffer|, the current line number, and whether or not +the current line is from |change_file| or |web_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@>= +begin if changing then print('. (change file ')@+else print('. ('); +print_ln('l.', line:1, ')'); +if loc>=limit then l:=limit else l:=loc; +for k:=1 to l do + if buffer[k-1]=tab_mark then print(' ') + else print(xchr[buffer[k-1]]); {print the characters already read} +new_line; +for k:=1 to l do print(' '); {space out the next line} +for k:=l+1 to limit do print(xchr[buffer[k-1]]); {print the part not yet read} +print(' '); {this space separates the message from future asterisks} +end +@y +@ The error locations during Phase I can be indicated by using the global +variables |loc|, |line|, and |changing|, which tell respectively the first +unlooked-at position in |buffer|, the current line number, and whether or not +the current line is from |web_file| or from one of the |change_files| +and if so, from which one. The variable |ch_act| gives the index of +the current |change_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@>= +begin if changing then print('. (change file ',ch_act:1,' ')@+ +else print('. ('); +print_ln('l.', line:1, ')'); +if loc>=limit then l:=limit else l:=loc; +for k:=1 to l do + if buffer[k-1]=tab_mark then print(' ') + else print(xchr[buffer[k-1]]); {print the characters already read} +new_line; +for k:=1 to l do print(' '); {space out the next line} +for k:=l+1 to limit do print(xchr[buffer[k-1]]); {print the part not yet read} +print(' '); {this space separates the message from future asterisks} +end +@z +@x No translation to uppercase. (58) + begin if buffer[i]>="a" then chopped_id[s]:=buffer[i]-@'40 +@y + begin if buffer[i]>="a" then chopped_id[s]:=buffer[i] +@z +@x No translation to uppercase. (63) + begin if c>="a" then c:=c-@'40; {convert to uppercase} +@y + begin if c>="a" then c:=c; {no uppercase} +@z +@x No translation to uppercase. (116) +"A",up_to("Z"): begin out_contrib[1]:=cur_char; send_out(ident,1); + end; +"a",up_to("z"): begin out_contrib[1]:=cur_char-@'40; send_out(ident,1); + end; +identifier: begin k:=0; j:=byte_start[cur_val]; w:=cur_val mod ww; + while (k<max_id_length)and(j<byte_start[cur_val+ww]) do + begin incr(k); out_contrib[k]:=byte_mem[w,j]; incr(j); + if out_contrib[k]>="a" then out_contrib[k]:=out_contrib[k]-@'40 + else if out_contrib[k]="_" then decr(k); + end; + send_out(ident,k); + end; +@y +"A",up_to("Z"): begin out_contrib[1]:=cur_char+@'40; send_out(ident,1); + end; +"a",up_to("z"): begin out_contrib[1]:=cur_char; send_out(ident,1); + end; +identifier: begin k:=0; j:=byte_start[cur_val]; w:=cur_val mod ww; + while (k<max_id_length)and(j<byte_start[cur_val+ww]) do + begin incr(k); out_contrib[k]:=byte_mem[w,j]; incr(j); + if out_contrib[k]>="a" then out_contrib[k]:=out_contrib[k] + else if out_contrib[k]="_" then decr(k); + end; + send_out(ident,k); + end; +@z + +@x (123) we define a type |ch_type| to characterise the local parameter +@* Introduction to the input phase. +We have now seen that \.{TANGLE} will be able to output the full +\PASCAL\ program, if we can only get that program into the byte memory in +the proper format. The input process is something like the output process +in reverse, since we compress the text as we read it in and we expand it +as we write it out. + +There are three main input routines. The most interesting is the one that gets +the next token of a \PASCAL\ text; the other two are used to scan rapidly past +\TeX\ text in the \.{WEB} source code. One of the latter routines will jump to +the next token that starts with `\.{@@}', and the other skips to the end +of a \PASCAL\ comment. +@y +@* Introduction to the input phase. +Now we have seen that \.{TANGLE} will be able to output the full +\PASCAL\ program, if we can only get that program into the byte memory in +the proper format. The input process is something like the output process +in reverse, since we compress the text as we read it in and we expand it +as we write it out. + +There are three main input routines. The most interesting is the one that gets +the next token of a \PASCAL\ text; the other two are used to scan rapidly past +\TeX\ text in the \.{WEB} source code. One of the latter routines will jump to +the next token that starts with `\.{@@}', and the other skips to the end +of a \PASCAL\ comment. + +In the \.{KNIT}--version of \.{TANGLE} there is more than one |change_file|. +So there are some modifications, which will be explained when they are made. +Altering the number of |change_file| only causes modifications in module~2 +of \.{KNIT} since we decided to use macros whereever +the number of |change_files| is important. + +We need a type |ch_type| to describe the local parameters. This will be done +here. + +@<Types...@>= +@!ch_type=1..ch_max; +@z + +@x (126) we expand |change_buffer|, |change_limit| and |ch_line| one dimension +@ When |changing| is |false|, the next line of |change_file| is kept in +|change_buffer[0..change_limit]|, for purposes of comparison with the next +line of |web_file|. After the change file has been completely input, we +set |change_limit:=0|, so that no further matches will be made. + +@<Globals...@>= +@!change_buffer:array[0..buf_size] of ASCII_code; +@!change_limit:0..buf_size; {the last position occupied in |change_buffer|} +@y +@ Different to the original \.{TANGLE}--version +we are able to handle more than +one |change_file|. So the variables |change_buffer| and +|change_limit| need one more dimension to indicate the current |change_file|. +The index of the current |change_file| will be kept in the variable |ch_act|. +It is set by the function |lines_dont_match|. The variable |ch_not_eof| +takes the value of the |input_ln| function when the input comes from a +|change_file|. To understand why we use this you should look up +the macro definition of |input_ch(#)| in module~2 of \.{KNIT}. +The current line numbers of the |web_file| and all the |change_files| +have to be managed. In the original version this could be done with the +macro |change_changing| defined in the previous module. \.{KNIT} has to handle +more than two files, so we use the array |ch_line| to keep the current line +numbers of all |change_files|. The variable |line| will be updated from +this array each time it is used. +When |changing| is |false|, the next lines of the +|change_files| are kept in their |change_buffers|. The length of the +|change_buffer i| is kept in |change_limit[i]|. After a |change_file| +has been completely input, we set its |change_limit = 0|, so that no +further matches will be made. + +@<Globals...@>= +@!change_buffer:array[1..ch_max,0..buf_size] of ASCII_code; +@!change_limit:array[1..ch_max] of 0..buf_size; + {the last position occupied in |change_buffer|} +@!ch_act:1..ch_max; {specifies the current |change_file|} +@!i:1..ch_max;{index used for loops} +@!ch_not_eof:boolean;{corresponds to the boolean |input_ln|} +@!ch_line:array[1..ch_max] of integer; {line refering to all |change_file|s} +@z + +@x (127) we define an additional function ch_dont_match +@ Here's a simple function that checks if the two buffers are different. + +@p function lines_dont_match:boolean; +label exit; +var k:0..buf_size; {index into the buffers} +begin lines_dont_match:=true; +if change_limit<>limit then return; +if limit>0 then + for k:=0 to limit-1 do if change_buffer[k]<>buffer[k] then return; +lines_dont_match:=false; +exit: end; +@y +@ Differing from the original version the index of the current |change_buffer| +has to be specified as a function parameter. +The function |lines_dont_match| checks if |change_buffer[i]| is equal to +the buffer filled by |web_file|. If so, the index of the |change_file| +is copied to the variable |ch_act|. The function |ch_dont_match| checks +if two |change_buffers| are equal. + +@p function lines_dont_match(i:ch_type):boolean; +label exit; +var k:0..buf_size; {index into the buffers} +begin lines_dont_match:=true; +if change_limit[i]<>limit then return; +if limit>0 then + for k:=0 to limit-1 do if change_buffer[i,k]<>buffer[k] then return; +ch_act:=i; +lines_dont_match:=false; +exit: end; +@# + +function ch_dont_match(i,j:ch_type):boolean; +label exit; +var k:0..buf_size; {index into the buffers} +begin ch_dont_match:=true; +if ((change_limit[i]=0) or (change_limit[j]=0)) then return; +if change_limit[i]<>change_limit[j] then return; +if change_limit[i]>0 then + for k:=0 to change_limit[i]-1 + do if change_buffer[i,k]<>change_buffer[j,k] then return; +ch_dont_match:=false; +exit: end; +@z + +@x (128) now we have to manage more than one change-file +@ 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=0)and not changing| if and +only if the change file is exhausted. This procedure is called only +when |changing| is true; hence error messages will be reported correctly. + +@p procedure prime_the_change_buffer; +label continue, done, exit; +var k:0..buf_size; {index into the buffers} +begin change_limit:=0; {this value will be 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|@>; +exit: end; +@y +@ Procedure |prime_the_change_buffer| prepares the addressed +|change_buffer| +for the next matching operation. Since blank lines in the change file are +not used for matching, we have |(change_limit=0)and not changing| if and +only if the change file is exhausted. This procedure is called only +when |changing| is true; hence error messages will be reported correctly. + +The procedure |pre_prime_the_change_buffer| is necessary if there are two +equal |change_buffers|. The one with the lower priority or the one which +is actually not compared with the buffer filled by |web_file| will be advanced +to the next matching position behind the @@x-line. First we have to skip over +the @@x...@@y...@@z passage before we call the original +|prime_the_change_buffer|. + +The procedure |compare_change_files| checks if the |change_buffers| of two +|change_files| are equal. If so, those with the lower priority will be +advanced. + +@p procedure prime_the_change_buffer(i:ch_type); +label continue, done, exit; +var k:0..buf_size; {index into the buffers} +begin change_limit[i]:=0; {this value will be 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|@>; +exit: end; +@# + +procedure pre_prime_the_change_buffer(i:ch_type); +label continue, done, exit; +begin change_limit[i]:=0; {this value will be used if the change file ends} +loop@+ begin line:=ch_line[i];incr(line);ch_line[i]:=line; + input_ch(i); + if not ch_not_eof then + begin err_print ('! Change file ',i:1,' ended without @@z'); + return end; + if limit < 2 then goto continue; + if ((buffer[0]="@@") and ((buffer[1]="z") or (buffer[1]="Z"))) + then goto done; +continue:end; +done: +prime_the_change_buffer(i); +exit: end; +@# + +procedure compare_change_files; +label restart; +var i,j:0..ch_max; +begin +restart: +for i:=1 to ch_max-1 do + for j:=i+1 to ch_max do + if not ch_dont_match(i,j) then + begin + print_nl(' WARNING: change_file ',i:1,' line ',ch_line[i]:4); + print_nl(' and change_file ',j:1,' line ',ch_line[j]:4, + ' refer to the same text !!!!'); + print_nl(' Change_file ',j:1,' will be advanced.');new_line; + pre_prime_the_change_buffer(j); + mark_harmless; goto restart + end; +end; +@z + +@x (129) we have to use an index for change-file +@ 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} or \.{@@z} (which would probably indicate that the change file is +fouled up). + +@<Skip over comment lines in the change file...@>= +loop@+ begin incr(line); + if not input_ln(change_file) then return; + if limit<2 then goto continue; + if buffer[0]<>"@@" then goto continue; + if (buffer[1]>="X")and(buffer[1]<="Z") then + buffer[1]:=buffer[1]+"z"-"Z"; {lowercasify} + if buffer[1]="x" then goto done; + if (buffer[1]="y")or(buffer[1]="z") then + begin loc:=2; err_print('! Where is the matching @@x?'); +@.Where is the match...@> + end; +continue: end; +done: +@y +@ While looking for a line that begins with \.{@@x} in the change file +indexed by |i|, +we allow lines that begin with \.{@@}, as long as they don't begin with +\.{@@y} or \.{@@z} (which would probably indicate that the change file is +fouled up). + +@<Skip over comment lines in the change file...@>= +loop@+ begin line:=ch_line[i];incr(line);ch_line[i]:=line; + input_ch(i); + if not ch_not_eof then return; + if limit<2 then goto continue; + if buffer[0]<>"@@" then goto continue; + if (buffer[1]>="X")and(buffer[1]<="Z") then + buffer[1]:=buffer[1]+"z"-"Z"; {lowercasify} + if buffer[1]="x" then goto done; + if (buffer[1]="y")or(buffer[1]="z") then + begin loc:=2; + err_print('! Where is the matching @@x in change-file ',i:1,' ?'); +@.Where is the match...@> + end; +continue: end; +done: +@z + +@x (130) we have to use an index using change-file +@ Here we are looking at lines following the \.{@@x}. + +@<Skip to the next nonblank line...@>= +repeat incr(line); + if not input_ln(change_file) then + begin err_print('! Change file ended after @@x'); +@.Change file ended...@> + return; + end; +until limit>0; +@y +@ Here we are looking at lines following the \.{@@x}. + +@<Skip to the next nonblank line...@>= +repeat line:=ch_line[i];incr(line);ch_line[i]:=line; + input_ch(i); + if not ch_not_eof then + begin err_print('! Change file ',i:1,' ended after @@x'); +@.Change file ended...@> + return; + end; +until limit>0; +@z + +@x (131) we have to specify the change-file by an index +@ @<Move |buffer| and |limit| to |change_buffer| and |change_limit|@>= +begin change_limit:=limit; +for k:=0 to limit do change_buffer[k]:=buffer[k]; +end +@y +@ @<Move |buffer| and |limit| to |change_buffer| and |change_limit|@>= +begin change_limit[i]:=limit; +for k:=0 to limit do change_buffer[i,k]:=buffer[k]; +end +@z + +@x (132) we have to specify and work with the actual change-file +@ The following procedure is used to see if the next change entry should +go into effect; it is called only when |changing| is false. +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|. + +@p procedure check_change; {switches to |change_file| if the buffers match} +label exit; +var n:integer; {the number of discrepancies found} +@!k:0..buf_size; {index into the buffers} +begin if lines_dont_match then return; +n:=0; +loop@+ begin change_changing; {now it's |true|} + incr(line); + if not input_ln(change_file) then + begin err_print('! Change file ended before @@y'); +@.Change file ended...@> + change_limit:=0; change_changing; {|false| again} + return; + end; + @<If the current line starts with \.{@@y}, + report any discrepancies and |return|@>; + @<Move |buffer| and |limit|...@>; + change_changing; {now it's |false|} + incr(line); + if not input_ln(web_file) then + begin err_print('! WEB file ended during a change'); +@.WEB file ended...@> + input_has_ended:=true; return; + end; + if lines_dont_match then incr(n); + end; +exit: end; +@y +@ The following procedure is used to see if the next change entry should +go into effect; it is called only when |changing| is false. +The idea is to test whether or not the current contents of |buffer| +matches the current contents of one of the |change_buffers|. +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 the current |change_file|. + +Since we use several |change_file|s we first have to examine all +|change_buffer|s. The index of the matching |change_file| is written to the +variable |ch_act| by the function |lines_dont_match|. +Each time one line of the current |change_file| is read into its +|change_buffer| this one is compared with all the other |change_buffers|. +In case of equality the latter |change_file| will be advanced by the +function |pre_prime_the_change_buffer|. + + +@p procedure check_change; {switches to |change_file| if the buffers match} +label exit,done,continue; +var n:integer; {index, the number of discrepancies found} + i:integer; {loop variable for processing the |change_files|} + temp:integer; {for temporary storage of the contents of |ch_act|} +@!k:0..buf_size; {index into the buffers} +begin +for i:= 1 to ch_max do + if not lines_dont_match(i) then goto done; + return;{no change-file matches} +done:{one change-file matches, the index has been written to ch-act} + n:=0; +loop@+ begin change_changing; {now it's |true|} + line:=ch_line[ch_act];incr(line);ch_line[ch_act]:=line; + input_ch(ch_act); + if not ch_not_eof then + begin err_print('! Change file ',ch_act:1,' ended before @@y'); +@.Change file ended...@> + change_limit[ch_act]:=0; change_changing; {|false| again} + return; + end; + @<If the current line starts with \.{@@y}, + report any discrepancies and |return|@>; + @<Move |buffer| and |limit|...@>; +{the actual change-buffer has to be compared with the other change-buffers, +the index of the actual |change_file| is stored to the variable temp} + temp:=ch_act; + for i:=1 to ch_max do + begin + if i=ch_act then goto continue; + if not ch_dont_match(i,ch_act) then + begin + print_nl(' WARNING: change_file ',i:1,' line ',ch_line[i]:4); + print_nl(' and change_file ',ch_act:1,' line ', + ch_line[ch_act]:4,' refer to the same text !!!!'); + print_nl(' Change_file ',i:1,' will be advanced.');new_line; + pre_prime_the_change_buffer(i); + end; + continue: end; + ch_act:=temp;i:=ch_act; {the actual index has to be reloaded} + change_changing; {now it's |false|} + incr(line); + if not input_ln(web_file) then + begin err_print('! WEB file ended during a change'); +@.WEB file ended...@> + input_has_ended:=true; return; + end; + if lines_dont_match(ch_act) then incr(n); + end; +exit: end; +@z + +@x (133) we specify the actual change-file in the error message +@ @<If the current line starts with \.{@@y}...@>= +if limit>1 then if buffer[0]="@@" then + begin if (buffer[1]>="X")and(buffer[1]<="Z") then + buffer[1]:=buffer[1]+"z"-"Z"; {lowercasify} + if (buffer[1]="x")or(buffer[1]="z") then + begin loc:=2; err_print('! Where is the matching @@y?'); +@.Where is the match...@> + end + else if buffer[1]="y" then + begin if n>0 then + begin loc:=2; err_print('! Hmm... ',n:1, + ' of the preceding lines failed to match'); +@.Hmm... n of the preceding...@> + end; + return; + end; + end +@y +@ @<If the current line starts with \.{@@y}...@>= +if limit>1 then if buffer[0]="@@" then + begin if (buffer[1]>="X")and(buffer[1]<="Z") then + buffer[1]:=buffer[1]+"z"-"Z"; {lowercasify} + if (buffer[1]="x")or(buffer[1]="z") then + begin loc:=2; + err_print('! Where is the matching @@y in change_file ',ch_act:1,' ?'); +@.Where is the match...@> + end + else if buffer[1]="y" then + begin if n>0 then + begin loc:=2; err_print('! Hmm... ',n:1, + ' of the preceding lines failed to match in change_file ',ch_act:1); +@.Hmm... n of the preceding...@> + end; + return; + end; + end +@z + +@x (134) to initialize we have to look at all change buffers +@ @<Initialize the input system@>= +open_input; line:=0; other_line:=0;@/ +changing:=true; prime_the_change_buffer; change_changing;@/ +limit:=0; loc:=1; buffer[0]:=" "; input_has_ended:=false; +@y +@ @<Initialize the input system@>= +open_input; line:=0; other_line:=0;@/ +for i:=1 to ch_max do ch_line[i]:=0;@/ +changing:=true; +for i:=1 to ch_max do prime_the_change_buffer(i);@/ +compare_change_files;change_changing;@/ +limit:=0; loc:=1; buffer[0]:=" "; input_has_ended:=false; +@z + +@x (135) we need a loop variable i +@ 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. + +@p procedure get_line; {inputs the next line} +label restart; +begin restart: if changing then + @<Read from |change_file| and maybe turn off |changing|@>; +if not changing then + begin @<Read from |web_file| and maybe turn on |changing|@>; + if changing then goto restart; + end; +loc:=0; buffer[limit]:=" "; +end; +@y +@ 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. + +@p procedure get_line; {inputs the next line} +label restart;var i:integer;{ loop variable for processing the change files} +ch_flag:boolean; {flag whether to use |check_change| or not} +begin restart: if changing then + @<Read from |change_file| and maybe turn off |changing|@>; +if not changing then + begin @<Read from |web_file| and maybe turn on |changing|@>; + if changing then goto restart; + end; +loc:=0; buffer[limit]:=" "; +end; +@z + +@x (136) we have to specify the actual change-file +@ @<Read from |web_file|...@>= +begin incr(line); +if not input_ln(web_file) then input_has_ended:=true +else if limit=change_limit then + if buffer[0]=change_buffer[0] then + if change_limit>0 then check_change; +end +@y +@ @<Read from |web_file|...@>= +begin incr(line); +if not input_ln(web_file) then input_has_ended:=true +else begin ch_flag:=false; + for i:=1 to ch_max do begin + if limit=change_limit[i] then + if buffer[0]=change_buffer[i,0] then + if change_limit[i]>0 then ch_flag:=true; end; + if ch_flag then check_change;end; +end +@z + +@x (137) we have to specify the change-file +@ @<Read from |change_file|...@>= +begin incr(line); +if not input_ln(change_file) then + begin err_print('! Change file ended without @@z'); +@.Change file ended...@> + buffer[0]:="@@"; buffer[1]:="z"; limit:=2; + end; +if limit>1 then {check if the change has ended} + if buffer[0]="@@" then + begin if (buffer[1]>="X")and(buffer[1]<="Z") then + buffer[1]:=buffer[1]+"z"-"Z"; {lowercasify} + if (buffer[1]="x")or(buffer[1]="y") then + begin loc:=2; err_print('! Where is the matching @@z?'); +@.Where is the match...@> + end + else if buffer[1]="z" then + begin prime_the_change_buffer; change_changing; + end; + end; +end +@y +@ @<Read from |change_file|...@>= +begin line:=ch_line[ch_act];incr(line);ch_line[ch_act]:=line; +input_ch(ch_act); +if not ch_not_eof then + begin err_print('! Change file ',ch_act:1,' ended without @@z'); +@.Change file ended...@> + buffer[0]:="@@"; buffer[1]:="z"; limit:=2; + end; +if limit>1 then {check if the change has ended} + if buffer[0]="@@" then + begin if (buffer[1]>="X")and(buffer[1]<="Z") then + buffer[1]:=buffer[1]+"z"-"Z"; {lowercasify} + if (buffer[1]="x")or(buffer[1]="y") then + begin loc:=2; err_print('! Where is the matching @@z in change_file ', + ch_act:1,' ?'); +@.Where is the match...@> + end + else if buffer[1]="z" then + begin prime_the_change_buffer(ch_act); + compare_change_files; change_changing; + end; + end; +end +@z + +@x (138) we have to specify the actual change-file +@ 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|. + +@<Check that all changes have been read@>= +if change_limit<>0 then {|changing| is false} + begin for loc:=0 to change_limit do buffer[loc]:=change_buffer[loc]; + limit:=change_limit; changing:=true; line:=other_line; loc:=change_limit; + err_print('! Change file entry did not match'); +@y +@ At the end of the program, we will tell the user if one of the +|change_file|s had a line that didn't match any relevant line in |web_file|. + +@<Check that all changes have been read@>= +for i:=1 to ch_max do begin +if change_limit[i]<>0 then {|changing| is false} + begin for loc:=0 to change_limit[i] do buffer[loc]:=change_buffer[i,loc]; + limit:=change_limit[i]; changing:=true; line:=ch_line[i]; + loc:=change_limit[i]; + ch_act:=i; + err_print('! Change_file ',i:1,' entry did not match'); +@.Change file entry did not match@> + end +@z +@x term_in (input) must not be redefined. (179) +@!term_in:text_file; {the user's terminal as an input file} +@y +@z diff --git a/web/knit/origin b/web/knit/origin new file mode 100644 index 0000000000..7e5060cb10 --- /dev/null +++ b/web/knit/origin @@ -0,0 +1,15 @@ +Date: Thu, 20 Oct 88 15:19:21 -0100 +From: "Wolfgang Appelt" <unido!gmdzi!appelt@uunet.UU.NET> +To: mackay@june.cs.washington.edu + +Dear Pierre, + +please excuse my later reply concerning the KNIT and TWIST programms +but I have been in Canada for two weeks. Except from this letter I shall +send you eight messages, each one containing a separate file. The first +line in the file tells you the name of the file. You should first have +a look at the file called README which should tell you all about the +seven other files. If you have problems please let me know + +--- Wolfgang --- + diff --git a/web/knit/twist.ch b/web/knit/twist.ch new file mode 100644 index 0000000000..39a85a03e7 --- /dev/null +++ b/web/knit/twist.ch @@ -0,0 +1,1138 @@ +% This is twist.ch +This is a changefile for WEAVE which produces TWIST, a version of WEAVE +which allows multiple changefiles (see TUGBoat Vol.7, No. 1, pg. 20-21). +@x We modify the text +@* Introduction. +This program converts a \.{WEB} file to a \TeX\ file. It was written +by D. E. Knuth in October, 1981; a somewhat similar {\mc SAIL} program had +been developed in March, 1979, although the earlier program used a top-down +parsing method that is quite different from the present scheme. +@y +\def\title{TWIST} +\def\contentspagenumber{1} % should be odd +\def\topofcontents{\null\vfill + \titlefalse % include headline on the contents page + \def\rheader{\mainfont Appendix E\hfil \contentspagenumber} + \centerline{\titlefont The {\ttitlefont TWIST} processor} + \vskip 15pt + \centerline{(Version 2.7)} + \vfill} +\pageno=\contentspagenumber \advance\pageno by 1 +@* Introduction. +This program converts a \.{WEB} file to a \TeX\ file. Several +changes have been made by GMD, to allow \.{WEAVE} to handle more +than one |change_file|. It is basically the original \.{WEAVE} written by +D.E. Knuth, but we'll name this \.{WEAVE}--Version \.{TWIST} to distinguish it +from the original one. The program was created by writing a |change_file| +modifying |weave.web|. To keep that |change_file| as small as possible we +didn't change the name "\.{WEAVE}" througout the documentation. +\bigskip +If you find a bug in the program you should send a message to: + +{\obeylines\parskip=0pt + W.Appelt, K.Horn + GMD Gesellschaft f\"ur Mathematik und Datenverarbeitung + Schlo\ss\ Birlinghoven + Postfach 1240 + D-5205 Sankt Augustin + + West-Germany + +} +\bigskip +The original version was written +by D. E. Knuth in October, 1981; a somewhat similar {\mc SAIL} program had +been developed in March, 1979, although the earlier program used a top-down +parsing method that is quite different from the present scheme. +@z + +@x (1) we modify the banner line +The ``banner line'' defined here should be changed whenever \.{WEAVE} +is modified. + +@d banner=='This is WEAVE, Version 2.8' +@y +The ``banner line'' defined here should be changed whenever \.{TWIST} +is modified. +@d banner=='This is TWIST based on WEAVE, Version 2.8 knitted by GMD' +@d term_in==input +@d term_out==output +@z + +@x (2) Now we allow three change-files +@ The program begins with a fairly normal header, made up of pieces that +@^system dependencies@> +will mostly be filled in later. The \.{WEB} input comes from files |web_file| +and |change_file|, and the \TeX\ output goes to file |tex_file|. +@y +@ Changes have been made to allow more than one |change_file|. +To mark changed modules we won't use the asteriks as \.{WEAVE} does, but +we'll indicate the number or numbers of the corresponding +|change_files| influencing the actual module. +In this \.{TWIST}--version three |change_files| are used. The necessary new +definitions are inserted quite below this explanation, so don't be +astonished to find a macro-definition in this module. +Changing the number of |change-files| only causes updates in this +module. + +The |change_files| have descending priorities. That means, if two +|change_files| want to modify the same line in a |web_file| , there will be a +warning and the |change_file| with the lower priority will be advanced +to the next section without influencing the input. +If a |change_file| wants to modify lines within the |web_file| which +are currently already changed by another |change_file| then changes +are ignored. +A warning will be generated. + +First you should examine this little example of \.{TWIST} with three +|change_files|: + +\bigskip +Input files: +$$\vbox{{\tt +\halign{& +\quad#\hfill\quad&\quad#\hfill\quad&\quad#\hfill\quad&\quad#\hfill\quad\cr +|web_file| & |ch_file_1| & |ch_file_2| & |ch_file_3| \cr +line1 & @@x & @@x & @@x \cr +line2 & line1 & line5 & line1 \cr +line3 & @@y & @@y & line2 \cr +line4 & line1from1 & line5from2 & @@y \cr +line5 & line2from1 & @@z & line1from3 \cr +line6 & line3from1 & @@x & @@z \cr +line7 & @@z & line10 & @@x \cr +line8 & *EOF* & @@y & line4 \cr +line9 & & line10from2 & line5 \cr +line10 & & line11from2 & line6 \cr +line11 & & @@z & @@y \cr +line12 & & *EOF* & line4from3 \cr +*EOF* & & & line5from3 \cr + & & & @@z \cr + & & & *EOF* \cr +}} +} +$$ + +The result will be that the program will input the following lines: + +{\tt\obeylines +***WARNING... (ch\_file\_3 will be advanced) +line1from1 +line2from1 +line3from1 +line2 +line3 +***WARNING... (ch\_file\_2 will be advanced) +line4from3 +line5from3 +line7 +line8 +line9 +line10from2 +line11from2 +line11 +line12 +*EOF* +} + +Now we want to explain the macros defined in this module. They serve to +avoid arrays of files since some PASCAL-compilers can't +handle arrays of files. Behind this we find the main-program's skeleton. + +The program begins with a fairly normal header, made up of pieces that +@^system dependencies@> +will mostly be filled in later. The \.{WEB} input comes +from the file |web_file| and from the files |change_files|, and the +\TeX\ output goes to file |tex_file|. +@z + +@x we have to split the change-file to allow system dependent changes +@d end_of_WEAVE = 9999 {go here to wrap it up} +@y +@d end_of_WEAVE = 9999 {go here to wrap it up} + +@d ch_max==3 {indicates the maximum number of |change_files| with descending +priority, if this number is modified the following three definitions and +the program statement must be updated as well, further don't forget to +initialize the array prime[1..ch\_max] properly} + +@d input_ch(#) == case # of + 1: ch_not_eof:=input_ln(ch_file_1); + 2: ch_not_eof:=input_ln(ch_file_2); + 3: ch_not_eof:=input_ln(ch_file_3);end + +@d reset_ch(#) == case # of + 1: reset(ch_file_1); + 2: reset(ch_file_2); + 3: reset(ch_file_3);end + +@d declaration_ch_files ==@/ + @! ch_file_1:text_file; + @! ch_file_2:text_file; + @! ch_file_3:text_file + +@d decl_prime == @! prime:array[1..ch_max] of integer +{augmenting the number of |change_files| don't forget to add the +corresponding prime number in the initialisation above} + +@z + +@x (2) program header,term_in and term_out (this shouldn't make worry) +program WEAVE(@!web_file,@!change_file,@!tex_file); +@y +program TWIST(@!term_in,@!term_out,@!web_file,@!ch_file_1,@!ch_file_2, + @!ch_file_3,@!tex_file); +@z + +@x We define the prime numbers +procedure initialize; + var @<Local variables for initialization@>@/ + begin @<Set initial values@>@/ + end; +@y +procedure initialize; + var @<Local variables for initialization@>@/ + begin @<Set initial values@> +prime[1]:=2; +prime[2]:=3; +prime[3]:=5; {augmenting the number of |change__ files| add a prime-number} +end; +@z + +@x Default in case statement is otherwise. (7) +@d othercases == others: {default for cases not listed explicitly} +@y +@d othercases == otherwise {default for cases not listed explicitly} +@z +@x term_out (output) must not be redefined. (20) +@!term_out:text_file; {the terminal as an output file} +@y +@z + +@x term_out (output) must not be opened. (21) +rewrite(term_out,'TTY:'); {send |term_out| output to the terminal} +@y +@z + +@x break is replaced by writeln (22) +@d update_terminal == break(term_out) {empty the terminal output buffer} +@y +@d update_terminal == write_ln(term_out) {empty the terminal output buffer} +@z + +@x (23) we declare the maximum number (ch_max) of change-files +@ The main input comes from |web_file|; this input may be overridden +by changes in |change_file|. (If |change_file| is empty, there are no changes.) + +@<Globals...@>= +@!web_file:text_file; {primary input} +@!change_file:text_file; {updates} +@y +@ The main input comes from |web_file|; this input may be overridden +by changes in the |change_files|. The |change_files| have descending +priority. Only one change may be done to the same text in the |web_file|. +(If all |change_files| are empty, there are no changes.) +Whenever the number of |change_files| is increased the correspondent +file-declarations have to be modified. Since we have written a macro +for this in module~2 of \.{TWIST}, no more changes are necessary here. + +@<Globals...@>= +@!web_file:text_file; {primary input} +declaration_ch_files; {look for the macro definitions at the beginning} +decl_prime; {look for the macro definition at the beginning} +@z + +@x (24) we have to reset all change-files explicitely +@ The following code opens the input files. Since these files were listed +in the program header, we assume that the \PASCAL\ runtime system has +already checked that suitable file names have been given; therefore no +additional error checking needs to be done. We will see below that +\.{WEAVE} reads through the entire input twice. +@^system dependencies@> + +@p procedure open_input; {prepare to read |web_file| and |change_file|} +begin reset(web_file); reset(change_file); +end; +@y +@ The following code opens the input files. Since these files were listed +in the program header, we assume that the \PASCAL\ runtime system has +already checked that suitable file names have been given; therefore no +additional error checking needs to be done. We will see below that +\.{WEAVE} reads through the entire input twice. +@^system dependencies@> +The macro |reset_ch(i)| resets the |i-th change_file|. +This is done by a macro, +so changing the number of |change_files| only makes necessary a modification +in the macro written in module~2 of the \.{TWIST}--program. + +@p procedure open_input; {prepare to read |web_file| and |change_file|} +var i:integer; {loop variable for processing the |change_files|} +begin reset(web_file); for i:=1 to ch_max do reset_ch(i);end; +@z + +@x (32) We want to specify the current change-file +@ The error locations can be indicated by using the global variables +|loc|, |line|, and |changing|, which tell respectively the first +unlooked-at position in |buffer|, the current line number, and whether or not +the current line is from |change_file| or |web_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@>= +begin if changing then print('. (change file ')@+else print('. ('); +print_ln('l.', line:1, ')'); +if loc>=limit then l:=limit else l:=loc; +for k:=1 to l do + if buffer[k-1]=tab_mark then print(' ') + else print(xchr[buffer[k-1]]); {print the characters already read} +new_line; +for k:=1 to l do print(' '); {space out the next line} +for k:=l+1 to limit do print(xchr[buffer[k-1]]); {print the part not yet read} +if buffer[limit]="|" then print(xchr["|"]); + {end of \PASCAL\ text in module names} +print(' '); {this space separates the message from future asterisks} +end +@y +@ The error locations can be indicated by using the global variables +|loc|, |line|, and |changing|, which tell respectively the first +unlooked-at position in |buffer|, the current line number, and whether or not +the current line is from a |change_file| (and if yes, from which one) +or from |web_file|. The variable |ch_act| gives the index of +the current |change_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@>= +begin if changing then print('. (change file ',ch_act:1,' ')@+ +else print('. ('); +print_ln('l.', line:1, ')'); +if loc>=limit then l:=limit else l:=loc; +for k:=1 to l do + if buffer[k-1]=tab_mark then print(' ') + else print(xchr[buffer[k-1]]); {print the characters already read} +new_line; +for k:=1 to l do print(' '); {space out the next line} +for k:=l+1 to limit do print(xchr[buffer[k-1]]); {print the part not yet read} +if buffer[limit]="|" then print(xchr["|"]); + {end of \PASCAL\ text in module names} +print(' '); {this space separates the message from future asterisks} +end +@z + +@x (45) we specify the actual change file +@ We keep track of the current module number in +|module_count|, which is the total number of modules that have started. +Modules which have been altered by a change file entry +have their |changed_module| flag turned on during the first phase. + +@<Globals...@>= +@!module_count:0..max_modules; {the current module number} +@!changed_module: packed array [0..max_modules] of boolean; {is it changed?} +@!change_exists: boolean; {has any module changed?} +@y +@ We keep track of the current module number in +|module_count|, which is the total number of modules that have started. +Modules which have been altered by a |change_file|--entry +have their |changed_module| flag turned on during the first phase. +Since we have to work with more than one |change_file| we cannot +use a boolean flag. We have decided to associate a prime number +with each |change_file| and to multiply the flag's value with this +prime number if the module is changed by that |change_file|. The +initial value for the |changed_module|--flag is one. +A value different from one, indicates that a change has been made. + +@<Globals...@>= +@!module_count:0..max_modules; {the current module number} +@!changed_module: packed array [0..max_modules] of integer; +{keeps a product of primes or 1} +@!change_exists: integer; {unequal 1 if a module has changed} +@z + +@x (70) we define a type |ch_type| to characterise the local parameter +@* Lexical scanning. +Let us now consider the subroutines that read the \.{WEB} source file +and break it into meaningful units. There are four such procedures: +One simply skips to the next `\.{@@\ }' or `\.{@@*}' that begins a +module; another passes over the \TeX\ text at the beginning of a +module; the third passes over the \TeX\ text in a \PASCAL\ comment; +and the last, which is the most interesting, gets the next token of +a \PASCAL\ text. +@y +@* Lexical scanning. +Let us now consider the subroutines that read the \.{WEB} source file +and break it into meaningful units. There are four such procedures: +One simply skips to the next `\.{@@\ }' or `\.{@@*}' that begins a +module; another passes over the \TeX\ text at the beginning of a +module; the third passes over the \TeX\ text in a \PASCAL\ comment; +and the last, which is the most interesting, gets the next token of +a \PASCAL\ text. + +In the \.{TWIST}--version of \.{WEAVE} there is more than one |change_file|. +So there are some modifications, which will be explained when they are made. +Altering the number of |change_files| only causes modifications in module~2 +of \.{TWIST} since we decided to use macros whereever +the number of |change_files| is important. + +We need a type |ch_type| to describe the local parameters. This will be done +here. + +@<Types...@>= +@!ch_type=1..ch_max; +@z + +@x (73) we expand |change_buffer|, |change_limit| and |ch_line| one dimension +@ When |changing| is |false|, the next line of |change_file| is kept in +|change_buffer[0..change_limit]|, for purposes of comparison with the next +line of |web_file|. After the change file has been completely input, we +set |change_limit:=0|, so that no further matches will be made. + +@<Globals...@>= +@!change_buffer:array[0..buf_size] of ASCII_code; +@!change_limit:0..buf_size; {the last position occupied in |change_buffer|} +@y +@ Different to the original version we are able to handle more than +one |change_file|. So the variable |change_buffer| and +|change_limit| need one more dimension to indicate the current |change_file|. +The index of the current |change_file| will be kept in the variable |ch_act|. +It is set by the function |lines_dont_match|. The variable |ch_not_eof| +takes the value of the |input_ln| function when the input comes from a +|change_file|. To understand why we need this you should look up +the macro definition of |input_ch(#)| in module~2 of \.{TWIST}. +The current line numbers of the |web_file| and all the |change_files| +have to be managed. In the original version this could be done with the +macro |change_changing| defined in the previous module. \.{TWIST} has to handle +more than two files, so we use the array |ch_line| to keep the current line +numbers of all |change_files|. The variable |line| will be updated from +this array each time it is used. +When |changing| is |false|, the next lines of the +|change_files| are kept in their |change_buffers|. +The length of the |change_buffers| are kept in |change_limit[i]|. +After a |change_file| has been completely input, we +set its |change_limit:=0|, so that no further matches will be made. + +@<Globals...@>= +@!change_buffer:array[1..ch_max,0..buf_size] of ASCII_code; +@!change_limit:array[1..ch_max] of 0..buf_size; + {the last position occupied in |change_buffer|} +@!ch_act:1..ch_max; {specifies the current |change_file|} +@!i:1..ch_max;{index used for loops} +@!ch_not_eof:boolean;{corresponds to the boolean |input_ln|} +@!ch_line:array[1..ch_max] of integer; {line refering to all |change_file|s} +@z + +@x (74) we define an additional function ch_dont_match +@ Here's a simple function that checks if the two buffers are different. + +@p function lines_dont_match:boolean; +label exit; +var k:0..buf_size; {index into the buffers} +begin lines_dont_match:=true; +if change_limit<>limit then return; +if limit>0 then + for k:=0 to limit-1 do if change_buffer[k]<>buffer[k] then return; +lines_dont_match:=false; +exit: end; +@y +@ Differing from the original version the index of the current |change_buffer| +has to be specified as function parameter. +The function |lines_dont_match| checks if |change_buffer[i]| is equal to +the buffer filled by |web_file|. If so, the index of the |change_file| +is copied to the variable |ch_act|. The function |ch_dont_match| checks +if two |change_buffers| are equal. + +@p function lines_dont_match(i:ch_type):boolean; +label exit; +var k:0..buf_size; {index into the buffers} +begin lines_dont_match:=true; +if change_limit[i]<>limit then return; +if limit>0 then + for k:=0 to limit-1 do if change_buffer[i,k]<>buffer[k] then return; +ch_act:=i; +lines_dont_match:=false; +exit: end; +@t\hskip 2in@> + +function ch_dont_match(i,j:ch_type):boolean; +label exit; +var k:0..buf_size; {index into the buffers} +begin ch_dont_match:=true; +if ((change_limit[i]=0) or (change_limit[j]=0)) then return; +if change_limit[i]<>change_limit[j] then return; +if change_limit[i]>0 then + for k:=0 to change_limit[i]-1 + do if change_buffer[i,k]<>change_buffer[j,k] then return; +ch_dont_match:=false; +exit: end; +@z + +@x (75) now we have to manage more than one change-file +@ 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=0)and not changing| if and +only if the change file is exhausted. This procedure is called only +when |changing| is true; hence error messages will be reported correctly. + +@p procedure prime_the_change_buffer; +label continue, done, exit; +var k:0..buf_size; {index into the buffers} +begin change_limit:=0; {this value will be 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|@>; +exit: end; +@y +@ Procedure |prime_the_change_buffer| sets the indexed +|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=0)and not changing| if and +only if the change file is exhausted. This procedure is called only +when |changing| is true; hence error messages will be reported correctly. + +The procedure |pre_prime_the_change_buffer| is necessary if there are two +equal |change_buffers|. The one with the lower priority or the one which +is currently not compared with the buffer filled by |web_file| will be advanced +to the next matching position behind the @@x-line. First we have to skip over +the @@x...@@y...@@z passage before we call the original +|prime_the_change_buffer|. + +The procedure |compare_change_files| checks if the |change_buffers| of all +|change_files| are equal. If so, those one with the lower priority will be +advanced. + +@p procedure prime_the_change_buffer(i:ch_type); +label continue, done, exit; +var k:0..buf_size; {index into the buffers} +begin change_limit[i]:=0; {this value will be 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|@>; +exit: end; +@t\hskip 2in@> + +procedure pre_prime_the_change_buffer(i:ch_type); +label continue, done, exit; +begin change_limit[i]:=0; {this value will be used if the change file ends} +loop@+ begin line:=ch_line[i];incr(line);ch_line[i]:=line; + input_ch(i); + if not ch_not_eof then + begin err_print ('! Change file ',i:1,' ended without @@z'); + return end; + if limit < 2 then goto continue; + if ((buffer[0]="@@") and ((buffer[1]="z") or (buffer[1]="Z"))) + then goto done; +continue:end; +done: +prime_the_change_buffer(i); +exit: end; +@t\hskip 2in@> + +procedure compare_change_files; +label restart; +var i,j:0..ch_max; +begin +restart: +for i:=1 to ch_max-1 do + for j:=i+1 to ch_max do + if not ch_dont_match(i,j) then + begin + print_nl(' WARNING: change_file ',i:1,' line ',ch_line[i]:4); + print_nl(' and change_file ',j:1,' line ',ch_line[j]:4, + ' refer to the same text !!!!'); + print_nl(' Change_file ',j:1,' will be advanced.');new_line; + pre_prime_the_change_buffer(j); + mark_harmless; goto restart + end; +end; +@z + +@x (76) we have to use an index for change-file +@ 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} or \.{@@z} (which would probably indicate that the change file is +fouled up). + +@<Skip over comment lines in the change file...@>= +loop@+ begin incr(line); + if not input_ln(change_file) then return; + if limit<2 then goto continue; + if buffer[0]<>"@@" then goto continue; + if (buffer[1]>="X")and(buffer[1]<="Z") then + buffer[1]:=buffer[1]+"z"-"Z"; {lowercasify} + if buffer[1]="x" then goto done; + if (buffer[1]="y")or(buffer[1]="z") then + begin loc:=2; err_print('! Where is the matching @@x?'); +@.Where is the match...@> + end; +continue: end; +done: +@y +@ While looking for a line that begins with \.{@@x} in the change file, +indexed by |i|, +we allow lines that begin with \.{@@}, as long as they don't begin with +\.{@@y} or \.{@@z} (which would probably indicate that the change file is +fouled up). + +@<Skip over comment lines in the change file...@>= +loop@+ begin line:=ch_line[i];incr(line);ch_line[i]:=line; + input_ch(i); + if not ch_not_eof then return; + if limit<2 then goto continue; + if buffer[0]<>"@@" then goto continue; + if (buffer[1]>="X")and(buffer[1]<="Z") then + buffer[1]:=buffer[1]+"z"-"Z"; {lowercasify} + if buffer[1]="x" then goto done; + if (buffer[1]="y")or(buffer[1]="z") then + begin loc:=2; + err_print('! Where is the matching @@x in change-file ',i:1,' ?'); +@.Where is the match...@> + end; +continue: end; +done: +@z + +@x (77) we have to use an index using change-file +@ Here we are looking at lines following the \.{@@x}. + +@<Skip to the next nonblank line...@>= +repeat incr(line); + if not input_ln(change_file) then + begin err_print('! Change file ended after @@x'); +@.Change file ended...@> + return; + end; +until limit>0; +@y +@ Here we are looking at lines following the \.{@@x}. + +@<Skip to the next nonblank line...@>= +repeat line:=ch_line[i];incr(line);ch_line[i]:=line; + input_ch(i); + if not ch_not_eof then + begin err_print('! Change file ',i:1,' ended after @@x'); +@.Change file ended...@> + return; + end; +until limit>0; +@z + +@x (78) we have to specify the change-file by an index +@ @<Move |buffer| and |limit| to |change_buffer| and |change_limit|@>= +begin change_limit:=limit; +if limit>0 then for k:=0 to limit-1 do change_buffer[k]:=buffer[k]; +end +@y +@ @<Move |buffer| and |limit| to |change_buffer| and |change_limit|@>= +begin change_limit[i]:=limit; +if limit>0 then for k:=0 to limit-1 do change_buffer[i,k]:=buffer[k]; +end +@z + +@x (79) we have to specify and work with the actual change-file +@ The following procedure is used to see if the next change entry should +go into effect; it is called only when |changing| is false. +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|. + +@p procedure check_change; {switches to |change_file| if the buffers match} +label exit; +var n:integer; {the number of discrepancies found} +@!k:0..buf_size; {index into the buffers} +begin if lines_dont_match then return; +n:=0; +loop@+ begin change_changing; {now it's |true|} + incr(line); + if not input_ln(change_file) then + begin err_print('! Change file ended before @@y'); +@.Change file ended...@> + change_limit:=0; change_changing; {|false| again} + return; + end; + @<If the current line starts with \.{@@y}, + report any discrepancies and |return|@>; + @<Move |buffer| and |limit|...@>; + change_changing; {now it's |false|} + incr(line); + if not input_ln(web_file) then + begin err_print('! WEB file ended during a change'); +@.WEB file ended...@> + input_has_ended:=true; return; + end; + if lines_dont_match then incr(n); + end; +exit: end; +@y +@ The following procedure is used to see if the next change entry should +go into effect; it is called only when |changing| is false. +The idea is to test whether or not the current contents of |buffer| +matches the current contents of one of the |change_buffers|. +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 the current |change_file|. + +Since we use several |change_file|s we first have to examine all +|change_buffer|s. The index of the matching |change_file| is written to the +variable |ch_act| by the function |lines_dont_match|. +Each time one line of the current |change_file| is read into its +|change_buffer| this one is compared with all other |change_buffers|. +In case of equality the latter |change_file| will be advanced by the +function |pre_prime_the_change_buffer|. + + +@p procedure check_change; {switches to |change_file| if the buffers match} +label exit,done,continue; +var n:integer; {index, the number of discrepancies found} + i:integer; {loop variable for processing the |change_files|} + temp:integer; {for temporary storage of the contents of |ch_act|} +@!k:0..buf_size; {index into the buffers} +begin +for i:= 1 to ch_max do + if not lines_dont_match(i) then goto done; + return;{no change-file matches} +done:{one change-file matches, the index has been written to ch-act} + n:=0; +loop@+ begin change_changing; {now it's |true|} + line:=ch_line[ch_act];incr(line);ch_line[ch_act]:=line; + input_ch(ch_act); + if not ch_not_eof then + begin err_print('! Change file ',ch_act:1,' ended before @@y'); +@.Change file ended...@> + change_limit[ch_act]:=0; change_changing; {|false| again} + return; + end; + @<If the current line starts with \.{@@y}, + report any discrepancies and |return|@>; + @<Move |buffer| and |limit|...@>; +{the actual change-buffer has to be compared with the other change-buffers, +the index of the actual |change_file| is stored to the variable temp} + temp:=ch_act; + for i:=1 to ch_max do + begin + if i=ch_act then goto continue; + if not ch_dont_match(i,ch_act) then + begin + print_nl(' WARNING: change_file ',i:1,' line ',ch_line[i]:4); + print_nl(' and change_file ',ch_act:1,' line ', + ch_line[ch_act]:4,' refer to the same text !!!!'); + print_nl(' Change_file ',i:1,' will be advanced.');new_line; + pre_prime_the_change_buffer(i); + end; + continue: end; + ch_act:=temp;i:=ch_act; {the actual index has to be reloaded} + change_changing; {now it's |false|} + incr(line); + if not input_ln(web_file) then + begin err_print('! WEB file ended during a change'); +@.WEB file ended...@> + input_has_ended:=true; return; + end; + if lines_dont_match(ch_act) then incr(n); + end; +exit: end; +@z + +@x (80) we specify the actual change-file in the error message +@ @<If the current line starts with \.{@@y}...@>= +if limit>1 then if buffer[0]="@@" then + begin if (buffer[1]>="X")and(buffer[1]<="Z") then + buffer[1]:=buffer[1]+"z"-"Z"; {lowercasify} + if (buffer[1]="x")or(buffer[1]="z") then + begin loc:=2; err_print('! Where is the matching @@y?'); +@.Where is the match...@> + end + else if buffer[1]="y" then + begin if n>0 then + begin loc:=2; err_print('! Hmm... ',n:1, + ' of the preceding lines failed to match'); +@.Hmm... n of the preceding...@> + end; + return; + end; + end +@y +@ @<If the current line starts with \.{@@y}...@>= +if limit>1 then if buffer[0]="@@" then + begin if (buffer[1]>="X")and(buffer[1]<="Z") then + buffer[1]:=buffer[1]+"z"-"Z"; {lowercasify} + if (buffer[1]="x")or(buffer[1]="z") then + begin loc:=2; + err_print('! Where is the matching @@y in change_file ',ch_act:1,' ?'); +@.Where is the match...@> + end + else if buffer[1]="y" then + begin if n>0 then + begin loc:=2; err_print('! Hmm... ',n:1, + ' of the preceding lines failed to match in change_file ',ch_act:1); +@.Hmm... n of the preceding...@> + end; + return; + end; + end +@z + +@x (81) to initialize we have to look at all change buffers +@ The |reset_input| procedure, which gets \.{WEAVE} ready to read the +user's \.{WEB} input, is used at the beginning of phases one and two. + +@p procedure reset_input; +begin open_input; line:=0; other_line:=0;@/ +changing:=true; prime_the_change_buffer; change_changing;@/ +limit:=0; loc:=1; buffer[0]:=" "; input_has_ended:=false; +end; +@y +@ The |reset_input| procedure, which gets \.{WEAVE} ready to read the +user's \.{WEB} input, is used at the beginning of phases one and two. + +@p procedure reset_input; +var i : integer; {local loop variable} +begin open_input; line:=0; other_line:=0;@/ +for i:=1 to ch_max do ch_line[i]:=0;@/ +changing:=true; +for i:=1 to ch_max do prime_the_change_buffer(i);@/ +compare_change_files;change_changing;@/ +limit:=0; loc:=1; buffer[0]:=" "; input_has_ended:=false; +end; +@z + +@x (82) we need a loop variable i and to store the module number +@ 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. + +@p procedure get_line; {inputs the next line} +label restart; +begin restart: if changing then changed_module[module_count]:=true +else @<Read from |web_file| and maybe turn on |changing|@>; +if changing then + begin @<Read from |change_file| and maybe turn off |changing|@>; + if not changing then + begin changed_module[module_count]:=true; goto restart; + end; + end; +loc:=0; buffer[limit]:=" "; +end; +@y +@ 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. + +In \.{TWIST} we add the procedure |set_index|. The idea is, that we don't +only want to mark a module as changed, as in the \.{WEAVE}--version with a +single |change_file|, but we want to enumerate all numbers of those +|change_files| influencing the module. + +The i-th |change_file| is represented by the i-th prime. Using +primes allows us to verify easily whether the actual |change_file| +is already marked by the value of |changed_module[m]|. This +is neccessary since each time a line is read from a |change_file| +\.{TWIST} wants to mark its number in |changed_module[m]|. + + +@p procedure set_index(m,i:integer); +{ m specifies the module, i specifies the |change_file| to be taken as index} +begin +if changed_module[m] mod prime[i] <> 0 + then changed_module[m] := changed_module[m] * prime[i]; +end; +@# +procedure get_line; {inputs the next line} +label restart;var i:integer;{ loop variable for processing the change files} +ch_flag:boolean; {flag whether to use |check_change| or not} +begin restart: if changing then set_index(module_count,ch_act) +else @<Read from |web_file| and maybe turn on |changing|@>; +if changing then + begin @<Read from |change_file| and maybe turn off |changing|@>; + if not changing then + begin set_index(module_count,ch_act); goto restart; + end; + end; +loc:=0; buffer[limit]:=" "; +end; +@z + +@x (83) we have to specify the actual change-file +@ @<Read from |web_file|...@>= +begin incr(line); +if not input_ln(web_file) then input_has_ended:=true +else if limit=change_limit then + if buffer[0]=change_buffer[0] then + if change_limit>0 then check_change; +end +@y +@ @<Read from |web_file|...@>= +begin incr(line); +if not input_ln(web_file) then input_has_ended:=true +else begin ch_flag:=false; + for i:=1 to ch_max do begin + if limit=change_limit[i] then + if buffer[0]=change_buffer[i,0] then + if change_limit[i]>0 then ch_flag:=true; end; + if ch_flag then check_change;end; +end +@z + +@x (84) we have to specify the change-file +@ @<Read from |change_file|...@>= +begin incr(line); +if not input_ln(change_file) then + begin err_print('! Change file ended without @@z'); +@.Change file ended...@> + buffer[0]:="@@"; buffer[1]:="z"; limit:=2; + end; +if limit>1 then {check if the change has ended} + if buffer[0]="@@" then + begin if (buffer[1]>="X")and(buffer[1]<="Z") then + buffer[1]:=buffer[1]+"z"-"Z"; {lowercasify} + if (buffer[1]="x")or(buffer[1]="y") then + begin loc:=2; err_print('! Where is the matching @@z?'); +@.Where is the match...@> + end + else if buffer[1]="z" then + begin prime_the_change_buffer; change_changing; + end; + end; +end +@y +@ @<Read from |change_file|...@>= +begin line:=ch_line[ch_act];incr(line);ch_line[ch_act]:=line; +input_ch(ch_act); +if not ch_not_eof then + begin err_print('! Change file ',ch_act:1,' ended without @@z'); +@.Change file ended...@> + buffer[0]:="@@"; buffer[1]:="z"; limit:=2; + end; +if limit>1 then {check if the change has ended} + if buffer[0]="@@" then + begin if (buffer[1]>="X")and(buffer[1]<="Z") then + buffer[1]:=buffer[1]+"z"-"Z"; {lowercasify} + if (buffer[1]="x")or(buffer[1]="y") then + begin loc:=2; err_print('! Where is the matching @@z in change_file ', + ch_act:1,' ?'); +@.Where is the match...@> + end + else if buffer[1]="z" then + begin prime_the_change_buffer(ch_act); + compare_change_files; change_changing; + end; + end; +end +@z + +@x (85) we have to specify the actual change-file +@ 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|. + +@<Check that all changes have been read@>= +if change_limit<>0 then {|changing| is false} + begin for loc:=0 to change_limit do buffer[loc]:=change_buffer[loc]; + limit:=change_limit; changing:=true; line:=other_line; loc:=change_limit; + err_print('! Change file entry did not match'); +@y +@ At the end of the program, we will tell the user if one of the +|change_file|s had a line that didn't match any relevant line in |web_file|. + +@<Check that all changes have been read@>= +for i:=1 to ch_max do begin +if change_limit[i]<>0 then {|changing| is false} + begin for loc:=0 to change_limit[i] do buffer[loc]:=change_buffer[i,loc]; + limit:=change_limit[i]; changing:=true; line:=ch_line[i]; + loc:=change_limit[i]; + ch_act:=i; + err_print('! Change_file ',i:1,' entry did not match'); +@.Change file entry did not match@> + end +@z + +@x (109) we have to specify the change-file number +@ The overall processing strategy in phase one has the following +straightforward outline. + +@<Phase I: Read all the user's text and store the cross references@>= +phase_one:=true; phase_three:=false; +reset_input; +module_count:=0; skip_limbo; change_exists:=false; +while not input_has_ended do + @<Store cross reference data for the current module@>; +changed_module[module_count]:=change_exists; + {the index changes if anything does} +phase_one:=false; {prepare for second phase} +@<Print error messages about unused or undefined module names@>; +@y +@ The overall processing strategy in phase one has the following +straightforward outline. + +@<Phase I: Read all the user's text and store the cross references@>= +phase_one:=true; phase_three:=false; +reset_input; +module_count:=0; skip_limbo; change_exists:=1; +while not input_has_ended do + @<Store cross reference data for the current module@>; +changed_module[module_count]:=change_exists; + {the index changes if anything does} +phase_one:=false; {prepare for second phase} +@<Print error messages about unused or undefined module names@>; +@z + +@x (110) we have to ask for zero instead of false +@ @<Store cross reference data...@>= +begin incr(module_count); +if module_count=max_modules then overflow('section number'); +changed_module[module_count]:=false; {it will become |true| if any line changes} +if buffer[loc-1]="*" then + begin print('*',module_count:1); + update_terminal; {print a progress report} + end; +@<Store cross references in the \TeX\ part of a module@>; +@<Store cross references in the \(definition part of a module@>; +@<Store cross references in the \PASCAL\ part of a module@>; +if changed_module[module_count] then change_exists:=true; +end +@y +@ @<Store cross reference data...@>= +begin incr(module_count); +if module_count=max_modules then overflow('section number'); +changed_module[module_count]:=1;{it will become |<>1| if any line changes} +if buffer[loc-1]="*" then + begin print('*',module_count:1); + update_terminal; {print a progress report} + end; +@<Store cross references in the \TeX\ part of a module@>; +@<Store cross references in the \(definition part of a module@>; +@<Store cross references in the \PASCAL\ part of a module@>; +if changed_module[module_count] <> 1 then +change_exists:=changed_module[module_count]; +end +@z + +@x +`\.{\\input webmac}'. + +@<Set init...@>= +out_ptr:=1; out_line:=1; out_buf[1]:="c"; write(tex_file,'\input webma'); +@y +`\.{\\input patchmac}'. + +@<Set init...@>= +out_ptr:=1; out_line:=1; out_buf[1]:="c"; write(tex_file,'\input patchma'); +@z + +@x (130)we indicate the change-file's number instead of an asteriks +@ The number to be converted by |out_mod| is known to be less than +|def_flag|, so it cannot have more than five decimal digits. If +the module is changed, we output `\.{\\*}' just after the number. + +@p procedure out_mod(@!m:integer); {output a module number} +var k:0..5; {index into |dig|} +@!a:integer; {accumulator} +begin k:=0; a:=m; +repeat dig[k]:=a mod 10; a:=a div 10; incr(k); +until a=0; +repeat decr(k); out(dig[k]+"0"); +until k=0; +if changed_module[m] then out2("\")("*"); +@.\\*@> +end; +@y +@ The number to be converted by |out_mod| is known to be less than +|def_flag|, so it cannot have more than five decimal digits. If +the module is changed, we output the numbers of the corresponding +|change_files| as indices just after the number. +Since we coded the numbers of the influencing |change_files| as +the product of the corresponding primes, we have to use the procedure +|out_index| to decode the corresponding numbers of the |change_files|. + +@p procedure out_index(m:integer); {output the |change--file| number} +var i:integer; +begin +i:=1; +while m mod prime[i] <> 0 do incr(i); +out(i+"0");incr(i); +while i <= ch_max do begin + if m mod prime[i] = 0 then begin out(",");out(i+"0") end; + incr(i) end; +end; +@# +procedure out_mod(@!m:integer); {output a module number} +var k:0..5; {index into |dig|} +@!a:integer; {accumulator} +begin k:=0; a:=m; +repeat dig[k]:=a mod 10; a:=a div 10; incr(k); +until a=0; +repeat decr(k); out(dig[k]+"0"); +until k=0; +if changed_module[m] <> 1 then begin out3("$")("^")("{"); + out_index(changed_module[m]);out2("}")("$");end +@.\\*@> +end; +@z + +@x (239) change_exist now is an integer-variable +We are nearly finished! \.{WEAVE}'s only remaining task is to write out the +index, after sorting the identifiers and index entries. + +@<Phase III: Output the cross-reference index@>= +phase_three:=true; print_nl('Writing the index...'); +if change_exists then + begin finish_line; @<Tell about changed modules@>; + end; +finish_line; out4("\")("i")("n")("x"); finish_line; +@.\\inx@> +@<Do the first pass of sorting@>; +@<Sort and output the index@>; +out4("\")("f")("i")("n"); finish_line; +@.\\fin@> +@<Output all the module names@>; +out4("\")("c")("o")("n"); finish_line; +@.\\con@> +print('Done.'); +@y +We are nearly finished! \.{WEAVE}'s only remaining task is to write out the +index, after sorting the identifiers and index entries. + +@<Phase III: Output the cross-reference index@>= +phase_three:=true; print_nl('Writing the index...'); +if change_exists <> 1 then + begin finish_line; @<Tell about changed modules@>; + end; +finish_line; out4("\")("i")("n")("x"); finish_line; +@.\\inx@> +@<Do the first pass of sorting@>; +@<Sort and output the index@>; +out4("\")("f")("i")("n"); finish_line; +@.\\fin@> +@<Output all the module names@>; +out4("\")("c")("o")("n"); finish_line; +@.\\con@> +print('Done.'); +@z + +@x (241) we indicate the change-file's number instead of an asteriks +@ @<Tell about changed modules@>= +begin {remember that the index is already marked as changed} +k_module:=1; +while not changed_module[k_module] do incr(k_module); +out4("\")("c")("h")(" "); +out_mod(k_module); +repeat repeat incr(k_module)@+ until changed_module[k_module]; + out2(",")(" "); out_mod(k_module); +until k_module=module_count; +out("."); +end +@y +@ @<Tell about changed modules@>= +begin {remember that the index is already marked as changed} +k_module:=1; +while changed_module[k_module] = 1 do incr(k_module); +out4("\")("c")("h")(" "); +out_mod(k_module); +repeat repeat incr(k_module)@+ until changed_module[k_module] <> 1; + out2(",")(" "); out_mod(k_module); +until k_module=module_count; +out("."); +end +@z |