Our second CLEAN program, lifeconvert, indicates the format for character-based application programs. The Start rule is not an expression to be printed, but a state transition function to be applied to the outside world. This program converts a file containing a list of points to the PostScript format described in section 6.
The WORLD abstract data type describes the world outside the program, containing among other things the file system. The Unix command-line argument vector would logically also be part of this abstract data type, but cannot be accessed by CLEAN at present.
MODULE lifeconvert; IMPORT lifeio; RULE :: Start UNQ WORLD -> UNQ WORLD;
The result of this program is an ABORT message if there are errors in opening or closing files. The first step in calculating the result world'' of the program is to use OpenFiles to extract the file system from the world, and in turn to extract the standard input/output file StdIO from the file system. The variable f1 contains the file descriptor for this file.
Start world -> ABORT (Concat ["Can't open '", input, "'\n"]), IF NOT okinput -> ABORT (Concat ["No cells found in '", input, "'\n"]), IF = numcells 0 -> ABORT (Concat ["Can't open '", output, "'\n"]), IF NOT okopen -> ABORT (Concat ["Can't close '", output, "'\n"]), IF NOT okclose -> world'', (filesystem1, world'): OpenFiles world, (f1, filesystem2): StdIO filesystem1,
The standard input/output file is then modified using single-threaded operations which output a prompt, and read a line of input. This line of input is a string which contains a terminating new-line character, so the DropNewLine macro defined at the end of the program is used to remove the last character of the string. The style of functional programming needed here seems a little strange at first, but provides a useful discipline for the programmer. We use f1, f2, etc. for the thread of file values for the standard input/output file, just as we use filesystem1, filesystem2, etc. for the thread of file system values.
f2: FWriteS "Input file name: " f1, (inputline, f3): FReadLine f2, input: DropNewLine inputline,
The string read from the input is used as a file name for opening a non-unique input file g. The constant FReadText opens the file as a text file for reading only. The list of points in the input file is read using the shared-file operation defined in section 6, and the number of cells and minimum and maximum coordinates are calculated. Notice that tuples cannot be nested in the left-hand side of local definitions, i.e. a definition of the form (x,(y,z)):e is not legal.
(okinput, g, filesystem3): SFOpen input FReadText filesystem2, points: SReadPtsList g, (numcells, statistics): StatPnt points, (a, totx, c, b, toty, d): statistics,
The output file name is then read and used to open the output file h for writing:
f4: FWriteS "Output file name: " f3, (outputline, f5): FReadLine f4, output: DropNewLine outputline, (okopen, h, filesystem4): FOpen output FWriteText filesystem3,
We then write the PostScript file, using an appropriate header message, and close the output file. The WritePS function requires the rectangle ((a,b),(c,d)) occupied by the points.
cellcount: Concat [" [", ITOS numcells, " cells]"], message: Concat ["Life pattern ", input, cellcount], h': WritePS message ((a,b),(c,d)) points h, (okclose, filesystem5): FClose h' filesystem4,
Finally, we write a message to the standard output, and use our FCloseStd function to force the write to be executed (the dummy status is ignored). Placing the final file system back into the world gives the final result of the program. In spite of its name, CloseFiles does not close any open files, it merely places the file system back into the world.
f6: FWriteS (Concat [input, " -> ", output, cellcount, "\n"]) f5, (dummy, filesystem6): FCloseStd f6 filesystem5, world'': CloseFiles filesystem6 world';
Removing the last character of a string is done with the following macro, which selects characters 0 ... (n -2) of a string of length n :
MACRO DropNewLine s -> SLICE s 0 (- (LENGTH s) 2);
Execution of this program is as follows, with the output shown in Figure 1. At present CLEAN prints a redundant 65536 which corresponds to the final world object.
Input file name: eight Output file name: PATTERN eight -> PATTERN [40 cells] 65536 Execution: 0.02 Garbage collection: 0.00 Total: 0.02
The present version of CLEAN cannot directly print files, but on a Macintosh we can combine the PostScript output with existing 'drag-and-drop' printing tools. On a Unix system we can write a file (with a name constructed from the input file name and the current time) to a spooling directory, and use a shell script to print it. This provides the same functionality as being able to directly print files.
File Input and Output for the Game of Life
A GUI-Based Conversion Program