diff options
Diffstat (limited to 'Build/source/texk/web2c/tiedir/tie.w')
-rw-r--r-- | Build/source/texk/web2c/tiedir/tie.w | 1297 |
1 files changed, 1297 insertions, 0 deletions
diff --git a/Build/source/texk/web2c/tiedir/tie.w b/Build/source/texk/web2c/tiedir/tie.w new file mode 100644 index 00000000000..227328b729e --- /dev/null +++ b/Build/source/texk/web2c/tiedir/tie.w @@ -0,0 +1,1297 @@ +% This is TIE.W as of 17 Dec 92 +%--------------------------------------------------------- +% Copyright (c) 1989,1992 by THD/ITI. +% All rights reserved. +% +% This program is distributed WITHOUT ANY WARRANTY, express or implied. +% +% Permission is granted to make and distribute verbatim copies of this +% program provided that the copyright notice and this permission notice +% are preserved on all copies. +% +% Permission is granted to copy and distribute modified versions of this +% program under the conditions for verbatim copying, provided that the +% entire resulting derived work is distributed under the terms of a +% permission notice identical to this one. +% +% +% Version 1.7 was built alike TIE.WEB Version 1.6 (89-01-27) +% but used a command line interface only +% Version 2.0 was revised to include optional tab expansion (89-02-01) +% Version 2.1 deleted WEB relicts. (-js) (89-10-25) +% Version 2.2 repaired replacing strategy (89-11-27) +% Version 2.3 was slightly modified to be processed with the +% cweb by Levy&Knuth. +% also repaired loop control for end of changes test (92-09-24) +% Version 2.4 included <stdlib.h> instead of <malloc.h> when +% used with ANSI-C (92-12-17) +% + +% Here is TeX material that gets inserted after \input cwebmac + +\def\hang{\hangindent 3em\indent\ignorespaces} +\font\mc=cmr9 +\def\PASCAL{Pascal} +\def\Cl{{\rm C}} +\def\ASCII{{\mc ASCII}} + +\def\title{TIE} +\def\topofcontents{\null\vfill + \centerline{\titlefont The {\ttitlefont TIE} processor} + \vskip 15pt + \centerline{(CWEB Version 2.4)} + \vfill} +\def\botofcontents{ +\null\vfill +\item{$\copyright$}1989, 1992 + by Technische Hochschule Darmstadt,\hfill\break +Fachbereich Informatik, Institut f\"ur Theoretische Informatik\hfill\break +All rights reserved.\hfill\break +This program is distributed WITHOUT ANY WARRANTY, express or implied. +\hfill\break +Permission is granted to make and distribute verbatim copies of this +program provided that the copyright notice and this permission notice +are preserved on all copies. +\hfill\break +Permission is granted to copy and distribute modified versions of this +program under the conditions for verbatim copying, provided that the +entire resulting derived work is distributed under the terms of a +permission notice identical to this one. +} + + + + + +@* Introduction. + +\noindent Whenever a programmer wants to change a given +\.{WEB} or \.{CWEB} program (referred to as a \.{WEB} program throughout +this program) because of system dependencies, she or he will +create a new change file. In addition there may be a second +change file to modify system independent modules of the +program. But the \.{WEB} file cannot be tangled and weaved +with more than one change file simultaneously. Therefore, +we introduce the present program to merge a \.{WEB} file and +several change files producing a new \.{WEB} file. Since +the input files are tied together, the program is called +\.{TIE}. Furthermore, the program can be used to merge +several change files giving a new single change file. This +method seems to be more important because it doesn't modify +the original source file. The use of \.{TIE} can be +expanded to other programming languages since this processor +only knows about the structure of change files and does not +interpret the master file at all. + +The program \.{TIE} has to read lines from several input +files to bring them in some special ordering. For this +purpose an algorithm is used which looks a little bit +complicated. But the method used only needs one buffer line +for each input file. Thus the storage requirement of +\.{TIE} does not depend on the input data. + +The program is written in \Cl\ and uses only few features of a +particular environment that may need to be changed in other +installations. +E.g.\ it will not use the |enum| type declarations. +The changes needed may refer to the access of the command line +if this can be not supported by any \Cl\ compiler. + +The ``banner line'' defined here should be changed whenever +\.{TIE} is modified. This program is put into the public +domain. Nevertheless the copyright notice must not be +replaced or modified. + +@d banner "This is TIE, CWEB Version 2.4." +@d copyright + "Copyright (c) 1989,1992 by THD/ITI. All rights reserved." + + +@ The main outline of the program is given in the next section. +This can be used more or less for any \Cl\ program. +@c +@<Global |#include|s@>@; +@<Global constants@>@; +@<Global types@>@; +@<Global variables@>@; +@<Error handling functions@>@; +@<Internal functions@>@; +@<The main function@>@; + +@ Here are some macros for common programming idioms. + +@d incr(v) v+=1 /* increase a variable by unity */ +@d decr(v) v-=1 /* decrease a variable by unity */ +@d loop @+ while (1)@+ /* repeat over and over until a |break| happens */ +@d do_nothing /* empty statement */ +@f loop while + + +@ Furthermore we include the additional types |boolean| and |string|. +@d false 0 +@d true 1 +@<Global types@>= +typedef int boolean; +typedef char* string; + + +@ The following parameters should be sufficient for most +applications of \.{TIE}. +@^system dependencies@> + +@<Global constants@>= +#define buf_size 512 /* maximum length of one input line */ +#define max_file_index 9 +/* we don't think that anyone needs more than 9 change files, + but \dots\ just change it */ + + +@ We introduce a history variable that allows us to set a +return code if the operating system can use it. +First we introduce the coded values for the history. +This variable must be initialized. +(We do this even if the value given may be the default for +variables, just to document the need for the initial value.) +@d spotless 0 +@d troublesome 1 +@d fatal 2 + +@<Global variables@>= +static int history=spotless; + + + +@* The character set. + +\noindent One of the main goals in the design of \.{TIE} has +been to make it readily portable between a wide variety of +computers. Yet \.{TIE} by its very nature must use a +greater variety of characters than most computer programs +deal with, and character encoding is one of the areas in +which existing machines differ most widely from each other. + +To resolve this problem, all input to \.{TIE} is converted to an +internal seven-bit code that is essentially standard \ASCII{}, the +``American Standard Code for Information Interchange.'' The conversion +is done immediately when each character is read in. Conversely, +characters are converted from \ASCII{} to the user's external +representation just before they are output. But the algorithm is +prepared for the usage of eight-bit data. + +\noindent Here is a table of the standard visible \ASCII{} codes: +$$\def\:{\char\count255\global\advance\count255 by 1} +\count255='40 +\vbox{ +\hbox{\hbox to 40pt{\it\hfill0\/\hfill}% +\hbox to 40pt{\it\hfill1\/\hfill}% +\hbox to 40pt{\it\hfill2\/\hfill}% +\hbox to 40pt{\it\hfill3\/\hfill}% +\hbox to 40pt{\it\hfill4\/\hfill}% +\hbox to 40pt{\it\hfill5\/\hfill}% +\hbox to 40pt{\it\hfill6\/\hfill}% +\hbox to 40pt{\it\hfill7\/\hfill}} +\vskip 4pt +\hrule +\def\^{\vrule height 10.5pt depth 4.5pt} +\halign{\hbox to 0pt{\hskip -24pt\O{#0}\hfill}&\^ +\hbox to 40pt{\tt\hfill#\hfill\^}& +&\hbox to 40pt{\tt\hfill#\hfill\^}\cr +04&\:&\:&\:&\:&\:&\:&\:&\:\cr\noalign{\hrule} +05&\:&\:&\:&\:&\:&\:&\:&\:\cr\noalign{\hrule} +06&\:&\:&\:&\:&\:&\:&\:&\:\cr\noalign{\hrule} +07&\:&\:&\:&\:&\:&\:&\:&\:\cr\noalign{\hrule} +10&\:&\:&\:&\:&\:&\:&\:&\:\cr\noalign{\hrule} +11&\:&\:&\:&\:&\:&\:&\:&\:\cr\noalign{\hrule} +12&\:&\:&\:&\:&\:&\:&\:&\:\cr\noalign{\hrule} +13&\:&\:&\:&\:&\:&\:&\:&\:\cr\noalign{\hrule} +14&\:&\:&\:&\:&\:&\:&\:&\:\cr\noalign{\hrule} +15&\:&\:&\:&\:&\:&\:&\:&\:\cr\noalign{\hrule} +16&\:&\:&\:&\:&\:&\:&\:&\:\cr\noalign{\hrule} +17&\:&\:&\:&\:&\:&\:&\:\cr} +\hrule width 280pt}$$ +(Actually, of course, code |040| is an invisible blank space.) Code |0136| +was once an upward arrow (\.{\char'13}), and code |0137| was +once a left arrow (\.^^X), in olden times when the first draft +of ASCII code was prepared; but \.{TIE} works with today's standard +ASCII in which those codes represent circumflex and underline as shown. +The maximum value used is also defined, it must be changed if an +extended \ASCII{} is used. + +If the \Cl\ compiler is not able to process \&{unsigned char}'s, you +should define |ASCII_Code| as \&{short}. +@^system dependencies@> + +@<Global types@>= +#define max_ASCII (@'~'+1) +typedef unsigned char ASCII_Code; + /* eight-bit numbers, a subrange of the integers */ + + +@ \Cl\ was first implemented on a machine that uses the +\ASCII{} representation for characters. But to make it readily +available also for other machines (big brother is watching?)\ +we add a conversion that may be undone for most installations. +\.{TIE} assumes that it is being used +with a character set that contains at least the +characters of standard \ASCII{} as listed above. + +In order to accommodate this difference, we shall use the +name |text_char| to stand for the data type of the +characters in the input and output files. We shall also +assume that |text_char| consists of the elements +|first_text_char| through |last_text_char|, +inclusive. The following definitions should be adjusted if +necessary. +@^system dependencies@> + +@d first_text_char 0 /* ordinal number of the smallest element of |text_char|*/ +@d last_text_char 255 /* ordinal number of the largest element of |text_char|*/ + +@<Global types@>= +typedef unsigned char text_char; /* the data type of characters in text files */ +typedef FILE* text_file; + + +@ The \.{TIE} processor converts between \ASCII{} code and +the user's external character set by means of arrays |xord| +and |xchr| that are analogous to \PASCAL's |ord| and |chr| +functions. + +The mapping may be disabled by changing the following macro +definitions to just a cast. If your \Cl\ compiler does not support +\&{unsigned char}'s, you should incorporate a binary and with |0xff|. +@^system dependencies@> + +@d map_xchr(c) (text_char)(c) + /* change this to |xchr[c]| on non \ASCII{} machines */ +@d map_xord(c) (ASCII_Code)(c) + /* change this to |xord[c]| on non \ASCII{} machines */ + +@<Global variables@>= +static ASCII_Code xord[last_text_char+1]; + /* specifies conversion of input characters */ +static text_char xchr[max_ASCII+1]; + /* specifies conversion of output characters */ + + +@ If we assume that every system using \.{WEB} is able to +read and write the visible characters of standard \ASCII{} +(although not necessarily using the \ASCII{} codes to +represent them), the following assignment statements +initialize most of the |xchr| array properly, without +needing any system-dependent changes. For example, the +statement |xchr[@'A']='A'| that appears in the present +\.{WEB} file might be encoded in, say, {\mc EBCDIC} code on +the external medium on which it resides, but \.{CTANGLE} will +convert from this external code to \ASCII{} and back again. +Therefore the assignment statement |xchr[65]='A'| will +appear in the corresponding \Cl\ file, and \Cl\ will +compile this statement so that |xchr[65]| receives the +character \.A in the external code. Note that it +would be quite incorrect to say |xchr[@'A']=@'A'|, because +|@'A'| is a constant of type |int| not \&{char}, and +because we have |@'A'==65| regardless of the external +character set. + +@<Set initial values@>= +xchr[@' ']=' '; +xchr[@'!']='!'; +xchr[@'\"']='\"'; +xchr[@'#']='#';@/ +xchr[@'$']='$'; +xchr[@'%']='%'; +xchr[@'&']='&'; +xchr[@'\'']='\'';@/ +xchr[@'(']='('; +xchr[@')']=')'; +xchr[@'*']='*'; +xchr[@'+']='+';@/ +xchr[@',']=','; +xchr[@'-']='-'; +xchr[@'.']='.'; +xchr[@'/']='/';@/ +xchr[@'0']='0'; +xchr[@'1']='1'; +xchr[@'2']='2'; +xchr[@'3']='3';@/ +xchr[@'4']='4'; +xchr[@'5']='5'; +xchr[@'6']='6'; +xchr[@'7']='7';@/ +xchr[@'8']='8'; +xchr[@'9']='9'; +xchr[@':']=':'; +xchr[@';']=';';@/ +xchr[@'<']='<'; +xchr[@'=']='='; +xchr[@'>']='>'; +xchr[@'?']='?';@/ +xchr[@'@@']='@@'; +xchr[@'A']='A'; +xchr[@'B']='B'; +xchr[@'C']='C';@/ +xchr[@'D']='D'; +xchr[@'E']='E'; +xchr[@'F']='F'; +xchr[@'G']='G';@/ +xchr[@'H']='H'; +xchr[@'I']='I'; +xchr[@'J']='J'; +xchr[@'K']='K';@/ +xchr[@'L']='L'; +xchr[@'M']='M'; +xchr[@'N']='N'; +xchr[@'O']='O';@/ +xchr[@'P']='P'; +xchr[@'Q']='Q'; +xchr[@'R']='R'; +xchr[@'S']='S';@/ +xchr[@'T']='T'; +xchr[@'U']='U'; +xchr[@'V']='V'; +xchr[@'W']='W';@/ +xchr[@'X']='X'; +xchr[@'Y']='Y'; +xchr[@'Z']='Z'; +xchr[@'[']='[';@/ +xchr[@'\\']='\\'; +xchr[@']']=']'; +xchr[@'^']='^'; +xchr[@'_']='_';@/ +xchr[@'`']='`'; +xchr[@'a']='a'; +xchr[@'b']='b'; +xchr[@'c']='c';@/ +xchr[@'d']='d'; +xchr[@'e']='e'; +xchr[@'f']='f'; +xchr[@'g']='g';@/ +xchr[@'h']='h'; +xchr[@'i']='i'; +xchr[@'j']='j'; +xchr[@'k']='k';@/ +xchr[@'l']='l'; +xchr[@'m']='m'; +xchr[@'n']='n'; +xchr[@'o']='o';@/ +xchr[@'p']='p'; +xchr[@'q']='q'; +xchr[@'r']='r'; +xchr[@'s']='s';@/ +xchr[@'t']='t'; +xchr[@'u']='u'; +xchr[@'v']='v'; +xchr[@'w']='w';@/ +xchr[@'x']='x'; +xchr[@'y']='y'; +xchr[@'z']='z'; +xchr[@'{']='{';@/ +xchr[@'|']='|'; +xchr[@'}']='}'; +xchr[@'~']='~';@/ +xchr[0]=' '; xchr[0x7F]=' '; /* these \ASCII{} codes are not used */ + + +@ Some of the \ASCII{} codes below |0x20| have been given a +symbolic name in \.{TIE} because they are used with a special +meaning. + +@d tab_mark @'\t' /* \ASCII{} code used as tab-skip */ +@d nl_mark @'\n' /* \ASCII{} code used as line end marker */ +@d form_feed @'\f' /* \ASCII{} code used as page eject */ + + +@ When we initialize the |xord| array and the remaining +parts of |xchr|, it will be convenient to make use of an +index variable, |i|. + +@<Local variables for initialisation@>= +int i; + + +@ Here now is the system-dependent part of the character +set. If \.{TIE} is being implemented on a garden-variety +\Cl\ for which only standard \ASCII{} codes will appear +in the input and output files, you don't need to make any +changes here. +@^system dependencies@> + +Changes to the present module will make \.{TIE} more +friendly on computers that have an extended character set, +so that one can type things like \.^^Z. If you have an +extended set of characters that are easily incorporated into +text files, you can assign codes arbitrarily here, giving an +|xchr| equivalent to whatever characters the users of +\.{TIE} are allowed to have in their input files, provided +that unsuitable characters do not correspond to special +codes like |tab_mark| that are listed above. + +@<Set init...@>= +for (i=1;i<@' ';xchr[i++]=' '); +xchr[tab_mark]='\t'; +xchr[form_feed]='\f'; +xchr[nl_mark]='\n'; + + +@ The following system-independent code makes the |xord| +array contain a suitable inverse to the information in +|xchr|. + +@<Set init...@>= +for ( i=first_text_char ; i<=last_text_char ; xord[i++]=@' ' ) do_nothing; +for ( i=1 ; i<=@'~' ; i++ ) xord[xchr[i]] = i; + + + + + +@* Input and output. + +\noindent Output for the user is done by writing on file |term_out|, +which is assumed to consist of characters of type \&{text\_char}. It +should be linked to |stdout| usually. Terminal input is not needed in +this version of \.{TIE}. |stdin| and |stdout| are predefined if we +include the \.{stdio.h} definitions. Although I/O redirection for +|stdout| is usually available you may lead output to another file if +you change the definition of |term_out|. Also we define some macros +for terminating an output line and writing strings to the user. + +@^system dependencies@> +@d term_out stdout +@d print(a) fprintf(term_out,a) /* `|print|' means write on the terminal */ +@d print2(a,b) fprintf(term_out,a,b) /* same with two arguments */ +@d print3(a,b,c) fprintf(term_out,a,b,c) /* same with three arguments */ +@d print_c(v) fputc(v,term_out); /* print a single character */ +@d new_line(v) fputc('\n',v) /* start new line */ +@d term_new_line new_line(term_out) + /* start new line of the terminal */ +@d print_ln(v) {fprintf(term_out,v);term_new_line;} + /* `|print|' and then start new line */ +@d print2_ln(a,b) {print2(a,b);term_new_line;} /* same with two arguments */ +@d print3_ln(a,b,c) {print3(a,b,c);term_new_line;} + /* same with three arguments */ +@d print_nl(v) {term_new_line; print(v);} + /* print information starting on a new line */ +@d print2_nl(a,b) {term_new_line; print2(a,b);} + /* same for two arguments */ + +@<Global |#include|s@>= +#include <stdio.h> + + +@ And we need dynamic memory allocation. +This should cause no trouble in any \Cl\ program. +@^system dependencies@> + +@<Global |#include|s@>= +#ifdef __STDC__ +#include <stdlib.h> +#else +#include <malloc.h> +#endif + +@ The |update_terminal| function is called when we want to +make sure that everything we have output to the terminal so +far has actually left the computer's internal buffers and +been sent. +@^system dependencies@> + +@d update_terminal fflush(term_out) /* empty the terminal output buffer */ + + + + + +@* Data structures. + +\noindent The multiple input files (master file and change +files) are treated the same way. To organize the +simultaneous usage of several input files, we introduce the +data type \&{in\_file\_modes}. + +The mode |search| indicates that \.{TIE} searches for a +match of the input line with any line of an input file in +|reading| mode. |test| is used whenever a match is found +and it has to be tested if the next input lines do match +also. |reading| describes that the lines can be read without +any check for matching other lines. |ignore| denotes +that the file cannot be used. This may happen because an +error has been detected or because the end of the file has +been found. + +\leavevmode |file_types| is used to describe whether a file +is a master file or a change file. The value |unknown| is added +to this type to set an initial mode for the output file. +This enables us to check whether any option was used to select +the kind of output. (this would even be necessary if we +would assume a default action for missing options.) + +@<Global types@>= +#define search 0 +#define test 1 +#define reading 2 +#define ignore 3 +typedef int in_file_modes; /* should be |enum(search,test,reading,ignore)| */ +#define unknown 0 +#define master 1 +#define chf 2 +typedef int file_types; /* should be |enum(unknown,master,chf)| */ + + +@ A variable of type |out_md_type| will tell us in what state the output +change file is during processing. |normal| will be the state, when we +did not yet start a change, |pre| will be set when we write the lines +to be changes and |post| will indicate that the replacement lines are +written. + +@<Global types@>= +#define normal 0 +#define pre 1 +#define post 2 +typedef int out_md_type; /* should be |enum(normal,pre,post)| */ + + +@ Two more types will indicate variables used as an index into either +the file buffer or the file table. + +@<Global types@>= +typedef int buffer_index; /* |-1..buf_size| */ +typedef int file_index; /* |-1..max_file_index+1| */ + + +@ The following data structure joins all informations needed to use +these input files. +%`line' is a normal identifier throughout this program +@f line dummy +@<Global types@>= +typedef struct _idsc{ + string name_of_file; + ASCII_Code buffer[buf_size]; + in_file_modes mode; + long line; + file_types type_of_file; + buffer_index limit; + text_file the_file; + } input_description; + + +@ These data types are used in the global variable section. +They refer to the files in action, the number of change files, +the mode of operation and the current output state. + +@<Global variables@>= +static file_index actual_input,test_input,no_ch; +static file_types prod_chf=unknown; +static out_md_type out_mode; + + +@ All input files (including the master file) are recorded +in the following structure. +Mostly the components are accessed through a local pointer variable. +This corresponds to \PASCAL's \&{with}-statement +and results in a one-time-computation of the index expression. + +@<Global variables@>= +static input_description *input_organization[max_file_index+1]; + + + + + +@* File I/O. + +\noindent The basic function |get_line| can be used to get a line from +an input file. The line is stored in the |buffer| part of the +descriptor. The components |limit| and |line| are updated. If the +end of the file is reached |mode| is set to |ignore|. On some systems +it might be useful to replace tab characters by a proper number of +spaces since several editors used to create change files insert tab +characters into a source file not under control of the user. So it +might be a problem to create a matching change file. +@^tab character expansion@> + +We define |get_line| to read a line from a file specified by +the corresponding file descriptor. + +@<Internal functions@>= +void get_line(i) + file_index i; +{register input_description *inp_desc=input_organization[i]; + if (inp_desc->mode==ignore) return; + if (feof(inp_desc->the_file)) + @<Handle end of file and return@>@; + @<Get line into buffer@>@; +} + + +@ End of file is special if this file is the master file. +Then we set the global flag variable |input_has_ended|. + +@<Handle end of file ...@>= +{ + inp_desc->mode=ignore; + inp_desc->limit=-1; /* mark end-of-file */ + if (inp_desc->type_of_file==master) input_has_ended=true; + return; +} + + +@ This variable must be declared for global access. + +@<Global variables@>= +static boolean input_has_ended=false; + + +@ Lines must fit into the buffer completely. +We read all characters sequentially until an end of line is found +(but do not forget to check for |EOF|!). +Too long input lines will be truncated. +This will result in a damaged output if they occur in the +replacement part of a change file, or in an incomplerte check if the +matching part is concerned. +Tab character expansion might be done here. +@^tab character expansion@> + +@<Get line into buffer@>= +{int final_limit; /* used to delete trailing spaces */ + int c; /* the actual character read */ + @<Increment the line number and print a progess report at + certain times@>@; + inp_desc->limit=final_limit=0; + while (inp_desc->limit<buf_size) { + c=fgetc(inp_desc->the_file); + @<Check |c| for |EOF|, |return| if line was empty, otherwise + |break| to process last line@>@; + inp_desc->buffer[inp_desc->limit++]=c=map_xord(c); + if (c==nl_mark) break; /* end of line found */ + if (c!=@' ' && c!=tab_mark) + final_limit=inp_desc->limit; + } + @<Test for truncated line, skip to end of line@>@; + inp_desc->limit=final_limit; +} + + +@ This section does what its name says. Every 100 lines +in the master file we print a dot, every 500 lines the number +of lines is shown. + +@<Increment the line number and print ...@>= +incr(inp_desc->line); +if (inp_desc->type_of_file==master && inp_desc->line % 100==0) { + if (inp_desc->line % 500 == 0) print2("%ld",inp_desc->line); + else print_c('.'); + update_terminal; +} + + +@ There may be incomplete lines of the editor used does +not make sure that the last character before end of file +is an end of line. In such a case we must process the +final line. Of the current line is empty, we just can \&{return}. +Note that this test must be done {\sl before} the character read +is translated. +@^system dependencies@> + +@<Check |c| for |EOF|...@>= + if (c==EOF) { + if (inp_desc->limit<=0) { + inp_desc->mode=ignore; + inp_desc->limit=-1; /* mark end-of-file */ + if (inp_desc->type_of_file==master) input_has_ended=true; + return; + } else { /* add end of line mark */ + c=nl_mark; + break; + } + } + + +@ If the line is truncated we must skip the rest +(and still watch for |EOF|!). +@<Test for truncated line, skip to end of line@>= +if (c!=nl_mark) { + err_print("! Input line too long")(i); +@.Input line too long@> + while ( (c=fgetc(inp_desc->the_file)) != EOF && map_xord(c) != nl_mark ) + do_nothing; /* skip to end */ + } + + + + + +@* Reporting errors to the user. + +\noindent There may be errors if a line in a given change +file does not match a line in the master file or a +replacement in a previous change file. Such errors are +reported to the user by saying +$$ + \hbox{|err_print("! Error message")(file_no)|;} +$$ +where |file_no| is the number of the file which is concerned by the +error. Please note that no trailing dot is supplied by the error +message because it is appended by |err_print|. + +This function is implemented as a macro. It gives a message and an +indication of the offending file. The actions to determine the error +location are provided by a function called |err_loc|. + +@d error_loc(m) err_loc(m); history=troublesome; @+ } +@d err_print(m) { @+ print_nl(m); error_loc + +@<Error handling...@>= +void err_loc(i) /* prints location of error */ + int i; +{ + print3_ln(" (file %s, l.%ld).", + input_organization[i]->name_of_file, + input_organization[i]->line); +} + + +@ Non recoverable errors are handled by calling |fatal_error| that +outputs a message and then calls `|jump_out|'. |err_print| will print +the error message followed by an indication of where the error was +spotted in the source files. |fatal_error| cannot state any files +because the problem is usually to access these. + +@d fatal_error(m) { + print(m); print_c('.'); history=fatal; + term_new_line; jump_out(); + } + + +@ |jump_out| just cuts across all active procedure levels and jumps +out of the program. It is used when no recovery from a particular +error has been provided. The return code from this program should be +regarded by the caller. + +@d jump_out() exit(1) + + + + + +@* Handling multiple change files. + +\noindent In the standard version we take the name of the +files from the command line. +It is assumed that filenames can be used as given in the +command line without changes. + +First there are some sections to open all files. +If a file is not accessible, the run will be aborted. +Otherwise the name of the open file will be displayed. + +@<Prepare the output file@>= +{ + out_file=fopen(out_name,"w"); + if (out_file==NULL) { + fatal_error("! Could not open/create output file"); +@.Could not open/create output file@> + } +} + + +@ The name of the file and the file desciptor are stored in +global variables. + +@<Global variables@>= +static text_file out_file; +static string out_name; + + +@ For the master file we start just reading its first line +into the buffer, if we could open it. + +@<Get the master file started@>= +{ input_organization[0]->the_file= + fopen(input_organization[0]->name_of_file,"r"); + if (input_organization[0]->the_file==NULL) + fatal_error("! Could not open master file"); +@.Could not open master file@> + print2("(%s)",input_organization[0]->name_of_file); + term_new_line; + input_organization[0]->type_of_file=master; + get_line(0); +} + +@ For the change files we must skip the comment part and +see, whether we can find any change in it. +This is done by |init_change_file|. + +@<Prepare the change files@>= +{file_index i; + i=1; + while (i<no_ch) { + input_organization[i]->the_file= + fopen(input_organization[i]->name_of_file,"r"); + if (input_organization[i]->the_file==NULL) + fatal_error("!Could not open change file"); +@.Could not open change file@> + print2("(%s)",input_organization[i]->name_of_file); + term_new_line; + init_change_file(i,true); + incr(i); + } +} + + + + + +@*Input/output organization. + +\noindent Here's a simple function that checks if two lines +are different. + +@<Internal functions@>= +boolean lines_dont_match(i,j) + file_index i,j; +{ + buffer_index k,lmt; + if (input_organization[i]->limit != input_organization[j]->limit) + return(true); + lmt=input_organization[i]->limit; + for ( k=0 ; k<lmt ; k++ ) + if (input_organization[i]->buffer[k] != input_organization[j]->buffer[k]) + return(true); + return(false); +} + + +@ Function |init_change_file(i,b)| is used to ignore all +lines of the input file with index |i| until the next change +module is found. The boolean parameter |b| indicates +whether we do not want to see \.{@@x} or \.{@@y} entries during +our skip. + +@<Internal functions@>= +void init_change_file(i,b) + file_index i; boolean b; +{register input_description *inp_desc=input_organization[i]; + @<Skip over comment lines; |return| if end of file@>@; + @<Skip to the next nonblank line; |return| if end of 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...@>= +loop@+ {ASCII_Code c; + get_line(i); + if (inp_desc->mode==ignore) return; + if (inp_desc->limit<2) continue; + if (inp_desc->buffer[0] != @'@@') continue; + c=inp_desc->buffer[1]; + if (c>=@'X' && c<=@'Z') + c+=@'z'-@'Z'; /*lowercasify*/ + if (c==@'x') break; + if (c==@'y' || c==@'z') + if (b) /* scanning for start of change */ + err_print("! Where is the matching @@x?")(i); +@.Where is the match...@> +} + + +@ Here we are looking at lines following the \.{@@x}. + +@<Skip to the next nonblank line...@>= +do{ + get_line(i); + if (inp_desc->mode==ignore) { + err_print("! Change file ended after @@x")(i); +@.Change file ended...@> + return; + } +} while (inp_desc->limit<=0); + + +@ The |put_line| function is used to write a line from +input buffer |j| to the output file. + +@<Internal functions@>= +void put_line(j) + file_index j; +{buffer_index i; /* index into the buffer */ + buffer_index lmt; /* line length */ + ASCII_Code *p; /* output pointer */ + lmt=input_organization[j]->limit; + p=input_organization[j]->buffer; + for (i=0;i<lmt;i++) fputc(map_xchr(*p++),out_file); + new_line(out_file); +} + + +@ The function |e_of_ch_module| returns true if the input +line from file |i| starts with \.{@@z}. + +@<Internal functions@>= +boolean e_of_ch_module(i) + file_index i; +{register input_description *inp_desc=input_organization[i]; + if (inp_desc->limit<0) { + print_nl("! At the end of change file missing @@z "); +@.At the end of change file...@> + print2("%s",input_organization[i]->name_of_file); + term_new_line; + return(true); + } else if (inp_desc->limit>=2) if (inp_desc->buffer[0]==@'@@' && + (inp_desc->buffer[1]==@'Z' || inp_desc->buffer[1]==@'z')) + return(true); + return(false); +} + + +@ The function |e_of_ch_preamble| returns |true| if the input +line from file |i| starts with \.{@@y}. + +@<Internal functions@>= +boolean e_of_ch_preamble(i) + file_index i; +{register input_description *inp_desc=input_organization[i]; + if (inp_desc->limit>=2 && inp_desc->buffer[0]==@'@@') + if (inp_desc->buffer[1]==@'Y'||inp_desc->buffer[1]==@'y') return(true); + return(false); +} + + + +@ To process the input file the next section +reads a line of the actual input file and updates the +|input_organization| for all files with index |test_file| greater +|actual_input|. + +@<Process a line, |break| when end of source reached@>= +{file_index test_file; + @<Check the current files for any ends of changes@>@; + if (input_has_ended && actual_input==0) break; /* all done */ + @<Scan all other files for changes to be done@>@; + @<Handle output@>@; + @<Step to next line@>@; +} + + +@ Any of the current change files may have reached the end of change. +In such a case intermediate lines must be skipped and the next start +of change is to be found. This may make a change file inactive if +it reaches end of file. + +@<Check the...@>= +{register input_description *inp_desc; +while (actual_input>0 && e_of_ch_module(actual_input)) { + inp_desc=input_organization[actual_input]; + if (inp_desc->type_of_file==master) { + /* emergency exit, everything mixed up!*/ + fatal_error("! This can't happen: change file is master file"); +@.This can't happen...@> + } + inp_desc->mode=search; + init_change_file(actual_input,true); + while ((input_organization[actual_input]->mode!=reading + && actual_input>0)) decr(actual_input); +} +} + + +@ Now we will set |test_input| to the file that has another match +for the current line. This depends on the state of the other +change files. If no other file matches, |actual_input| refers to +a line to write and |test_input| ist set to |none|. + +@d none (max_file_index+1) + +@<Scan all other files...@>= +test_input=none; +test_file=actual_input; +while (test_input==none && test_file<no_ch-1){ + incr(test_file); + switch (input_organization[test_file]->mode) { + case search: if (lines_dont_match(actual_input,test_file)==false) { + input_organization[test_file]->mode=test; + test_input=test_file; + } + break; + case test: if (lines_dont_match(actual_input, test_file)==true) { + /* error, sections do not match */ + input_organization[test_file]->mode=search; + err_print("! Sections do not match")(actual_input); +@.Sections do not match@> + err_loc(test_file); + init_change_file(test_file,false); + } else test_input=test_file; + break; + case reading: do_nothing; /* this can't happen */ + break; + case ignore: do_nothing; /* nothing to do */ + break; + } +} + + +@ For the output we must distinguish whether we create a new change +file or a new master file. The change file creation needs some closer +inspection because we may be before a change, in the pattern part or +in the replacement part. For a master file we have to write the line +from the current actual input. + +@<Handle output@>= +if (prod_chf==chf) { + loop @+ { + @<Test for normal, |break| when done@>@; + @<Test for pre, |break| when done@>@; + @<Test for post, |break| when done@>@; + } +} else +if (test_input==none) put_line(actual_input); + + +@ Check whether we have to start a change file entry. +Without a match nothing must be done. + +@<Test for normal...@>= +if (out_mode==normal) { + if (test_input!=none) { + fputc(map_xchr(@'@@'),out_file); fputc(map_xchr(@'x'),out_file); + new_line(out_file); + out_mode=pre; + } else break; +} + + +@ Check whether we have to start the replacement text. +This is the case when we have no more matching line. +Otherwise the master file source line must be copied to the +change file. +@<Test for pre...@>= + + if (out_mode==pre) { + if (test_input==none) { + fputc(map_xchr(@'@@'),out_file); fputc(map_xchr(@'y'),out_file); + new_line(out_file); + out_mode=post; + } else { + if (input_organization[actual_input]->type_of_file==master) + put_line(actual_input); + break; + } + } + + +@ Check whether an entry from a change file is complete. +If the current change was a change for a change file in effect, +then this change file line must be written. +If the actual input has been reset to the master file we +can finish this change. + +@<Test for post...@>= + if (out_mode==post) { + if (input_organization[actual_input]->type_of_file==chf) { + if (test_input==none) put_line(actual_input); + break; + } else { + fputc(map_xchr(@'@@'),out_file); fputc(map_xchr(@'z'),out_file); + new_line(out_file); + new_line(out_file); + out_mode=normal; + } + } + + +@ If we had a change, we must proceed in the actual file +to be changed and in the change file in effect. + +@<Step to next line@>= +get_line(actual_input); +if (test_input!=none) { + get_line(test_input); + if (e_of_ch_preamble(test_input)==true) { + get_line(test_input); /* update current changing file */ + input_organization[test_input]->mode=reading; + actual_input=test_input; + test_input=none; + } +} + + +@ To create the new output file we have to scan the whole +master file and all changes in effect when it ends. +At the very end it is wise to check for all changes +to have completed--in case the last line of the master file +was to be changed. + +@<Process the input@>= +actual_input=0; +input_has_ended=false; +while (input_has_ended==false||actual_input!=0) + @<Process a line...@>@; +if (out_mode==post) { /* last line has been changed */ + fputc(map_xchr(@'@@'),out_file); fputc(map_xchr(@'z'),out_file); + new_line(out_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 the master file or any of the change files. + +@<Check that all changes have been read@>= +{file_index i; +for (i=1;i<no_ch;i++) {/* all change files */ + if (input_organization[i]->mode!=ignore) + err_print("! Change file entry did not match")(i); +@.Change file entry ...@> + } +} + + +@ We want to tell the user about our command line options. This is +done by the |usage()| function. It contains merely the necessary +print statement and exits afterwards. + +@<Intern...@>= +void usage() +{ + print("Usage: tie -[mc] outfile master changefile(s)"); + term_new_line; + jump_out(); +} + + +@ We must scan through the list of parameters, given in |argv|. The +number is in |argc|. We must pay attention to the flag parameter. We +need at least 5~parameters and can handle up to |max_file_index| +change files. The names fo the file parameters will be inserted into +the structure of |input_organization|. The first file is special. It +indicates the output file. When we allow flags at any position, we +must find out which name is for what purpose. The master file is +already part of the |input_organization| structure (index~0). As long +as the number of files found (counted in |no_ch|) is |-1| we have not +yet found the output file name. + +@<Scan the parameters@>= +{int act_arg; + if ( argc < 5 || argc > max_file_index+4-1 ) usage(); + no_ch = -1; /* fill this part of |input_organization| */ + for ( act_arg=1 ; act_arg<argc ; act_arg++ ) { + if (argv[act_arg][0]=='-') @<Set a flag@>@; + else @<Get a file name@>@; + } + if (no_ch<=0|| prod_chf==unknown) usage(); +} + + +@ The flag is about to determine the processing mode. +We must make sure that this flag has not been set before. +Further flags might be introduced to avoid/force overwriting of +output files. +Currently we just have to set the processing flag properly. + +@<Set a flag@>= +if (prod_chf!=unknown) usage(); +else + switch (argv[act_arg][1]) { + case 'c': + case 'C': prod_chf=chf; break; + case 'm': + case 'M': prod_chf=master; break; + default: usage(); + } + + +@ We have to distinguish whether this is the very first file name +(known if |no_ch==(-1)|) or if the next element of |input_organization| +must be filled. + +@<Get a file name@>= +{ if (no_ch==(-1)) { + out_name=argv[act_arg]; + } else { register input_description *inp_desc; + inp_desc=(input_description *) + malloc(sizeof(input_description)); + if (inp_desc==NULL) + fatal_error("! No memory for descriptor"); +@.No memory for descriptor@> + inp_desc->mode=search; + inp_desc->line=0; + inp_desc->type_of_file=chf; + inp_desc->limit=0; + inp_desc->name_of_file=argv[act_arg]; + input_organization[no_ch]=inp_desc; + } + incr(no_ch); +} + + +@* The main program. + +\noindent Here is where \.{TIE} starts, and where it ends. + +@<The main function@>= +main(argc,argv) + int argc; string *argv; +{{@<Local variables for initialisation@>@; + @<Set initial...@>@; + } + print_ln(banner); /* print a ``banner line'' */ + print_ln(copyright); /* include the copyright notice */ + actual_input=0; + out_mode=normal; + @<Scan the parameters@>@; + @<Prepare the output file@>@; + @<Get the master file started@>@; + @<Prepare the change files@>@; + @<Process the input@>@; + @<Check that all changes have been read@>@; + @<Print the job |history|@>@; +} + +@ We want to pass the |history| value to the operating system so that +it can be used to govern whether or not other programs are started. +Additionaly we report the history to the user, although this may not +be ``{\mc UNIX}'' style---but we are in best companion: \.{WEB} and +\TeX{} do the same. +@^system dependencies@> + +@<Print the job |history|@>= +{string msg; + switch (history) { + case spotless: msg="No errors were found"; break; + case troublesome: msg="Pardon me, but I think I spotted something wrong."; + break; + case fatal: msg="That was a fatal error, my friend"; break; + } /* there are no other cases */ + print2_nl("(%s.)",msg); term_new_line; + exit ( history == spotless ? 0 : 1 ); +} + + + + + +@* System-dependent changes. + +\noindent This section should be replaced, if necessary, by +changes to the program that are necessary to make \.{TIE} +work at a particular installation. It is usually best to +design your change file so that all changes to previous +modules preserve the module numbering; then everybody's +version will be consistent with the printed program. More +extensive changes, which introduce new modules, can be +inserted here; then only the index itself will get a new +module number. +@^system dependencies@> + + + + + +@* Index. + +\noindent Here is the cross-reference table for the \.{TIE} +processor. |