188  PART 29: FILE NAMES  TEX82 §511

511. File names. It’s time now to fret about file names. Besides the fact that different operating systems treat files in different ways, we must cope with the fact that completely different naming conventions are used by different groups of people. The following programs show what is required for one particular operating system; similar routines for other systems are not difficult to devise.

TEX assumes that a file name has three parts: the name proper; its “extension”; and a “file area” where it is found in an external file system. The extension of an input file or a write file is assumed to be ‘ ’ unless otherwise specified; it is ‘ ’ on the transcript file that records each run of TEX; it is ‘ ’ on the font metric files that describe characters in the fonts TEX uses; it is ‘ ’ on the output files that specify typesetting information; and it is ‘ ’ on the format files written by to initialize TEX. The file area can be arbitrary on input files, but files are usually output to the user’s current area. If an input file cannot be found on the specified area, TEX will look for it on a special system area; this special area is intended for commonly used input files like .

Simple uses of TEX refer only to file names that have no explicit extension or area. For example, a person usually says ‘ ’ or ‘ ’ instead of ‘ ’ or ‘ ’. Simple file names are best, because they make the TEX source files portable; whenever a file name consists entirely of letters and digits, it should be treated in the same way by all implementations of TEX. However, users need the ability to refer to other files in their environment, especially when responding to error messages concerning unopenable files; therefore we want to let them use the syntax that appears in their favorite operating system.

512. In order to isolate the system-dependent aspects of file names, the system-independent parts of TEX are expressed in terms of three system-dependent procedures called begin name, more name, and end name. In essence, if the user-specified characters of the file name are c_{1} … c_{n}, the system-independent driver program does the operations

begin name; more name(c_{1}); …; more name(c_{n}); end name.

These three procedures communicate with each other via global variables. Afterwards the file name will appear in the string pool as three strings called cur name, cur area, and cur ext ; the latter two are null (i.e., ), unless they were explicitly specified by the user.

Actually the situation is slightly more complicated, because TEX needs to know when the file name ends. The more name routine is a function (with side effects) that returns true on the calls more name(c_{1}), …, more name(c_{n−1}). The final call more name(c_{n}) returns false; or, it returns true and the token following c_{n} is something like ‘ ’ (i.e., not a character). In other words, more name is supposed to return true unless it is sure that the file name has been completely scanned; and end name is supposed to be able to finish the assembly of cur name, cur area, and cur ext regardless of whether more name(c_{n}) returned true or false. ⟨ Global variables 13 ⟩ +≡ cur name: str number ; { name of file just scanned } cur area: str number ; { file area just scanned, or } cur ext : str number ; { file extension just scanned, or }

513. The file names we shall deal with for illustrative purposes have the following structure: If the name contains ‘ ’ or ‘ ’, the file area consists of all characters up to and including the final such character; otherwise the file area is null. If the remaining file name contains ‘ ’, the file extension consists of all such characters from the first remaining ‘ ’ to the end, otherwise the file extension is null.

§514  TEX82  PART 29: FILE NAMES 189

We can scan such file names easily by using two global variables that keep track of the occurrences of area and extension delimiters: ⟨ Global variables 13 ⟩ +≡ area delimiter : pool pointer ; { the most recent ‘ ’ or ‘ ’, if any } ext delimiter : pool pointer ; { the relevant ‘ ’, if any } 514. Input files that can’t be found in the user’s area may appear in a standard system area called TEX area. Font metric files whose areas are not given explicitly are assumed to appear in a standard system area called TEX font area. These system area names will, of course, vary from place to place.

define TEX area ≡

define TEX font area ≡

515. Here now is the first of the system-dependent routines for file name scanning. procedure begin name;

begin area delimiter ← 0; ext delimiter ← 0;

end;

516. And here’s the second. The string pool might change as the file name is being scanned, since a new might be entered; therefore we keep area delimiter and ext delimiter relative to the beginning of the current string, instead of assigning an absolute address like pool ptr to them. function more name(c : ASCII code): boolean;

begin if c = ␣ then more name ← false

else begin str room(1); append char (c); { contribute c to the current string }

if (c = ) ∨ (c = ) then begin area delimiter ← cur length; ext delimiter ← 0; end

else if (c = ) ∧ (ext delimiter = 0) then ext delimiter ← cur length;

more name ← true;

end; end;

517. The third. procedure end name;

