FORTH FACTORY
Utilities
by Bob Gonsalves
In this installment we'll present some utility definitions that you may easily add to your system. The first set of words can be used in many Forth systems, the second set are designed to access the Atari disk file management system.
SOME QUICKIES
Screen 30 shows some useful extensions to fig-Forth systems. Following /, all characters on a line will be ignored; the text following the / is used for commenting. NOT is used to reverse the logical state of the top stack item and is often used before conditional testing words, such as IF, UNTIL.
Another group of words facilitates operations on bytes in 16-bit word. LSBYTE will leave the least significant byte of the top stack value, while MSBYTE leaves the most significant byte. >< uses these two operations to reverse the two halves of a 16-bit stack value.
Our final category introduces a new data type. The 'TO' type variable, introduced by Paul Bartholdi in Forth Dimensions, improves readability and reliability of Forth programs by reducing the number of @ (fetch) and ! (store) operations that must be included in the source text. If the VAR defined variable is preceded by TO, a stack parameter will be stored in the variable. Otherwise, the variable simply leaves its contents. The following should illustrate:
0 VAR temp.cell / our specific instance
20 TO temp.cell / store 20 in the variable
temp.cell . 20/ prints the contents of temp.cell
FMS 'n' FIELDS
With extensions such as these, we're now ready to suggest some ways of accessing FMS formatted files. As shown in figure 1, both the FMS directory entries and the individual sectors of a FMS file share a similar structure. They both feature range of disk space, with certain bytes or 16-bit words having specific significance. In the case of a directory entry, locations are used to store the state of the file (open, closed, etc), its length and starting sector, plus the characters that make up its name. These locations are offset from an address DIR.ADDR, which contains the address, within the disk buffers, of the start of the directory entry. The defining word FIELDER will create some words that allow us to access these fields. These access words are structured like the TO variables mentioned above. Thus, START.SECTOR for example, will normally leave the starting sector number of a file on the stack. If it is preceded by TO, however, as in:
3 TO START.SECTOR
then a value is stored to the START.SECTOR field. (This doesn't work as well for FLAG.BYTE, which is only a single byte location.)
Three additional words on screen 32 show ways to make use of the data from these fields. ?NULLED examines the least significant byte of the FLAG.BYTE of a directory entry, and leaves a true flag on the stack if the directory entry is not an active one. PNAME will print the name of an entry. BUMPSEC will print out the number of sectors used by an individual file, and increments a counter containing the number of sectors used on the disk.
Our DIR example makes use of all of the above definitions to print out the directory of a FMS disk. It does so by examining sectors 361 through 368 for valid directory entries. Each entry is 16 bytes long (8 per sector) and is checked to see if it is null. If it isn't, its name is printed and the file length is added to the running total (#SEC). At the end of the directory listing, the total number of sectors used by the files is printed, as well as the number of sectors available (according to the FMS Volume Table of Contents), using .USED.
In the case of an actual data sector from a file, the words FILE, POINTER and BCOUNT (all defined by DATA) return the values stored at the end of a data sector. Because of various tricks that are performed (to save disk space) additional words are required to convert the values into a useable format. The word #FILE returns the position of the file in the directory. The next sector number in the file is returned by #POINT, which equals 0 if this sector is the end of the file. #BYTES returns the number of data bytes in the sector, which may range from 125 to 1.
Two other words are useful in this context. DATA.FIELD leaves the address, in the disk buffers, of the start of the sector. ?LAST leaves a false flag, plus the next sector number of the file, if the end of the file has not been reached. Otherwise a true flag is left on the stack.
Our final example, on screen 36, illustrates what it might take to list a file. Assuming that the value of DIR.ADDR has been set to point to the directory entry in the disk buffers, PRINT.FILE starts at the first sector of the file, and types #BYTES from the DATA.FIELD of the sector, until the last sector is reached.
FOR MORE INFO
Because of space limitations, we'll skip over exactly how one locates a specific directory entry. This could be done by simply DUMPing the contents of a disk block, or by actually accepting text from a user and performing a string match against the FMS directory. Other applications for this system, such as loading character fonts into memory and repairing 'damaged' files, can be obtained by writing to the author c/o Pink Noise Studios, P.0. Box 785 Crockett, CA 94525. Please include a self-addressed, stamped envelope.
---------------------------------------- pink noise studios/fig-forth 1/82 \ 30 extensions for others rfg20apr82 : \ in @ c/l / 1+ c/l * in ! ; immediate \ from Henry Laxen : NOT 0= ; hex : MSBYTE 0 100 u/ swap drop ; : LSBYTE ff and ; : >< \ byteswap dup lsbyte 100 * swap msbyte + ; 0 variable TOFLAG : TO 1 toflag ! ; : VAR <builds , does> toflag @ if ! else @ then 0 toflag ! ; decimal ;s ---------------------------------------- \ 31 fields in directory rfg18apr82 0 variable DIR.ADDR \ points to directory entry in buffers : FIELDER <builds c, \ offset into field does> c@ dir.addr @ + \ compute addr toflag @ if ! else @ then 0 toflag ! ; 0 fielder FLAG.BYTE 1 fielder SECTOR.COUNT 3 fielder START.SECTOR : NAME.FIELD dir.addr @ 5 + ; ;s ---------------------------------------- \ 32 utilities for DIR rfg18apr82 : ?NULLED \ return true if nulled out flag.byte lsbyte dup 80 = swap 0= or sector.count 0= or ; hex : PNAME name.field dup 8 type 2e emit 8 + 3 type ; 0 variable #SEC : BUMPSEC \ increment total and print sector.count dup #sec +! 4 .r ; decimal : .USED \ according to VTOC 359 block 3 + @ 4 .r ," sectors available " cr ; ;s ---------------------------------------- pink noise studios/fig-forth 1/82 \ 33 directory of FMS disks rfg18apr82 decimal : DIR 0 #sec ! cr 368 360 \ directory sectors do i block dup b/buf + swap do i dir.addr ! ?nulled not if pname bumpsec cr then 16 +loop loop cr #sec @ 4 .r ." sectors used by files " cr .used ; ;s ---------------------------------------- \ 34 fields in sectors rfg18apr82 decimal 0 variable SECTOR : DATA <builds c, does> c@ sector @ 1- block + toflag @ if ! else @ then 0 toflag ! ; 125 data FILE 125 data POINTER 127 data BCOUNT hex \ below return values : #FILE file lsbyte 4 / ; : #POINT pointer >< 3ff and ; : #BYTES bcount 7f and 7d min ; \above accounts for short sectors ;s ---------------------------------------- \ 35 dos access utilities rfg18apr82 : DATA.FIELD \ first storage location sector @ 1- block ; : ?LAST #point -dup 0= ; \ leave true or false + link \ to next sector ---------------------------------------- pink noise studios/fig-forth 1/82 \ 36 printing a file rfg18apr82 \ assumes dir.addr points to \ directory entry in buffers : PRINT.FILE start.sector sector ! begin data.field #bytes type ?last not while sector ! repeat ; ----------------------------------------