§25  TEX82  PART 3: INPUT AND OUTPUT 13

25. Input and output. The bane of portability is the fact that different operating systems treat input and output quite differently, perhaps because computer scientists have not given sufficient attention to this problem. People have felt somehow that input and output are not part of “real” programming. Well, it is true that some kinds of programming are more fun than others. With existing input/output conventions being so diverse and so messy, the only sources of joy in such parts of the code are the rare occasions when one can find a way to make the program a little less bad than it might have been. We have two choices, either to attack I/O now and get it over with, or to postpone I/O until near the end. Neither prospect is very attractive, so let’s get it over with.

The basic operations we need to do are (1) inputting and outputting of text, to or from a file or the user’s terminal; (2) inputting and outputting of eight-bit bytes, to or from a file; (3) instructing the operating system to initiate (“open”) or to terminate (“close”) input or output from a specified file; (4) testing whether the end of an input file has been reached.

TEX needs to deal with two kinds of files. We shall use the term alpha file for a file that contains textual data, and the term byte file for a file that contains eight-bit binary information. These two types turn out to be the same on many computers, but sometimes there is a significant distinction, so we shall be careful to distinguish between them. Standard protocols for transferring such files from computer to computer, via high-speed networks, are now becoming available to more and more communities of users.

The program actually makes use also of a third kind of file, called a word file, when dumping and reloading base information for its own initialization. We shall define a word file later; but it will be possible for us to specify simple operations on word files before they are defined. ⟨ Types in the outer block 18 ⟩ +≡

eight bits = 0 . . 255; { unsigned one-byte quantity }

alpha file = packed file of text char ; { files that contain textual data }

byte file = packed file of eight bits ; { files that contain binary data }

26. Most of what we need to do with respect to input and output can be handled by the I/O facilities that are standard in Pascal, i.e., the routines called get , put , eof , and so on. But standard Pascal does not allow file variables to be associated with file names that are determined at run time, so it cannot be used to implement TEX; some sort of extension to Pascal’s ordinary reset and rewrite is crucial for our purposes. We shall assume that name of file is a variable of an appropriate type such that the Pascal run-time system being used to implement TEX can open a file whose external name is specified by name of file. ⟨ Global variables 13 ⟩ +≡ name of file: packed array [1 . . file name size] of char ;

{ on some systems this may be a record variable } name length: 0 . . file name size;

14  PART 3: INPUT AND OUTPUT  TEX82 §27

{ this many characters are actually relevant in name of file (the rest are blank) } 27. The Pascal-H compiler with which the present version of TEX was prepared has extended the rules of Pascal in a very convenient way. To open file f, we can write

reset (f, name, ´ ´ )  for input;
rewrite(f, name, ´ ´ )  for output.

The ‘name’ parameter, which is of type ‘packed array [⟨any ⟩] of char ’, stands for the name of the external file that is being opened for input or output. Blank spaces that might appear in name are ignored.

The ‘ ’ parameter tells the operating system not to issue its own error messages if something goes wrong. If a file of the specified name cannot be found, or if such a file cannot be opened for some other reason (e.g., someone may already be trying to write the same file), we will have erstat (f) ≠ 0 after an unsuccessful reset or rewrite. This allows TEX to undertake appropriate corrective action.