begin if str ptr + 3 > max strings then overflow ( ␣ ␣ , max strings − init str ptr ); if area delimiter = 0 then cur area ← else begin cur area ← str ptr ; str start [str ptr + 1] ← str start [str ptr ] + area delimiter ; incr (str ptr );

end; if ext delimiter = 0 then

begin cur ext ← ; cur name ← make string ;

end else begin cur name ← str ptr ;

str start [str ptr + 1] ← str start [str ptr ] + ext delimiter − area delimiter − 1; incr (str ptr ); cur ext ← make string ; end;

end;

518. Conversely, here is a routine that takes three strings and prints a file name that might have produced them. (The routine is system dependent, because some operating systems put the file area last instead of first.) ⟨ Basic printing procedures 57 ⟩ +≡ procedure print file name(n, a, e : integer );

begin slow print (a); slow print (n); slow print (e);

end; 519. Another system-dependent routine is needed to convert three internal TEX strings into the name of file value that is used to open files. The present code allows both lowercase and uppercase letters in the file name.

define append to name(#) ≡ begin c ← #; incr (k); if k ≤ file name size then name of file[k] ← xchr [c]; end

procedure pack file name(n, a, e : str number );

var k: integer ; { number of positions filled in name of file } c: ASCII code; { character being packed } j: pool pointer ; { index into str pool }

begin k ← 0; for j ← str start [a] to str start [a + 1] − 1 do append to name(so(str pool [j])); for j ← str start [n] to str start [n + 1] − 1 do append to name(so(str pool [j])); for j ← str start [e] to str start [e + 1] − 1 do append to name(so(str pool [j])); if k ≤ file name size then name length ← k else name length ← file name size; for k ← name length + 1 to file name size do name of file[k] ← ´ ␣´ ; end;

520. A messier routine is also needed, since format file names must be scanned before TEX’s string mechanism has been initialized. We shall use the global variable TEX format default to supply the text for default system areas and extensions related to format files.

define format default length = 20 { length of the TEX format default string } define format area length = 11 { length of its area part } define format ext length = 4 { length of its ‘ ’ part } define format extension = { the extension, as a constant }

⟨ Global variables 13 ⟩ +≡ TEX format default : packed array [1 . . format default length] of char ;

521. ⟨ Set initial values of key variables 21 ⟩ +≡ TEX format default ← ´ ´ ;

522. ⟨ Check the “constant” values for consistency 14 ⟩ +≡ if format default length > file name size then bad ← 31; 523. Here is the messy routine that was just mentioned. It sets name of file from the first n characters of TEX format default , followed by buffer [a . . b], followed by the last format ext length characters of TEX format default .

We dare not give error messages here, since TEX calls this routine before the error routine is ready to roll. Instead, we simply drop excess characters, since the error will be detected in another way when a strange file name isn’t found. procedure pack buffered name(n : small number ; a, b : integer );

var k: integer ; { number of positions filled in name of file } c: ASCII code; { character being packed } j: integer ; { index into buffer or TEX format default }

begin if n + b − a + 1 + format ext length > file name size then

b ← a + file name size − n − 1 − format ext length; k ← 0; for j ← 1 to n do append to name(xord [TEX format default [j]]); for j ← a to b do append to name(buffer [j]); for j ← format default length − format ext length + 1 to format default length do

append to name(xord [TEX format default [j]]); if k ≤ file name size then name length ← k else name length ← file name size; for k ← name length + 1 to file name size do name of file[k] ← ´ ␣´ ; end;

524. Here is the only place we use pack buffered name. This part of the program becomes active when a “virgin” TEX is trying to get going, just after the preliminary initialization, or when the user is substituting another format file by typing ‘ ’ after the initial ‘ ’ prompt. The buffer contains the first line of input in buffer [loc . . (last − 1)], where loc < last and buffer [loc] ≠ ␣ . ⟨ Declare the function called open fmt file 524 ⟩ ≡ function open fmt file: boolean;

label found , exit ; var j: 0 . . buf size; { the first space after the format file name } begin j ← loc; if buffer [loc] = then

begin incr (loc); j ← loc; buffer [last ] ← ␣ ;

while buffer [j] ≠ ␣ do incr (j);

pack buffered name(0, loc, j − 1); { try first without the system file area }

if w open in(fmt file) then goto found ;

pack buffered name(format area length, loc, j − 1); { now try the system format file area } if w open in(fmt file) then goto found ; wake up terminal ; wterm ln(´ ␣ ␣ ´ ´ ␣ ␣ ␣ ´ , ´ ␣ ␣ ␣ ´ ); update terminal ; end; { now pull out all the stops: try for the system file }

pack buffered name(format default length − format ext length, 1, 0); if ¬w open in(fmt file) then

begin wake up terminal ; wterm ln(´ ␣ ´ ´ ␣ ␣ ␣ ␣ ␣ ´ ); open fmt file ← false; return; end;

found : loc ← j; open fmt file ← true; exit : end;

This code is used in section 1303.

525. Operating systems often make it possible to determine the exact name (and possible version number) of a file that has been opened. The following routine, which simply makes a TEX string from the value of name of file, should ideally be changed to deduce the full name of file f, which is the file most recently opened, if it is possible to do this in a Pascal program.

This routine might be called after string memory has overflowed, hence we dare not use ‘str room’. function make name string : str number ;

var k: 1 . . file name size; { index into name of file }

begin if (pool ptr + name length > pool size) ∨ (str ptr = max strings ) ∨ (cur length > 0) then

make name string ← else begin for k ← 1 to name length do append char (xord [name of file[k]]);

make name string ← make string ;

end; end;

function a make name string (var f : alpha file): str number ; begin a make name string ← make name string ; end;

function b make name string (var f : byte file): str number ; begin b make name string ← make name string ; end;

function w make name string (var f : word file): str number ; begin w make name string ← make name string ; end;

526. Now let’s consider the “driver” routines by which TEX deals with file names in a system-independent manner. First comes a procedure that looks for a file name in the input by calling get x token for the information. procedure scan file name;

label done;

begin name in progress ← true; begin name; ⟨ Get the next non-blank non-call token 406 ⟩; loop begin if (cur cmd > other char ) ∨ (cur chr > 255) then { not a character }

begin back input ; goto done; end;

if ¬more name(cur chr ) then goto done; get x token; end;

done: end name; name in progress ← false; end;

527. The global variable name in progress is used to prevent recursive use of scan file name, since the begin name and other procedures communicate via global variables. Recursion would arise only by devious tricks like ‘ ’; such attempts at sabotage must be thwarted. Furthermore, name in progress prevents from being initiated when a font size specification is being scanned.

Another global variable, job name, contains the file name that was first by the user. This name is extended by ‘ ’ and ‘ ’ and ‘ ’ in the names of TEX’s output files. ⟨ Global variables 13 ⟩ +≡ name in progress : boolean; { is a file name being scanned? } job name: str number ; { principal file name } log opened : boolean; { has the transcript file been opened? } 528. Initially job name = 0; it becomes nonzero as soon as the true name is known. We have job name = 0 if and only if the ‘ ’ file has not been opened, except of course for a short time just after job name has become nonzero. ⟨ Initialize the output routines 55 ⟩ +≡

job name ← 0; name in progress ← false; log opened ← false;

529. Here is a routine that manufactures the output file names, assuming that job name ≠ 0. It ignores and changes the current settings of cur area and cur ext .

define pack cur name ≡ pack file name(cur name, cur area, cur ext ) procedure pack job name(s : str number ); { s = , , or format extension }

begin cur area ← ; cur ext ← s; cur name ← job name; pack cur name; end;

530. If some trouble arises when TEX tries to open a file, the following routine calls upon the user to supply another file name. Parameter s is used in the error message to identify the type of file; parameter e is the default extension if none is given. Upon exit from the routine, variables cur name, cur area, cur ext , and name of file are ready for another attempt at file opening. procedure prompt file name(s, e : str number );

label done; var k: 0 . . buf size; { index into buffer } begin if interaction = scroll mode then wake up terminal ; if s = ␣ ␣ then print err ( ␣ ´ ␣ ␣ ␣` ) else print err ( ␣ ´ ␣ ␣ ␣ ␣` ); print file name(cur name, cur area, cur ext ); print ( ´ ); if e = then show context ; print nl ( ␣ ␣ ␣ ); print (s); if interaction < scroll mode then fatal error ( ␣ ␣ ␣ ␣ ␣ ␣ ␣ ); clear terminal ; prompt input ( ␣ ); ⟨ Scan file name in the buffer 531 ⟩; if cur ext = then cur ext ← e; pack cur name; end;

531. ⟨ Scan file name in the buffer 531 ⟩ ≡ begin begin name; k ← first ; while (buffer [k] = ␣ ) ∧ (k < last ) do incr (k); loop begin if k = last then goto done;

if ¬more name(buffer [k]) then goto done; incr (k); end;

done: end name;

end

This code is used in section 530.

532. Here’s an example of how these conventions are used. Whenever it is time to ship out a box of stuff, we shall use the macro ensure dvi open.

define ensure dvi open ≡

if output file name = 0 then begin if job name = 0 then open log file; pack job name( ); while ¬b open out (dvi file) do prompt file name( ␣ ␣ ␣ , ); output file name ← b make name string (dvi file); end

⟨ Global variables 13 ⟩ +≡ dvi file: byte file; { the device-independent output goes here } output file name: str number ; { full name of the output file } log name: str number ; { full name of the log file }

533. ⟨ Initialize the output routines 55 ⟩ +≡ output file name ← 0;

534. The open log file routine is used to open the transcript file and to help it catch up to what has previously been printed on the terminal. procedure open log file;

var old setting : 0 . . max selector ; { previous selector setting } k: 0 . . buf size; { index into months and buffer } l: 0 . . buf size; { end of first input line } months : packed array [1 . . 36] of char ; { abbreviations of month names }

begin old setting ← selector ; if job name = 0 then job name ← ; pack job name( ); while ¬a open out (log file) do ⟨ Try to get a different log file name 535 ⟩; log name ← a make name string (log file); selector ← log only ; log opened ← true; ⟨ Print the banner line, including the date and time 536 ⟩; input stack [input ptr ] ← cur input ; { make sure bottom level is in memory } print nl ( ); l ← input stack [0].limit field ; { last position of first line } if buffer [l] = end line char then decr (l); for k ← 1 to l do print (buffer [k]); print ln; { now the transcript file contains the first line of input } selector ← old setting + 2; { log only or term and log } end;

535. Sometimes open log file is called at awkward moments when TEX is unable to print error messages or even to show context . The prompt file name routine can result in a fatal error , but the error routine will not be invoked because log opened will be false.

The normal idea of batch mode is that nothing at all should be written on the terminal. However, in the unusual case that no log file could be opened, we make an exception and allow an explanatory message to be seen.

Incidentally, the program always refers to the log file as a ‘ ’, because some systems cannot use the extension ‘ ’ for this file. ⟨ Try to get a different log file name 535 ⟩ ≡

begin selector ← term only ; prompt file name( ␣ ␣ , ); end

This code is used in section 534.

536. ⟨ Print the banner line, including the date and time 536 ⟩ ≡ begin wlog (banner ); slow print (format ident ); print ( ␣␣ ); print int (day ); print char ( ␣ ); months ← ´ ´ ; for k ← 3 ∗ month − 2 to 3 ∗ month do wlog (months [k]); print char ( ␣ ); print int (year ); print char ( ␣ ); print two(time div 60); print char ( ); print two(time mod 60); end

This code is used in section 534.

537. Let’s turn now to the procedure that is used to initiate file reading when an ‘ ’ command is being processed. procedure start input ; { TEX will something }

label done; begin scan file name; { set cur name to desired file name } if cur ext = then cur ext ← ; pack cur name; loop begin begin file reading ; { set up cur file and new level of input }

if a open in(cur file) then goto done;

if cur area = then begin pack file name(cur name, TEX area, cur ext ); if a open in(cur file) then goto done; end;

end file reading ; { remove the level that didn’t work }

prompt file name( ␣ ␣ , );

end; done: name ← a make name string (cur file);

if job name = 0 then begin job name ← cur name; open log file; end; { open log file doesn’t show context , so limit and loc needn’t be set to meaningful values yet }

if term offset + length(name) > max print line − 2 then print ln else if (term offset > 0) ∨ (file offset > 0) then print char ( ␣ ); print char ( ); incr (open parens ); slow print (name); update terminal ; state ← new line; if name = str ptr − 1 then { we can conserve string pool space now }

begin flush string ; name ← cur name;

end; ⟨ Read the first line of the new file 538 ⟩; end;

538. Here we have to remember to tell the input ln routine not to start with a get . If the file is empty, it is considered to contain a single blank line. ⟨ Read the first line of the new file 538 ⟩ ≡

begin line ← 1; if input ln(cur file, false) then do nothing ; firm up the line; if end line char inactive then decr (limit ) else buffer [limit ] ← end line char ; first ← limit + 1; loc ← start ; end

This code is used in section 537.