% 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 @@/ begin @@/ end; @y procedure initialize; var @@/ begin @ 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.) @= @!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. @= @!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@> @= 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@> @= 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. @= @!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. @= @!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. @= @!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. @= @!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. @= @!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} @; @; @; 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} @; @; @; 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). @= 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). @= 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}. @= 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}. @= 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 @ @= begin change_limit:=limit; if limit>0 then for k:=0 to limit-1 do change_buffer[k]:=buffer[k]; end @y @ @= 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; @; @; 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; @; @; {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 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 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 @; if changing then begin @; 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 @; if changing then begin @; 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 @ @= 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 @ @= 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 @ @= 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 @ @= 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|. @= 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|. @= 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_one:=true; phase_three:=false; reset_input; module_count:=0; skip_limbo; change_exists:=false; while not input_has_ended do @; changed_module[module_count]:=change_exists; {the index changes if anything does} phase_one:=false; {prepare for second phase} @; @y @ The overall processing strategy in phase one has the following straightforward outline. @= phase_one:=true; phase_three:=false; reset_input; module_count:=0; skip_limbo; change_exists:=1; while not input_has_ended do @; changed_module[module_count]:=change_exists; {the index changes if anything does} phase_one:=false; {prepare for second phase} @; @z @x (110) we have to ask for zero instead of false @ @= 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; @; @; @; if changed_module[module_count] then change_exists:=true; end @y @ @= 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; @; @; @; if changed_module[module_count] <> 1 then change_exists:=changed_module[module_count]; end @z @x `\.{\\input webmac}'. @= out_ptr:=1; out_line:=1; out_buf[1]:="c"; write(tex_file,'\input webma'); @y `\.{\\input patchmac}'. @= 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_three:=true; print_nl('Writing the index...'); if change_exists then begin finish_line; @; end; finish_line; out4("\")("i")("n")("x"); finish_line; @.\\inx@> @; @; out4("\")("f")("i")("n"); finish_line; @.\\fin@> @; 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_three:=true; print_nl('Writing the index...'); if change_exists <> 1 then begin finish_line; @; end; finish_line; out4("\")("i")("n")("x"); finish_line; @.\\inx@> @; @; out4("\")("f")("i")("n"); finish_line; @.\\fin@> @; out4("\")("c")("o")("n"); finish_line; @.\\con@> print('Done.'); @z @x (241) we indicate the change-file's number instead of an asteriks @ @= 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 @ @= 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