Dealer Demo, part 11: One Assembler to Rule them All
We've now reached a compact bit of code in the Dealer Demo that provides an assembler to Forth. And the assembler in Forth is a thing of beauty indeed. Written by Bill Ragsdale (as was most of the Forth kernel), it provides an assembler that can produce surprisingly readable code without the use of labels. In essence it implements high-level assembler:
Recall TRAVERSE. In traditional assembler, it was written as:
154A: 4C 15 TRAV .WORD *+2 154C: B4 00 LDY 0,X 154E: 98 TRAV1 TYA 154F: 18 CLC 1550: 75 02 ADC 2,X 1552: 95 02 STA 2,X 1554: B5 01 LDA 1,X 1556: 75 03 ADC 3,X 1558: 95 03 STA 3,X 155A: A1 02 LDA (2,X) 155C: 10 F0 BPL TRAV1 155E: 4C 4D 0E JMP POPIn Forth assembler, it might be written as:
CODE TRAV ,X 0 LDY, BEGIN, TYA, CLC, ,X 2 ADC, ,X 2 STA, ,X 1 LDA, ,X 3 ADC, ,X 3 STA, X) 2 LDA, 0< NOT UNTIL, POP JMP, ;CODENotice that we didn't need a label we simply used BEGIN, to mark the beginning of a block and UNTIL, to mark the end. The Forth assembler also has IF, ELSE, THEN, which behave as expected. This is flexible enough to handle most assembly constructs without ever resorting to labels. It also uses a Forth-like syntax, with RPN ordering for the assembler codes, and the opcodes always ending in comma, since the effect is similar to the comma operator which puts data into memory in Forth. While I wouldn't want to write a large program this way, this is much more convenient than the USR function in BASIC.
Another interesting implementation detail is that all opcodes are split into two sets. The first set, encoded using the word CPU, are most of the traditional one-byte 'immediate' mode opcodes, and have the opcode byte value attached to them. The remaining opcodes, encoded using the word M/CPU, use a word and a byte for each of these to select the appropriate opcode depending on the current mode and whether to emit 0, 1 or 2 additional bytes taken from the stack. This is remarkably compact, thanks to the <BUILD … DOES> construct in Forth which we discussed in an earlier post.
You can read about this assembler in an article by Bill Ragsdale himself. It appeared in the September 1981 issue of Dr. Dobbs Journal (a issue dedicated to Forth), as well as the January 1982 issue of Forth Dimensions (v.3, number 5). The implementation itself was available earlier, since the Dealer Demo dates from late 1980, and the screens for the assembler date to June 1979 and earlier. It opens with:
This article should further polarize the attitudes of those outside the growing community of FORTH users. Some will be fascinated by a label-less macro-assembler whose source code is only 96 lines long! Others will be repelled by reverse Polish syntax and the absence of labels.
The author immodestly claims that this is the best FORTH assembler ever distributed. It is the only such assembler that detects all errors in op-code generation and conditional structuring. It is released to the public domain as a defense mechanism: three good 6502 assemblers were submitted to the FORTH Interest Group but each had some lack. Rather than merge and edit for publication, I chose to publish mine with all the submitted features plus several more.
If you compare the published Ragsdale implementation with the assembler in Dealer Demo, only a few minor differences appear. The word VS was added, to give symbolic access to the overflow branches (BVS & BVC, an oversight in the original assembler), and the word END-CODE was renamed to C;.
In Dealer Demo, the assembler occupies $25a0 - $2be4, or about 1.6k. So the Forth kernel and its assembler are about the same size as the Atari Assembler/Editor cartridge. Atari Forth on cartridge, that would have been an interesting product to see!