TEX’s file-opening procedures return false if no file identified by name of file could be opened. define reset OK (#) ≡ erstat (#) = 0 define rewrite OK (#) ≡ erstat (#) = 0

function a open in(var f : alpha file): boolean; { open a text file for input } begin reset (f, name of file, ´ ´ ); a open in ← reset OK (f); end;

function a open out (var f : alpha file): boolean; { open a text file for output } begin rewrite(f, name of file, ´ ´ ); a open out ← rewrite OK (f); end;

function b open in(var f : byte file): boolean; { open a binary file for input } begin reset (f, name of file, ´ ´ ); b open in ← reset OK (f); end;

function b open out (var f : byte file): boolean; { open a binary file for output } begin rewrite(f, name of file, ´ ´ ); b open out ← rewrite OK (f); end;

function w open in(var f : word file): boolean; { open a word file for input } begin reset (f, name of file, ´ ´ ); w open in ← reset OK (f); end;

function w open out (var f : word file): boolean; { open a word file for output } begin rewrite(f, name of file, ´ ´ ); w open out ← rewrite OK (f); end;

28. Files can be closed with the Pascal-H routine ‘close(f)’, which should be used when all input or output with respect to f has been completed. This makes f available to be opened again, if desired; and if f was used for output, the close operation makes the corresponding external file appear on the user’s area, ready to be read.

These procedures should not generate error messages if a file is being closed before it has been successfully opened. procedure a close(var f : alpha file); { close a text file }

begin close(f); end;

procedure b close(var f : byte file); { close a binary file } begin close(f); end;

procedure w close(var f : word file); { close a word file } begin close(f); end; 29. Binary input and output are done with Pascal’s ordinary get and put procedures, so we don’t have to make any other special arrangements for binary I/O. Text output is also easy to do with standard Pascal routines. The treatment of text input is more difficult, however, because of the necessary translation to ASCII code values. TEX’s conventions should be efficient, and they should blend nicely with the user’s operating environment.

30. Input from text files is read one line at a time, using a routine called input ln. This function is defined in terms of global variables called buffer , first , and last that will be described in detail later; for now, it suffices for us to know that buffer is an array of ASCII code values, and that first and last are indices into this array representing the beginning and ending of a line of text. ⟨ Global variables 13 ⟩ +≡ buffer : array [0 . . buf size] of ASCII code; { lines of characters being read } first : 0 . . buf size; { the first unused position in buffer } last : 0 . . buf size; { end of the line just input to buffer } max buf stack : 0 . . buf size; { largest index used in buffer } 31. The input ln function brings the next line of input from the specified file into available positions of the buffer array and returns the value true, unless the file has already been entirely read, in which case it returns false and sets last ← first . In general, the ASCII code numbers that represent the next line of the file are input into buffer [first ], buffer [first + 1], …, buffer [last − 1]; and the global variable last is set equal to first plus the length of the line. Trailing blanks are removed from the line; thus, either last = first (in which case the line was entirely blank) or buffer [last − 1] ≠ ␣ .

An overflow error is given, however, if the normal actions of input ln would make last ≥ buf size; this is done so that other parts of TEX can safely look at the contents of buffer [last + 1] without overstepping the bounds of the buffer array. Upon entry to input ln, the condition first < buf size will always hold, so that there is always room for an “empty” line.

The variable max buf stack , which is used to keep track of how large the buf size parameter must be to accommodate the present job, is also kept up to date by input ln.

If the bypass eoln parameter is true, input ln will do a get before looking at the first character of the line; this skips over an eoln that was in f↑. The procedure does not do a get when it reaches the end of the line; therefore it can be used to acquire input from the user’s terminal as well as from ordinary text files.

Standard Pascal says that a file should have eoln immediately before eof , but TEX needs only a weaker restriction: If eof occurs in the middle of a line, the system function eoln should return a true result (even though f↑ will be undefined).

Since the inner loop of input ln is part of TEX’s “inner loop”—each character of input comes in at this place—it is wise to reduce system overhead by making use of special routines that read in an entire array of characters at once, if such routines are available. The following code uses standard Pascal to illustrate what needs to be done, but finer tuning is often possible at well-developed Pascal sites. function input ln(var f : alpha file; bypass eoln : boolean): boolean;

{ inputs the next line or returns false } var last nonblank : 0 . . buf size; { last with trailing blanks removed }

begin if bypass eoln then

if ¬eof (f) then get (f); { input the first character of the line into f↑ } last ← first ; { cf. Matthew 19 : 30 } if eof (f) then input ln ← false else begin last nonblank ← first ;

while ¬eoln(f) do

begin if last ≥ max buf stack then begin max buf stack ← last + 1; if max buf stack = buf size then ⟨ Report overflow of the input buffer, and abort 35 ⟩; end;

buffer [last ] ← xord [f↑]; get (f); incr (last );

if buffer [last − 1] ≠ ␣ then last nonblank ← last ; end;

last ← last nonblank ; input ln ← true;

end; end;

32. The user’s terminal acts essentially like other files of text, except that it is used both for input and for output. When the terminal is considered an input file, the file variable is called term in, and when it is considered an output file the file variable is term out . ⟨ Global variables 13 ⟩ +≡ term in: alpha file; { the terminal as an input file } term out : alpha file; { the terminal as an output file } 33. Here is how to open the terminal files in Pascal-H. The ‘ ’ switch suppresses the first get .

define t open in ≡ reset (term in, ´ ´ , ´ ´ ) { open the terminal for text input }

define t open out ≡ rewrite(term out , ´ ´ , ´ ´ ) { open the terminal for text output }

34. Sometimes it is necessary to synchronize the input/output mixture that happens on the user’s terminal, and three system-dependent procedures are used for this purpose. The first of these, update terminal , 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. The second, clear terminal , is called when we wish to cancel any input that the user may have typed ahead (since we are about to issue an unexpected error message). The third, wake up terminal , is supposed to revive the terminal if the user has disabled it by some instruction to the operating system. The following macros show how these operations can be specified in Pascal-H:

define update terminal ≡ break (term out ) { empty the terminal output buffer }

define clear terminal ≡ break in(term in, true) { clear the terminal input buffer }

define wake up terminal ≡ do nothing { cancel the user’s cancellation of output }

35. We need a special routine to read the first line of TEX input from the user’s terminal. This line is different because it is read before we have opened the transcript file; there is sort of a “chicken and egg” problem here. If the user types ‘ ’ on the first line, or if some macro invoked by that line does such an , the transcript file will be named ‘ ’; but if no commands are performed during the first line of terminal input, the transcript file will acquire its default name ‘ ’. (The transcript file will not contain error messages generated by the first line before the first command.)

The first line is even more special if we are lucky enough to have an operating system that treats TEX differently from a run-of-the-mill Pascal object program. It’s nice to let the user start running a TEX job by typing a command line like ‘ ’; in such a case, TEX will operate as if the first line of input were ‘ ’, i.e., the first line will consist of the remainder of the command line, after the part that invoked TEX.

The first line is special also because it may be read before TEX has input a format file. In such cases, normal error messages cannot yet be given. The following code uses concepts that will be explained later. (If the Pascal compiler does not support non-local goto, the statement ‘goto final end ’ should be replaced by something that quietly terminates the program.) ⟨ Report overflow of the input buffer, and abort 35 ⟩ ≡

if format ident = 0 then begin write ln(term out , ´ ␣ ␣ ´ ); goto final end ; end

else begin cur input .loc field ← first ; cur input .limit field ← last − 1; overflow ( ␣ , buf size); end

This code is used in section 31.

36. Different systems have different ways to get started. But regardless of what conventions are adopted, the routine that initializes the terminal should satisfy the following specifications:

1) It should open file term in for input from the terminal. (The file term out will already be open for output to the terminal.)

