% 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@>@<Compiler directives@>
program TIE(out_file);
  label end_of_TIE; {go here to finish}
  const @<Constants in the outer block@>@;
  type @<Types in the outer block@>@;
  var @<Globals in the outer block@>@;
  @t\4@>@<Error handling procedures@>@;
  procedure initialize;
    var @<Local variables for initialization@>@;
    begin @<Set initial values@>
    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@>

@<Compiler directives@>=
@{@&@=$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@>

@<Constants...@>=
@!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

@<Globals in ...@>=
@!history:integer;


@ We must initialize this variable at the very beginning.

@<Set initial values@>=
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.

@<Types...@>=
@!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|}

@<Types...@>=
@!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.

@<Globals...@>=
@!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.

@<Set init...@>=
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|.

@<Local variables for init...@>=
@!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.

@<Set init...@>=
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|.

@<Set init...@>=
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

@<Globals...@>=
@!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 terminal input@>=
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@>

@<Set init...@>=
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 @<Handle end of file and return@>;
      @<Get line into buffer@>;
      end;
exit: end;


@ End of file is special if this file is the master file.

@<Handle end of 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@>

@<Get line into buffer@>=
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.

@<Types...@>=
@!in_file_modes=(@!search,@!test,@!reading,@!ignore);@/
@!file_types=(@!master,@!chf);


@ The following data structures join all informations needed to use
these input files.

@<Types...@>=
@!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.

@<Globals...@>=
@!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

@<Error handling...@>=
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|.

@<Error handling...@>=
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@>

@<Error handling...@>=
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_len<max_name_length)) do
    begin
      incr(name_field.nam_len);
      read_chr(c);
      name_field.name[name_field.nam_len]:=c;
    end;
  if name_field.nam_len>0 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.

@<Read master file name@>=
begin
  print_nl('Please enter the name of the master file: ');
@.Please enter the name ...@>
  update_terminal;
  @<Reset terminal input@>;
  rd_file;
  if name_field.nam_len=0 then
    fatal_error('! Illegal file name');
@.Illegal file name@>
  @<Ignore master file extension@>;
  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.

@<Ignore master file extension@>=
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@>

@<Read change file name@>=
begin
  print_ln('Please enter the name of change file ',no_ch:1);
  print('or <Return> 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@>

@<Change or master file producing@>=
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;


@
@<Get the master file started@>=
  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@>@<Procedures for |open_input|@>
procedure open_input; {prepare to read |input_files|}
label done;
var i:integer;
begin
  @<Initialize extensions@>;
  @<Get the master file started@>;
  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
      @<Read change file name@>;
      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.

@<Initialize 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|.

@<Globals...@>=
@!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@>

@<Set init...@>=
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.

@<Procedures for |open_input|@>=
procedure init_change_file(i:integer;b:boolean);
label continue, done, exit;
begin
  with input_organization[i] do
  begin
    @<Skip over comment lines; |return| if end of file@>;
    @<Skip to the next nonblank line; |return| if end of file@>;
  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).

@<Skip over comment lines...@>=
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}.

@<Skip to the next nonblank line...@>=
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.

@<Initialize the input system@>=
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
  @<Init process@>;
  @<Handle output@>;
  @<Step to next line@>;
exit: end;


@ If |test_input| is |none|, no change file is tested.

@d none == (max_file_index+1)

@<Init process@>=
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<no_ch)) do
begin
  incr(i);
  case input_organization[i].mode of
  search: if not lines_dont_match(actual_input,i) then
	    begin
	      input_organization[i].mode:=test;
	      test_input:=i;
	    end;
  test:   begin
	    if lines_dont_match(actual_input, i) then
	      begin
		{error, modules do not match}
		input_organization[i].mode:=search;
		err_print('! Sections do not match')(actual_input);
@.Sections do not match@>
		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


@ @<Handle output@>=
if prod_chf then
begin
  loop @+ begin
    @<Test for normal@>;
    @<Test for pre@>;
    @<Test for post@>;
  end;
done:
end else
if test_input=none then put_line(actual_input)


@ Check whether we have to start a change file entry.

@<Test for normal@>=
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.

@<Test for pre@>=
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.

@<Test for post@>=
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


@ @<Step to next line@>=
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


@ @<Process the input@>=
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.

@<Check that all changes have been read@>=
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}
  @<Initialize the input system@>;
  @<Read master file name@>;
  open_input;
  @<Change or master file producing@>;
  @<Process the input@>;
  @<Check that all changes have been read@>;
end_of_TIE:
  {here files should be closed if the operating system requires it}
  @<Print the job |history|@>;
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@>

@<Print the job |history|@>=
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.
