So, while having lunch in my car, away from the riff-raff of all my cats and just enjoying the pitter-patter of a gentle rain on the roof of my car, I had an epiphany.
It is no secret that I’ve always wanted a home-brew computer running Forth as its host operating system. It is also no secret that I admire the IBM mainframe architecture a great deal. And, it’s absolutely no secret that I have a solid crush on Plan 9 from Bell Labs and on Project Oberon. While I very much favor the simplicity of systems like Forth, it’s clear that more sophisticated operating systems like Plan 9 or MVS make it easier to handle device or file independence.
But, for the first time since I thought about this problem, I had a vision which kinda sorta combined attributes of all these systems into one cohesive and relatively easy to use whole. Some of you will likely look at this and think, “Well, yeah, DUHH.” And I agree; this is so obvious in retrospect. I don’t know why I didn’t think of this sooner.
A typical system admin problem: I need to copy a “file” from block storage onto either a fixed or removable storage device. Conventional Forth wisdom says that I’ll need a plurality of copy
implementations to handle each conceivable combination of features. After all, a “file” can be any one of the following:
That is a list of eight (known) things I might want to copy at any given time; thus, one might say I would need 64 different copy functions to handle each of the sources and destinations. Of course, in true Forth fashion, I’d only write what I needed, when I needed it; but the core problem remains – I have to remember what I called these tools and how to call them.
Well, it occurred to me, it would be better to just use cursors, objects whose job it is to keep track of its data source or sink. Hello, Vertigo! That’s what a rider is in the Carrier-Rider pattern from Project Oberon. It’s also what a data definition is in MVS and z/OS JCL. It’s also what a file descriptor does in Unix and Plan 9. Yeah, this is that DUHH moment.
And the syntax, while a tad bit unweildly, isn’t completely burdonsome either:
HERE
1 ProjSizeInBlocks DestBlock block-cursor,
2 ProjSizeInBlocks SrcBlock block-cursor,
copy
HERE - ALLOT
Here, block-cursor, ( skip size blk -- addrD addrV)
would “construct” a dictionary-resident object (in an OOP or FP sense; addrD
points to the instance data, while addrV
points to a v-table) whose job it is to keep track of where it is drawing data from or delivering data to. The skip
indicates how many blocks to advance after reading (or writing) a block. size
indicates the total number of blocks, and blk
indicates the starting block number.
If I wanted to copy a raw memory blob from a file into a local buffer, I could perhaps write something like this:
( ... ) CONSTANT /myBuffer
CREATE myBufferHere
/myBuffer ALLOT
( ... )
HERE
myBufferHere /myBuffer mem-cursor,
1 /myBuffer 1023 + 10 rshift DestBlock block-cursor,
copy
HERE - ALLOT
The whole HERE ... HERE - ALLOT
thing is there to free up dictionary space after *-cursor,
words have allocated space there. Once the copy operation is done, there’s no further need to keep the cursors in dictionary space. You could also use a MARKER word as well.