Dave's nasmjf Dev Log 18
Created: 2022-07-22
This is an entry in my developer’s log series written between December 2021 and August 2022 (started project in September). I wrote these as I completed my port of the JONESFORTH assembly language Forth interpreter.
I'm back! It's been over a month and I had to stop just
when it was getting exiciting. :-(
Anyway, I can now read any amount of jonesforth.f upon
starting the interpreter. So it's easy to test chunks
of Forth implementation by just incrementing the temporary
constant I've put in the assembly:
__lines_of_jf_to_read
But that doesn't mean it will be *easy*. The rest of
the FORTH implementation is implemented in FORTH, so
plenty of mind-bending lies ahead.
Setting the lines to read to 97, we now have these
10 new definitions to try out:
LITERAL ':' ';' '(' ')' '"' 'A' '0' '-' '.'
Yes, the quotes are part of the word names (it is 'A' not A).
First, LITERAL, sounds a lot like LIT, right?
Here's the entire definition:
\ LITERAL takes whatever is on the stack and compiles LIT <foo>
: LITERAL IMMEDIATE
' LIT , \ compile LIT
, \ compile the literal itself (from the stack)
;
I don't know ahout you, but I'm already starting to get a bit
rusty after my hitatus. So I'm going to need to remind myself
what these separate words mean.
IMMEDIATE Means LITERAL will always be executed as soon
as it's encountered even if we're in compile mode.
' (TICK) Matches the next "word" of input with a compiled
word and returns its address.
LIT Pushes the next value onto the stack.
, (COMMA) Puts the currently-pushed value from the stack to
the position pointed to by HERE.
None of ' LIT , are immediate mode words, so they're being compiled
into LITERAL.
All together, this means that we
* get the address of the next word
* push that address on the stack
* write the address at HERE ("compile it")
* write the next address from the stack ("compile it")
Well, I can't make heads or tails of that functionality on its
own, but the next words make use of it. Jones thoroughly breaks
the first of them down in a big comment:
\ Now we can use [ and ] to insert literals which are calculated at
\ compile time. (Recall that [ and ] are the FORTH words which switch
\ into and out of immediate mode.) Within definitions, use [ ... ]
\ LITERAL anywhere that '...' is a constant expression which you would
\ rather only compute once (at compile time, rather than calculating it
\ each time your word runs).
: ':'
[ \ go into immediate mode (temporarily)
CHAR : \ push the number 58 (ASCII code of colon) on the parameter stack
] \ go back to compile mode
LITERAL \ compile LIT 58 as the definition of ':' word
;
Followed by the rest of the definitions:
\ A few more character constants defined the same way as above.
: ';' [ CHAR ; ] LITERAL ;
: '(' [ CHAR ( ] LITERAL ;
: ')' [ CHAR ) ] LITERAL ;
: '"' [ CHAR " ] LITERAL ;
: 'A' [ CHAR A ] LITERAL ;
: '0' [ CHAR 0 ] LITERAL ;
: '-' [ CHAR - ] LITERAL ;
: '.' [ CHAR . ] LITERAL ;
These are all crazy to look at coming from "normal" programming
languages because of the single quotes in the word names!
Anyway, taken all together, we get:
* Compile a word called ':'
* Switch to immediate mode
* CHAR takes the next character and puts on stack
* : is next character
* Switch back to compile mode, : is on stack
* LITERAL:
* gets the address of the next word
* push that address on the stack
* write the address at HERE ("compile it")
* write the next address from the stack ("compile it")
Or, more simply: we put the ASCII value of the character on the
stack, and LITERAL compiles the LIT word followed by the character
from the stack. Thus, we end up with a compiled word that will
perform a LIT + character (puts that character on the stack).
Okay, got it. Let's see it in action with one of the defined words:
'A' .
65
'A' EMIT
A
':' EMIT
:
Now I'll try making my own word that uses LITERAL:
: Z [ CHAR z LITERAL ] ;
Z EMIT
z
And I can compile that into another word:
: emitz Z EMIT ;
emitz
z
Yay, I finally understand this! I kept trying to "get it" late
at night and it just wasn't clicking. What made this so hard
to understand has nothing to do with the stack, it's the
temporal nature of the compile-time vs run-time execution of
these low-level compiler words in FORTH.
Some of it runs "now" and some of it runs "later". For certain
definitions of now and later. Thankfully, it's all much
more comprehensible on a weekend morning.
Well, this log has now spanned two months. So even though I
didn't cover a TON of ground here, I'm gonna start a new log
for the next installment. :-)