2) If the user has given a command line, this line should be considered the first line of terminal input. Otherwise the user should be prompted with ‘ ’, and the first line of input should be whatever is typed in response.

3) The first line of input, which might or might not be a command line, should appear in locations first to last − 1 of the buffer array.

4) The global variable loc should be set so that the character to be read next by TEX is in buffer [loc].

This character should not be blank, and we should have loc < last . (It may be necessary to prompt the user several times before a non-blank line comes in. The prompt is ‘ ’ instead of the later ‘ ’ because the meaning is slightly different: ‘ ’ need not be typed immediately after ‘ ’.)

define loc ≡ cur input .loc field { location of first unread character in buffer }

37. The following program does the required initialization without retrieving a possible command line. It should be clear how to modify this routine to deal with command lines, if the system permits them. function init terminal : boolean; { gets the terminal input started }

label exit ;

begin t open in;

loop begin wake up terminal ; write(term out , ´ ´ ); update terminal ;

if ¬input ln(term in, true) then { this shouldn’t happen } begin write ln(term out ); write(term out , ´ ␣ ␣ ␣ ␣ ␣ ␣ ␣ ´ ); init terminal ← false; return; end; loc ← first ; while (loc < last ) ∧ (buffer [loc] = ␣ ) do incr (loc); if loc < last then

begin init terminal ← true; return; { return unless the line was all blank }

end; write ln(term out , ´ ␣ ␣ ␣ ␣ ␣ ␣ ␣ ´ ); end;

exit : end;