% This is TIE.WEB in text format, as of December 30, 1986. %------------------------------------------------------------ % % Version 1.0 was completed Nov 30, 1983. % Version 1.1 was released Mar 19, 1984. % Version 1.2 was restructured for release. (kg) (86-12-22) % Version 1.3 allowed to change the last line, % made none a constant, % read empty lines correct, % edited system dependent parts. (js) (86-12-30) % % Here is TeX material that gets inserted after \input webmac \def\hang{\hangindent 3em\indent\ignorespaces} \font\mc=cmcsc10 \def\PASCAL{{\mc Pascal}} \def\ASCII{{\mc ascii}} \def\pb{$\.|\ldots\.|$} % pascal brackets (|...|) \def\v{\.{\char'174}} % vertical (|) in typewriter font \mathchardef\BA="3224 % double arrow \def\({} % kludge for alphabetizing certain module names \def\title{TIE} \def\topofcontents{\null\vfill \centerline{\titlefont The {\ttitlefont TIE} processor} \vskip 15pt \centerline{(Version 1.3)} \vfill} \def\botofcontents{ \null\vfill \item{$\copyright$}1983, 1984, 1986 by Technische Hochschule Darmstadt,\hfill\break Fachbereich Informatik, Institut f\"ur Theoretische Informatik\hfill\break All rights reserved.\hfill\break This program is put into the public domain and may be used freely for any non commercial purposes. } @* Introduction. \noindent Whenever a programmer wants to change a given \.{WEB} program because of system dependencies, 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 uses only few features of the local \PASCAL\ compiler that may need to be changed in other installations. The changes needed may be similar to those used to change the \.{WEB} processors \.{TANGLE} and \.{WEAVE}. All places where changes may be needed are listed in the index under the entry ``system dependencies''. @!@^system dependencies@> 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, Version 1.3' @d copyright== 'Copyright (c) 1983, 1984, 1986 by THD/ITI. All rights reserved.' @ If it is necessary to abort the job because of a fatal error, the program calls the `|jump_out|' procedure, which goes to the label |end_of_TIE|. @d end_of_TIE = 9999 {go here to wrap it up} @p @t\4@>@ program TIE(out_file); label end_of_TIE; {go here to finish} const @@; type @@; var @@; @t\4@>@@; procedure initialize; var @@; begin @ end; @ Some of this code ist optional for use when debugging only; such material is enclosed between the delimiters |debug| and |gubed|. @d debug==@t@>@{ {change this to `|debug==@t@>|' when debugging} @d gubed==@t@>@} {change this to `|gubed==@t@>|' when debugging} @f debug==repeat @f gubed==until @ The \PASCAL\ compiler used to develop this system has ``compiler directives'' that can appear in comments whose first character is a dollar sign. In production versions of \.{TIE} these directives tell the compiler that it is safe to avoid range checks and to leave out the extra code it inserts for the \PASCAL\ debugger's benefit, although interrupts will occur if there is arithmetic overflow. @^system dependencies@> @= @{@&@=$D-@> @} {no range check, catch arithmetic overflow, no debug overhead} @!debug @{@&@=$D+@> @}@+ gubed @; {but turn everything on when debugging} @ Labels are given symbolic names by the following definitions. We insert the label `|exit|:' just before the `\ignorespaces|end|\unskip' of a procedure in which we have used the `|return|' statement defined below. Loops that are set up with the \&{loop} construction defined below are commonly exited by going to `|done|' or to `|found|' or to `|not_found|', and they are sometimes repeated by going to `|continue|'. @d exit=10 {go here to leave a procedure} @d continue=20 {go here to resume a loop} @d done=30 {go here to exit a loop} @d found=31 {go here when you've found it} @d not_found=32 {go here when you've found something else} @ Here are some macros for common programming idioms. @d incr(#) == #:=#+1 {increase a variable by unity} @d decr(#) == #:=#-1 {decrease a variable by unity} @d loop == @+ while true do@+ {repeat over and over until a |goto| happens} @d do_nothing == {empty statement} @d return == goto exit {terminate a procedure call} @f return == nil @f loop == xclause @ We assume that |case| statements may include a default case that applies if no matching label is found. Thus, we shall use constructions like $$ \vbox{ \halign{#\hfil\cr |case x of|\cr \qquad 1: $\langle\,$code for $x=1\,\rangle$;\cr \qquad 3: $\langle\,$code for $x=3\,\rangle$;\cr \qquad |othercases| $\langle\,$code for |x<>1| and |x<>3|$\,\rangle$\cr |endcases|\cr }} $$ since most \PASCAL\ compilers have plugged this hole in the language by incorporating some sort of default mechanism. For example, the compiler used to develop \.{WEB} and \TeX\ allows `|others|:' as a default label, and other \PASCAL s allow syntaxes like `\ignorespaces|else|\unskip' or `\&{otherwise}' or `\\{otherwise}:', etc. The definitions of |othercases| and |endcases| should be changed to agree with local conventions. (Of course, if no default mechanism is available, the |case| statements of this program must be extended by listing all remaining cases.) @^system dependencies@> @d othercases == others: {default for cases not listed explicitly} @d endcases == @+end {follows the default case in an extended |case| statement} @f othercases == else @f endcases == end @ The following parameters should be sufficient for most applications of \.{TIE}. Note that |max_file_index| should not be enlarged without changing the processing of change file names. @^system dependencies@> @= @!buf_size=100; {maximum length of one input line} @!max_file_index=9;{we dont think that anyone needs more than 9 change files} @!max_name_length=54; {adapt this to your local name space} @ We introduce a history variable that allows us to set a return code if the operating system and the local \PASCAL\ compiler allow it. First we introduce the coded values for the history. @d spotless=0 @d troublesome=1 @d fatal=2 @= @!history:integer; @ We must initialize this variable at the very beginning. @= 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. \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 @'136 was once an upward arrow (\.{\char'13}), and code @'137 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. @= @!ascii_code=0..127; {seven-bit numbers, a subrange of the integers} @ The original \PASCAL\ compiler was designed in the late 60s, when six-bit character sets were common, so it did not make provision for lowercase letters. Nowadays, of course, we need to deal with both capital and small letters in a convenient way, so \.{TIE} assumes that it is being used with a \PASCAL\ whose character set contains at least the characters of standard \ASCII{} as listed above. Some \PASCAL\ compilers use the original name |char| for the data type associated with the characters in text files, while other \PASCAL s consider |char| to be a 64-element subrange of a larger data type that has some other name. 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 |chr(first_text_char)| through |chr(last_text_char)|, inclusive. The following definitions should be adjusted if necessary. @^system dependencies@> @d text_char == char {the data type of characters in text files} @d first_text_char=0 {ordinal number of the smallest element of |text_char|} @d last_text_char=127 {ordinal number of the largest element of |text_char|} @= @!text_file=packed file of text_char; @ The \.{TIE} processor convert 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. @= @!xord: array [text_char] of ascii_code; {specifies conversion of input characters} @!xchr: array [ascii_code] of text_char; {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 \.{TANGLE} will convert from this external code to \ASCII{} and back again. Therefore the assignment statement \.{XCHR[65]:=\'A\'} will appear in the corresponding \PASCAL\ file, and \PASCAL\ will compile this statement so that |xchr[65]| receives the character \.A in the external (|char|) code. Note that it would be quite incorrect to say \.{xchr["A"]:="A"}, because |"A"| is a constant of type |integer|, not |char|, and because we have $|"A"|=65$ regardless of the external character set. @= 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[@'177]:=' '; {these \ASCII{} codes are not used} @ One of the \ASCII{} codes below @'40 has been given a symbolic name in \.{TIE} because it is used with a special meaning. @d tab_mark=@'11 {\ASCII{} code used as tab-skip} @ When we initialize the |xord| array and the remaining parts of |xchr|, it will be convenient to make use of an index variable, |i|. @= @!i:0..last_text_char; @ Here now is the system-dependent part of the character set. If \.{TIE} is being implemented on a garden-variety \PASCAL\ 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 |carriage_return| that are listed above. @= for i:=1 to @'37 do xchr[i]:=' '; @ The following system-independent code makes the |xord| array contain a suitable inverse to the information in |xchr|. @= for i:=first_text_char to last_text_char do xord[chr(i)]:=@'40; for i:=1 to @'176 do xord[xchr[i]]:=i; @* Input and output. \noindent Terminal output is done by writing on file |term_out|, which is assumed to consist of characters of type |text_char|. Terminal input is read from |term_in|. @^system dependencies@> @d print(#)==write(term_out,#) {`|print|' means write on the terminal} @d print_ln(#)==write_ln(term_out,#) @d new_line==write_ln(term_out) {start new line} @d print_nl(#)== @+ begin new_line; print(#) @+ end @= @!term_in:text_file; @!term_out:text_file; @ To initialize terminal input we do a reset, just when the first character is needed. This allows output before the first input line is read even if ``lazy input'' is not supported by your \PASCAL\ compiler. @^system dependencies@> @= reset(term_in,'TTY:','/I') @ Similarly we initialize terminal output to a file that should be associated to the user's terminal interactively. @^system dependencies@> @= rewrite(term_out,'TTY:'); @ The |update_terminal| procedure is called when we want to make sure that everything we have output to the terminal so far has actually left the computer's internal buffers and been sent. @^system dependencies@> @d update_terminal == break(term_out) {empty the terminal output buffer} @ If an error occurs we indicate this calling a procedure named |err_print|. This procedure is implemented as a macro. It gives a message and an indication of the offending file. The actions to determine the error location will be explained later. @d error_loc(#) == err_loc(#); history:=troublesome; @+ end @d err_print(#) == @+ begin print_nl(#); error_loc @ The basic procedure |get_ln_from_file| can be used to get a line from an input file. The line is stored in |input_files[i].buffer|. The components |limit| and |line| are updated. If the end of the file is reached |input_files[i].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 number. To ease an inplementation without arrays of files we introduce one more parameter. In such cases |get_line| is best replaced by a procedure containing a |case| statement to select the file needed. @d get_line(#)==get_ln_from_file(#,input_files[#]); @p procedure get_ln_from_file(i:integer;var cur_file:text_file); label exit; var final_limit:0..buf_size; begin with input_organization[i] do begin if mode=ignore then return; if eof(cur_file) then @; @; end; exit: end; @ End of file is special if this file is the master file. @= begin mode:=ignore; if type_of_file=master then input_has_ended:=true; return; end @ Lines must fit into the buffer completely. Tab character expansion might be done here. @^tab character expansion@> @= incr(line); limit:=0; final_limit := 0; while (not eoln(cur_file) and (limit<=buf_size)) do begin buffer[limit]:=xord[cur_file^]; get(cur_file); incr(limit); if (buffer[limit-1]<>" ")and(buffer[limit-1]<>tab_mark) then final_limit:=limit; end; limit:=final_limit; if not eoln(cur_file) then err_print('! Input line too long')(i); @.Input line too long@> read_ln(cur_file) @* 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 types |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 equality to 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. @= @!in_file_modes=(@!search,@!test,@!reading,@!ignore);@/ @!file_types=(@!master,@!chf); @ The following data structures join all informations needed to use these input files. @= @!out_md_type=(@!normal,@!pre,@!post);@/ @!name_type=record @!name:array[1..max_name_length] of char; @!nam_len:0..max_name_length; end; @!buffer_index=0..buf_size;@/ @!file_index=0..max_file_index;@/ @!input_description=record @!name_of_file: name_type; @!buffer: array[0..buf_size]of ascii_code; @!mode: in_file_modes; @!line: integer; @!type_of_file: file_types; @!limit: buffer_index; end; @ These data types are used in the global variable section. @= @!actual_input,@!test_input:integer; @!input_has_ended:boolean; @!input_organization: array[0..max_file_index] of input_description; @!input_files: array[0..max_file_index] of text_file; @!no_ch,@!cmd:integer; @!c:char; i,j:integer; @!prod_chf:boolean; @!name_field:name_type; @!master_ext:name_type; @!cf_ext:name_type; @!no_ext:name_type; @!out_mode:out_md_type; @!ext_start:0..max_name_length; @* 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')|.} $$ Please note that no trailing point is supplied by the error message because it is appended by |err_print|. Non recoverable errors are handled by calling |fatal_error| that outputs a message and then calls `|jump_out|'. \leavevmode |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. For |err_print| messages the following procedure is used to write the proper name of an input file. @d fatal_error(#)==begin print_nl(#); print_ln('.'); history:=fatal; jump_out; end @= procedure print_name_of_file(cur_index:file_index); var i:integer; begin with input_organization[cur_index] do for i:=1 to name_of_file.nam_len do print(name_of_file.name[i]); end; @ The actual error indications are provided by a procedure called |err_loc|. @= procedure err_loc(i:integer); {prints location of error} begin with input_organization[i] do begin print(' (file '); print_name_of_file(i); print(', l.',line:1,').'); new_line; end; end; @ The |jump_out| procedure just cuts across all active procedure levels and jumps out of the program. This is the only non-local |goto| statement in \.{TIE}. It is used when no recovery from a particular error has been provided. Some \PASCAL\ compilers do not implement non-local |goto| statements. In such cases the code that appears at label |end_of_TIE| should be copied into the |jump_out| procedure, followed by a call to a system procedure that terminates the program. @^system dependencies@> @= procedure jump_out; begin goto end_of_TIE; end; @* Handling multiple change files. \noindent In the standard version we read the name of the files from the console. Handling of the end of line indicator might need a system dependent update. There might be system dependent changes to get the names of the files from the command line. If \.{TIE} is used on a system which allows filenames only in upper or lower case, |read_chr| should be replaced by a procedure that converts the characters properly. @^system dependencies@> @d read_chr(#)==read(term_in,#) @p procedure rd_file; var c:char; begin name_field.nam_len:=0; while (not eoln(term_in) and (name_field.nam_len0 then if not eoln(term_in) then fatal_error('! File name too long'); @.File name too long@> end; @ To do our job we need the master file's name. @= begin print_nl('Please enter the name of the master file: '); @.Please enter the name ...@> update_terminal; @; rd_file; if name_field.nam_len=0 then fatal_error('! Illegal file name'); @.Illegal file name@> @; if name_field.nam_len+4>max_name_length then fatal_error('! File name too long'); @.File name too long@> end @ The extension of the master source file is to be ignored. We assume that extensions are separated from the base name by a period. Let us scan from the tail to the last period. We skip only characters, digits and underscore characters. Finding any other character stops extension scanning. @= begin if name_field.nam_len>3 then with name_field do begin i:=nam_len; ext_start:=0; {extension not yet found} while i>0 do if name[i]='.' then begin ext_start:=i; i:=0 end else if (name[i]>='a') and (name[i]<='z') then i:=i-1 else if (name[i]>='A') and (name[i]<='Z') then i:=i-1 else if (name[i]>='0') and (name[i]<='9') then i:=i-1 else if (name[i]='_') then i:=i-1 else i:=0; if ext_start>0 then begin {initialize master file extension} for i:=ext_start to nam_len do master_ext.name[i-ext_start+1]:=name[i]; master_ext.nam_len:=nam_len-ext_start+1; for i:=master_ext.nam_len+1 to max_name_length do master_ext.name[i]:=' '; nam_len:=ext_start-1; end; end end @ We must determine the name of a change file, too. This might be changed to command line parameter processing. @^command line processing@> @= begin print_ln('Please enter the name of change file ',no_ch:1); print('or if there are no more change files: '); update_terminal; read_ln(term_in); rd_file; end @ The selection whether a new master file or a mixed change file should be produced might be replaced by a command line switch, too. @^command line processing@> @= repeat print_nl('Do you want to create a new master or a change file (m/c)? '); @.Do you want to create ...@> update_terminal; read_ln(term_in); read_chr(c); cmd:=xord[c]; if (cmd>="a") and (cmd<="z") then cmd:=cmd+"A"-"a"; until (cmd="C")or(cmd="M"); prod_chf:=(cmd="C") @ We continue with a function to open a text file. Success is indicated by a boolean result. We assume that empty files can be handled like non existent ones. @^system dependencies@> @p function file_open(var f:text_file;name:name_type):boolean; begin reset(f,name.name); file_open:=not eof(f); end; @ The procedure |insert_name| initializes the name of the file number |cur_index| to ``prefix'' ``extension''. @p procedure insert_name(cur_index:file_index;prefix:name_type; extension:name_type); var i:integer; begin with input_organization[cur_index] do begin for i:=1 to max_name_length do name_of_file.name[i]:=' '; for i:=1 to prefix.nam_len do name_of_file.name[i]:=prefix.name[i]; for i:=1 to extension.nam_len do name_of_file.name[prefix.nam_len+i]:=extension.name[i]; name_of_file.nam_len:=prefix.nam_len+extension.nam_len; end; end; @ @= insert_name(0,name_field,master_ext); if not file_open(input_files[0],input_organization[0].name_of_file) then fatal_error('! Master file can''t be opened') @.Master file can't be opened@> else begin print('('); print_name_of_file(0); print_ln(')'); input_organization[0].type_of_file:=master; get_line(0); end; @ This is a procedure to open all input files. Changes might be used to get the names of the files from a command line. In this interactive version \.{TIE} will try to use change files with extensions \.{CF1} to \.{CF9} automatically---and there is no way to inhibit this, if those files exist. @^command line processing@> @p @t\4@>@ procedure open_input; {prepare to read |input_files|} label done; var i:integer; begin @; @; actual_input:=0; for i:=1 to max_file_index do begin cf_ext.name[cf_ext.nam_len]:=xchr["0"+i]; insert_name(i,name_field,cf_ext); end; no_ch:=0; while no_ch<9 do begin incr(no_ch); if not file_open(input_files[no_ch], input_organization[no_ch].name_of_file) then begin @; if name_field.nam_len=0 then begin decr(no_ch); goto done; end; insert_name(no_ch,name_field,no_ext); decr(no_ch); end else begin print('('); print_name_of_file(no_ch); print_ln(')'); init_change_file(no_ch,true); end; end; done: if no_ch=0 then fatal_error('! No change file found'); @.No change file found@> for i:=no_ch+1 to max_file_index do input_organization[i].mode:=ignore; end; @ According to the names given above, we initialize the extension names. We need the standard change file extensions that are \.{CF}$i$ and a ``dummy'' entry for change files that do not need additional extensions. @= cf_ext.name[1]:='.'; cf_ext.name[2]:='C'; cf_ext.name[3]:='F'; cf_ext.name[4]:=' '; cf_ext.nam_len:=4; for i:=cf_ext.nam_len+1 to max_name_length do cf_ext.name[i]:=' '; no_ext.nam_len:=0; for i:=1 to max_name_length do no_ext.name[i]:=' ' @ The main output goes to |out_file|. @= @!out_file:text_file; @ The following code opens |out_file|. Since this file is mentioned in the program header we assume that the \PASCAL\ runtime system has checked the existence of the file. @^system dependencies@> @= rewrite(out_file); @*Input/output organization. \noindent Here's a simple function that checks if two lines are different. @p function lines_dont_match(i,j:integer):boolean; label exit; var k,lmt:buffer_index; begin lines_dont_match:=true; if input_organization[i].limit <> input_organization[j].limit then return; lmt:=input_organization[i].limit; if lmt > 0 then for k:=0 to lmt-1 do if input_organization[i].buffer[k] <> input_organization[j].buffer[k] then return; lines_dont_match:=false; exit: end; @ Procedure |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. @= procedure init_change_file(i:integer;b:boolean); label continue, done, exit; begin with input_organization[i] do begin @; @; end; exit: end; @ 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 get_line(i); if mode=ignore 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 if b then {waiting for start of change} err_print('! Where is the matching @@x?')(i); @.Where is the match...@> continue: end; done: @ Here we are looking at lines following the \.{@@x}. @= repeat get_line(i); if mode=ignore then begin err_print('! Change file ended after @@x')(i); @.Change file ended...@> return; end; until limit>0 @ The |put_line| procedure is used to write a line from input buffer |j| to the output file. @p procedure put_line(j:integer); var i:integer; {index into the buffer} begin with input_organization[j] do for i:=0 to limit-1 do write(out_file,xchr[buffer[i]]); write_ln(out_file); end; @ The function |e_of_ch_module| returns true if the input line from file |i| is equal to \.{@@z}. @p function e_of_ch_module(i:integer):boolean; begin e_of_ch_module:=false; with input_organization[i] do if limit>=2 then if buffer[0]="@@" then if (buffer[1]="Z") or (buffer[1]="z") then e_of_ch_module:=true; end; @ The function |e_of_ch_preamble| returns true if the input line from file |i| is equal to \.{@@y}. @p function e_of_ch_preamble(i:integer):boolean; begin e_of_ch_preamble:=false; with input_organization[i] do if limit>=2 then if buffer[0]="@@" then if (buffer[1]="Y") or (buffer[1]="y") then e_of_ch_preamble:=true; end; @ To start with our inputs we set the indication that all change files are in state |search|. That means that we must skip to the next (in this case the first) change entry. @= for i:=0 to max_file_index do with input_organization[i] do begin mode:=search; line:=0; type_of_file:=chf; limit:=0; for j:=1 to max_name_length do name_of_file.name[j]:=' '; for j:=0 to buf_size do buffer[j]:=0; end; actual_input:=0; out_mode:=normal @ To process the input file the procedure |process_line| reads a line of the actual input file and updates the |input_organization| for all files with index |i| greater |actual_input|. @p procedure process_line; label done, exit; var i:integer; begin @; @; @; exit: end; @ If |test_input| is |none|, no change file is tested. @d none == (max_file_index+1) @= while e_of_ch_module(actual_input) do with input_organization[actual_input] do begin if type_of_file=master then {emergency exit, everything mixed up!} fatal_error('! This can''t happen'); mode:=search; init_change_file(actual_input,true); while ((input_organization[actual_input].mode<>reading) and (actual_input>0)) do decr(actual_input); end; if input_has_ended and (actual_input=0) then return; test_input:=none; i:=actual_input; while ((test_input=none) and (i err_loc(i); init_change_file(i,false); end else test_input:=i; end; reading:do_nothing; {this can't happen} ignore: do_nothing; {nothing to do} end; end @ @= if prod_chf then begin loop @+ begin @; @; @; end; done: end else if test_input=none then put_line(actual_input) @ Check whether we have to start a change file entry. @= begin if out_mode=normal then if test_input<>none then begin write(out_file,xchr["@@"]); write_ln(out_file,xchr["x"]); out_mode:=pre; end else goto done; end @ Check whether we have to start the replacement text. @= begin if out_mode=pre then begin if test_input=none then begin write(out_file,xchr["@@"]); write_ln(out_file,xchr["y"]); out_mode:=post; end else begin if input_organization[actual_input].type_of_file=master then put_line(actual_input); goto done; end; end; end @ Check whether a change file entry is complete. @= begin if out_mode=post then begin if input_organization[actual_input].type_of_file=chf then begin if test_input=none then put_line(actual_input); goto done; end else begin write(out_file,xchr["@@"]); write_ln(out_file,xchr["z"]); write_ln(out_file); out_mode:=normal; end; end; end @ @= get_line(actual_input); if test_input<>none then begin get_line(test_input); if e_of_ch_preamble(test_input) then begin get_line(test_input); {update input file} input_organization[test_input].mode:=reading; actual_input:=test_input; test_input:=none; end; end @ @= input_has_ended:=false; while not input_has_ended or (actual_input<>0) do process_line; if out_mode = post then {last line has been changed} begin write(out_file,xchr["@@"]); write_ln(out_file,xchr["z"]); end; @ 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. @= for i:=1 to max_file_index do {all change files} if input_organization[i].mode <> ignore then err_print('! Change file entry did not match')(i); @.Change file entry ...@> @* The main program. \noindent Here is where \.{TIE} starts, and where it ends. If a command line interface is present, changes to initialize the access might be needed here. @^system dependencies@> @^command line processing@> @p begin initialize; print_ln(banner); {print a ``banner line''} print_ln(copyright); {include the copyright notice} @; @; open_input; @; @; @; end_of_TIE: {here files should be closed if the operating system requires it} @; end. @ Some implementations may wish to pass the |history| value to the operating system so that it can be used to govern whether or not other programs are started. Here we simply report the history to the user. @^system dependencies@> @= case history of spotless: print_nl('(No errors were found.)'); troublesome: print_nl('(Pardon me, but I think I spotted something wrong.)'); fatal: print_nl('(That was a fatal error, my friend.)'); end {there are no other cases} @* 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.