CHAPTER 9. CHARACTER SEGMENT 107
Reading blocks of data usually means defining buffers. If maintainability is an issue, define a constant for the sizes of these buffers. You cannot only use this constants when defining buffers, but also when calling ’ACCEPT’.
Furthermore, ’ACCEPT’ returns the number of characters actually read. If this value is compared to the number of characters we actually wanted to read, we can determine whether a reading error or EOF occurred:
1024 constant bufsize bufsize string buffer
\ actual buffersize
\ define buffer file InFile \ value for file
\ open input file s" infile.dat" input open error?
abort" File could not be opened" to InFile
InFile use begin bufsize buffer over accept
<> until
\ save handle
\ redirect input
\ using bufsize
( n1)
( n1 a n1)
( n1 n2)
( f) \ make EOF flag
\ until EOF
Note that "BUFFER" is actually not a string, but a chunk of memory. But since a character in 4tH takes up a single address-unit (=byte), raw chunks of memory are allocated in the
Character Segment. This is not an uncommon practice in both Forth and C.
9.18
Writing binary files
Writing binary files is very easy. Of course you need a buffer, like we discussed in the previous section. The program is not much different than the previous one:
1024 constant bufsize bufsize string buffer file OutFile
\ actual buffersize
\ define buffer
\ value for file buffer bufsize char H fill \ fill the buffer
\ open output file s" infile.dat" output open error?
abort" File could not be opened" to Outfile
OutFile use buffer bufsize type
\ save handle
\ redirect input
\ write to file
This will write 1024 "H"s to "infile.dat". The actual command that does all writing is
’TYPE’. The word ’TYPE’ does not return anything. You can be assured that everything was alright, since if it wasn’t, 4tH would have caught the error itself.
9.19
Reading and writing block files
Block files are a special kind of files used by Forth compilers. In the old days Forth controlled the entire computer and directly communicated with all peripherals, including disks.
CHAPTER 9. CHARACTER SEGMENT 108
To Forth, a disk is just a bunch of numbered blocks. Each block is divided into 16 lines of exactly 64 characters. A block file simply mimics that layout.
Before we can begin, you need to create a block file. Well, that’s easy: include lib/ansblock.4th
4 s" blocks.scr" create-blockfile
This creates a file with four blocks. Then we have to tell 4tH which file to use: s" blocks.scr" open-blockfile
Note that apart from creating the file, we haven’t performed any I/O yet. First, we have to request a block. When a block is requested, its contents are transferred to a memory buffer.
You can manipulate this buffer any way you want with the standard words. If you request another block its contents are transferred to the buffer too, overwriting whatever is there.
All changes you have made are lost, unless you have flagged the block as dirty, which means its contents are different from the block on disk. If a block is dirty, it is written to disk before the next block is read. ’CLEAR’ is a special word, assigning an empty buffer to a block without reading it first. The buffer is BLANKed. ’UPDATE’ will flag the buffer as dirty. ’FLUSH’ writes the dirty buffer to disk and unassigns the buffer. So, first we clear block 0:
0 clear
Then we clear block 1, copy a string to it and flag it as dirty:
1 clear s" Hello world!" >r 1 buffer r@ cmove update
’BUFFER’ returns the address of the buffer assigned to that block
1
. If the buffer is dirty, it is FLUSHed before assigning a buffer to that block. We can also write the dirty buffer to disk, without unassigning it: save-buffers
Note that the buffer is not dirty anymore, since it has been synchronized. Let’s write something to another block: s" Goodbye cruel world!" 0 block swap cmove update
It is always a nice game to figure out what will happen now. The current block is block 1.
Since we haven’t UPDATEd it since ’SAVE-BUFFERS’, it is clean. That means that 4tH won’t perform a write. Since block 0 isn’t current, ’BLOCK’ reads it into the buffer. The
’UPDATE’ will flag the buffer as dirty.
1 block r> type cr
This is fun! The current block is block 0. It is dirty, so it is written to disk. Since block 1 isn’t current, it is read into the buffer. You catch my drift? If you want to print the contents of a block, you can use ’LIST’. Of course, ’LIST’ uses ’BLOCK’ and applies to the same rules:
1
That is particularly handy if your implementation can handle multiple buffers. In this implementation we have only one buffer, so we always return the same address.