Dave's nasmjf Dev Log 10
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.
Now that CREATE seems to work (making our new word's
dictionary entry header, we can take a look at the
rest of the words in COLON, starting with LIT.
Jones has a good explanation of LIT in the original
source. In short, it lets you get the address of a
word without executing that word. So:
LIT DOCOL COMMA
pushes the address of DOCOL on the stack for COMMA
to use.
The way it works is really clever. It basically works
just like the NEXT macro, except instead of jumping
to the address to execute it, LIT pushes the address
on the stack so it will be avaiable for the next
command.
Here's LIT:
lodsd
push eax
Here's NEXT:
lodsd
jmp [eax]
In both cases, lodsd stores the value pointed to by esi
in eax and then increments esi by 4 so it points to the
next word.
What's clever about this is that the lodsd will end up
being called twice and the word we just got the address
for will be skipped, not executed.
So here it is and the symbol pushed on the stack should
be DOCOL.
code_LIT () at nasmjf.asm:493
493 lodsd ; loads the value at esi into eax, incements esi
494 push eax ; push the literal number on to stack
(gdb) info sym $eax
DOCOL in section .text of /home/dave/nasmjf/nasmjf
Cool, continuing on, NEXT calls COMMA, which just
pops the address back off the top of the stack
and then stores it at HERE. So that's how
LIT DOCOL COMMA
puts the address of DOCOL into the definition of
our new word. Neat. :-)
(NEXT macro snipped)
code_COMMA () at nasmjf.asm:590
590 pop eax ; Code pointer to store.
591 call _COMMA
594 mov edi, [var_HERE]
595 stosd ; puts the value in eax at edi, increments edi
596 mov [var_HERE], edi
597 ret
Next, we'll get the address of our new word from
LATEST, fetch it,
dd LATEST, FETCH, HIDDEN ; Make the word hidden while it's being compiled.
; FETCH (@) grabs a value from an address on the stack and
; puts that value on the stack.
;
; HIDDEN toggles the hidden flag for the dictionary entry
; at the address on the stack
And LATEST is a variable that pushes the current
word's address onto the stack.
!!!!!!!!!!!!!!!!!!!! Update !!!!!!!!!!!!!!!!!!!!
! In log19.txt, I realize that my variable !
! handling is wrong. Variables should leave !
! their addresses on the stack, not their !
! values! We need FETCH to get the value from !
! the address! !
!!!!!!!!!!!!!!!!!!!! Update !!!!!!!!!!!!!!!!!!!!
LATEST makes sense: we'll get the word's address in order
to toggle its hidden flag so a partially-compiled word
won't show up in any dictionary searches (which I guess could
happen if a word relied on a previous definition of itself?)
(NEXT macro snipped)
code_LATEST () at nasmjf.asm:771
771 push dword [var_%4]
(NEXT macro snipped)
But I confess that I don't understand why we're using FETCH
after LATEST. LATEST has the address of the current word
being compiled. FETCH pops an address and pushes the value
at that address. And the value at the beginning of every word
"header" is a link to the previous word in the dictionary's
linked list.
So won't HIDDEN be toggling the previous word, not the
current word? Doesn't FETCH return the linked word?
code_FETCH () at nasmjf.asm:630
630 pop ebx ; address to fetch
631 mov eax, [ebx] ; fetch it
632 push eax ; push value onto stack
(NEXT macro snipped)
code_HIDDEN () at nasmjf.asm:636
636 pop edi ; Dictionary entry, first byte is link
637 add edi, 4 ; Move to name/flags byte.
638 xor [edi], word F_HIDDEN ; Toggle the HIDDEN bit in place.
(gdb) x/bx $edi
0x804a3b0: 0x06
(gdb) x/6c $edi + 1
0x804a3b1: 76 'L' 65 'A' 84 'T' 69 'E' 83 'S' 84 'T'
Uh, yeah, that's LATEST. Either this is a bug in the original
or I made a mistake somewhere. The later seems more likely.
This isn't a showstopper, though. Compilation can work without
it.
This next part is amazing: so to re-use all the same mechanisms
for input and word lookup, etc, FORTH just sets the state to
"compiling mode". Then "EXITs" back to input to get the rest
of the word definition.
(NEXT macro snipped)
code_RBRAC () at nasmjf.asm:613
613 mov [var_STATE], word 1 ; Set STATE to 1 (compile)
(NEXT macro snipped)
code_EXIT () at nasmjf.asm:44
44 mov %1, [ebp]
45 lea ebp, [ebp+4]
(NEXT macro snipped)
code_gtfo () at nasmjf.asm:214
214 mov ebx, 0 ; exit code
215 mov eax, 1 ; exit syscall
216 int 80h ; call kernel
[Inferior 1 (process 2529) exited normally]
So two things to address:
1. I guess remove the FETCH (@) and see if HIDDEN hides
the correct word.
2. Add BRANCH so the interpreter an keep on getting input
for the compilation of our new word!