Software Tools in Free Pascal

This document briefly explains my attempt to implement the programs written in the book Software Tools in Pascal[KP81] by Brian W. Kernighan and P. J. Plauger, a follow up to their earlier Software Tools[KP76]. A Bell Labs portability kit is distributed as Plan 9 from User Space, replacing the original research Unix kit. Finally, an implementation has been done in Haskell, http://www.crsr.net/Programming_Languages/SoftwareTools/.

The Pascal of the book refers to Revised Pascal as published in the Pascal User Manual and Report, specifically the Second Edition, first printed in 1974 (mentioned in Software Tools in Pascal in terms of the 1978 printing) commonly called J&W Pascal.

I started by studying Fortran and PL/1 in the original version, wanting to work through it first, before moving to Pascal. It became readily apparent that Ratfor was no longer needed since Fortran 90, a new, Modula-2 (Pascal's successor) influenced, language was now standard. After the book's suggestion to hand translate Ratfor into Fortran or PL/1 (if a Ratfor pre-processor was not available), I was reminded of Wirth's similar description of translating a Pascal compiler written in Pascal to the CDC-6000. After originally attempting to write the compiler in Fortran IV/66 (the available Algol compiler was poorly implemented and assembler code was considered dishonorable), and failing because of inadequacy of the language, they rewrote the compiler in 1970 in a Pascal subset, then hand translated this into a syntax-sugared, low-level language that had an available compiler. Once they had the boot strapped compiler, they could build the bigger one. At this point, it became clear to me that the Fortran of the book was a relic of the 1950s, and PL/1 a product of overcomplexity from the 1960s, the former surpassed by Algol, and the latter by C and Pascal. Yet C is its own monster, and C++ implements ideas that Pascal early implemented then refined. The original Software Tools is no longer necessary in context of the ellaborated on and modernized Pascal version. I had less interest in pursuing Fortran 90 (and its successors), and forever remain enamored of the Algol language world typified by Pascal and its successors. The weaknesses of Pascal that the book explains (and complains about) could be revisted in Modula-2 at a later date.

Getting Started

From time to time, Software Tools requires that primitives be built for the programs to function as intended. In most cases, I used the UCB Pascal primitives, or updated them to work with Free Pascal. Kernighan's descriptions leave out some interesting assumptions that are compiler specific for which I had to track down a copy of the UCB manual to identify the details of the assumption (e.g. blank padding).

Software Tools and Software Tools in Pascal both had tapes, last available for download from the plan9/cm website, which is now defunct. (See plan9.io for a partial mirror.) I was able to obtain the Ratfor Fortran source in time, and built it with GNU Fortran for GNU/Linux. The C version of Ratfor might be tracked down from the Unix Heritage site, as well as one written by Oz from Stanford. (The 4.3BSD Tahoe release has the UCB Pascal interpreter code in a portable edition that might be buildable as well.) The Pascal tape is now available from Kernighan's Princeton mirror of the Bell Labs' material. Having said that, in general, the books must be followed without prebuilt software being available, even though it seems intended that a professor will have provided everything necessary for a student to use. I have posted the book versions of necessary tools (e.g. ratfor, include, define) for Linux.

Using FPC's ISO mode, I found building the tools very easy. For instance, the program example for copyprog was buildable, on all supported platforms, with the following:

fpc -Miso -Xst -v0 -l- -ocopyprog wholecopy.p

This is essentially the copytext program provided as an example in the User Manual and Report, Second Edition, pg. 164 (1978 Springer Study Edition), or section 13 of the Report.

Under ACK, this can be done as follows:

ack -o copyprog wholecopy.p

This assumes the RPM I generated for RHEL 7 from the ackit 6.1 alpha code tree, and the correct configuration of the ACKDIR, ACKM, and ACKFE variables. I used the following shell profile configuration:

ACKDIR=/usr
ACKFE=/usr/share/ack/descr/fe
ACKM=linux386
export ACKDIR ACKFE ACKM

The Pascal-p system can be used, (though its strict implementation makes it unsuitable for most of the file and argument primitives). With S. A. Moore's build, I was able to get the following to work:

pcom <wholecopy.p

Copy the resulting prr file to prd, then run the pint command to run the program.

Finally, the p2c package, such as is found on Slackware, is a conversion tool for translating Pascal (or Modula-2) to C. It seems more suitable for converting an existing project to C, with expected code changes, than for being used as an actual compiler. At the least, I was unable, presumably due to I/O bugginess using p2cc, to get copyprog working without error:

$ p2cc -o copy copyprog.p
copyprog..c:63:1: warning: return type defaults to 'int' 
[-Wimplicit-int]
 main(int argc, Char *argv[])
 ^
$ chmod a+rx copy
$ ./copy
hello, world
hello, world


^C
$ ./copy
hello, world
hello, world
Pascal system I/O error 30 (end-of-file)

The first run above was on Slackware. After typing hello, world, I typed Ctrl plus d, which is the normal way to signal the end-of-file on Unix (use the cat command as an example), but it didn't work, so I had to type Ctrl plus c. After the second run, I typed Ctrl plus d twice, resulting in the error.

Counting tools

charcount requires the putdec procedure, which is not introduced in chapter 1, but the standard procedure write, if it is fully supported, can be used, as described on page 57:

{ putdec(nc, 1) }
write(nc:1)

This can be done until you arrive at the end of chapter 2 where putdec is described. I found it best to move along with the book in progression, instead of building other components that hadn't been introduced. Use the example of copyprog to assemble each procedure and function into a single program file.

The putdec procedure keeps consistent the use of the standard interface with the character type. Pascal has had the difficulty of often being provided in terms of the P2 subset interpreter, and thus compilers often did not always provide the standard write procedure (as the standard get and put procedures are often not available to construct them), which freely mixes integer and char. Since Free Pascal 2.6, an ISO mode has been made available, which is maturing, and as of 3.1.1 is feature complete.

Compilers based on INCITS/ISO/IEC 7185:1990[S2018], such as FPC's ISO mode, should be compatible with the code of the book. Kernighan was careful to only use a compatible subset of the definition of the 1978 (final) Report, the ANSI draft standard, and existing implementations (see pg 28-29). This approach of using a compatible subset also explains the primitives approach of both books, which at first seem redundant, but ultimately become clear in practice as the only way to handle portability between implementations, as well as provide the opportunity to tweak the efficiency of those primitives (sometimes due to inadequacies of a compiler).

#include

The include command is not provided until chapter 3, yet its use is introduced in the last program of chapter 1 (detab). Its use is implied with charcount, and thereafter, with the assumption of the availability of putdec, such as through the inclusion of globdefs.p, prims.p, and utility.p, (see the UCB wrapper example on pg. 322). If the include command is available, I found it easiest to take this in steps, as with this command example:

<detab.p include >detab.pas

I then filled in the rest of the context for detab.pas. This is described in greater detail in section 3.3, on pg 71. Of course, Free Pascal's $include can be used instead (similar to the PL/1 example of pg. 75 of the original Software Tools), but I found this made fixing mistakes harder as the line numbers didn't match up in error messages. (Some early Pascal systems, such as that provided for the Atari 800 XL, had #include as a built-in.) Wirth's CDC compiler used external references to independently compiled libraries (i.e. object files, such as can be independently compiled with the -c flag of the c99 or gfortran commands). This is consistent with the Whitesmith's and ACK example in the appendix. Free Pascal supports a similar external referencing feature if you write libraries with another compiler.

getarg

The original Software Tools recognized the common in and out simplicity of early computers: punch card (or tape) input and teletype printer output. Dumb terminals changed this to a keyboard direct input and TV (CRT) monitor screen output (which the first tool copyprog hints at). As the Appendix of the original Software Tools indicates, it may be necessary to use an input file to present the arguments for the program. The Revised Pascal of the Second Edition(s) Manual and Report (1974, 1978) had a program header that defined the computer's standard input and output files, and allowed the declaration of other external files (see Software Tools in Pascal, pg. 64). Unextended, Pascal is thus restricted to an input file for argument which getarg must be built around. The UCSD example primitives in Software Tools in Pascal show how to build the getarg primitive with its inherent command interface.

The getarg function under Free Pascal required modifications to the UCB example in the Appendix on page 331. Instead of argv and argc, paramcount and paramstr must be used. Replace (n < argc) with (n <= paramcount), and argv(n, arg) with arg := paramstr(n).

See the UCB globdefs.p example in the Appendix for the string type.

message and error

Though a goto and label could be used for each specific program, or even a branch with a simpler program where a writeln is at the end (as I did with my refactoring of the crypt example from Why Pascal Is Not My Favorite Language[Ker81]), an error function is fairly simple in Free Pascal. First, Free Pascal provides a halt statement, the same as is described in the User Manual and Report, Second Edition. Using the Free Pascal shortstring type, and writeln for directing output to STDERR, the macros suggested by the book can be avoided:

  PROCEDURE message (CONST s: shortstring);
  BEGIN writeln(openlist[STDERR].filevar, s)
  END; 
  PROCEDURE error (CONST s: shortstring);
  BEGIN message(s); halt
  END; 

Free Pascal can also write to erroutput (instead of the initialized STDIN/STDOUT/STDERR environment of the Appendix UCB primitives).

compare0

Currently, compare0 is not possible with Free Pascal 3.0.4, as the use of files in the program header does not allow the required type declaration. Program header functionality has been introduced in 3.1.1, but does not appear to be sufficiently bug free for this program to work.

The function getline as provided in the Appendix opens up a whole can of worms for other procedures not yet discussed. Unless the entirety of chapter 3's primitives are complete, it is best to follow the directions in the chapter to build this function with getc, (which did not seem as clear to me how to do this as claimed).

compare

The first thing needed for compare is the open primitive from UCB. This primitive pads intname with blanks, which is not explained in the Appendix, but which makes sense once comparing against the UCB BSD Unix manual. The for loop should be removed for Free Pascal.

The second thing is that Free Pascal uses the standard reset and rewrite commands, so the extended syntax cannot be used. Instead, use the Free Pascal assign procedure:

  assign(openlist[i].filevar, intname);
  IF (mode = IOREAD) THEN RESET(openlist[i].filevar)
  ELSE REWRITE(openlist[i].filevar);

A fix for the return status deficiency of the UCB example can be tested against ioresult at the end of the procedure:

  IF (ioresult <> 0) THEN open := IOERROR

This requires that { $i+ } preceeds, and { $i- } succeeds the procedure.

The open procedure relies on initio from pg. 326. In Free Pascal on Unix and GNU/Linux, instead of assigning /dev/tty to STDERR, use a blank string: assign(openlist[STDERR].filevar, ''). The rest can be taken verbatim from the UCB primitives.

The file descriptors described here seem confusing to some, however, this seems like a simple approach. It allows for the amount of files to be numbered, and makes it simpler for assigning the internal file name in a way that can count what the maximum file amount is. The MAXOPEN variable for file handle count has a small number, but a modern OS can easily deal with thousands of files. In GNU/Linux, see the file /proc/sys/fs/file-max, printed using:

 sysctl fs.file-max

xclose

Free Pascal has a built-in close procedure, which name collides with close.p (first used in include.p), requiring the procedure to be called xclose (otherwise it calls itself recursively). Until the macro tool of chapter 8 is built, and the #define shown on page 340 for the UCSD wrapper can be used (using the macro or define syntax. See pg. 280 or 305), any program that uses xclose will have to be manually edited so uses of close are changed to xclose. These are the following:

outer.p

Assembling a program following the instructions in Chapter 3, and the Appendix, using the described outer.p template, making the assembled primitive files in their particular directories (resulting in the described globdefs.p, prims.p, and utility.p primitive files), and updating the outer.p file to include copy.p and call the appropriate main program, assembly of the programs should be fairly easy. On Windows, I made the following batch script for use with the Free Pascal 3.1.1 x64 cross compiler:

include <outer.p >copy.pas
ppcrossx64 -Miso copy.pas
del *.o
rem comment the below to debug:
del copy.pas

As the Appendix notes, this is inefficient for small programs that don't use all the primitives and utilities. Something similar to the Whitesmith's example can also be used with Free Pascal's external, for function declarations. This is likely the closest to CDC 6000 3.4 Pascal of the Manual & Report, except that many of these utilities and some primitives are built using Pascal, so this is more about independent compilation in a build system than taking advantage of another language to supplement Pascal, of which most of Free Pascal's built-in functions are already suitable.

References

[Ker81]
B. W. Kernighan, Why Pascal is Not My Favorite Programming Language, AT&T Bell Laboratories, Computing Science Technical Report No. 100, 2 April 1981
[KP76]
B. W. Kernighan, P. J. Plauger, Software Tools, Addison-Wesley, 1976
[KP81]
B. W. Kernighan, P. J. Plauger, Software Tools in Pascal, Addison-Wesley, 1981

©2016-2019 David Egan Evans.