Dave's nasmjf Dev Log 05
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.
Third time's the charm. First time, I might have...
...fallen asleep while stepping through the program.
The second time, I got to the end to discover that
I'd either missed a line of instruction or accidentally
deleted it. Either way, I was missing the crucial
instruction to add the parsed digit to the total
in the NUMBER word.
And in between each of those attempts were sleepy
nights when I just booted up, read some source,
poked around in the debugger, and then logged off
again.
These things ebb and flow.
But if you just keep coming back to them day after
day, they DO get completed. I'm increasingly able
to make use of this obvious truth.
Anyway, parsing literal numbers in the interpreter.
Note the '42' below is me typing at the Forth
prompt (via STDIN):
Reading symbols from nasmjf...
(gdb) break _NUMBER
Breakpoint 3 at 0x80491ac: file nasmjf.asm, line 407.
(gdb) c
Continuing.
42
Breakpoint 3, _NUMBER () at nasmjf.asm:407
407 xor eax,eax
408 xor ebx,ebx
410 test ecx,ecx ; trying to parse a zero-length string is an error, but returns
411 jz .return
GDB prints the _next_ line of source to be executed
after a step. So below, I'm checking the BASE (radix)
for number parsing right after line 413 executes.
As we can see, base 10 is the default.
413 mov edx, [var_BASE] ; get BASE (in dl)
416 mov bl,[edi] ; bl = first character in string
(gdb) p $dl
$1 = 10
417 inc edi
And now let's examine the first character of input from
the "42" I typed. Yup, it's a '4'.
(gdb) p/c $bl
$2 = 52 '4'
Now we convert the ASCII char '4' to the actual
value 4 using the base.
418 push eax ; push 0 on stack
_NUMBER () at nasmjf.asm:419
419 cmp bl,'-' ; negative number?
420 jnz .convert_char
_NUMBER.convert_char () at nasmjf.asm:435
435 sub bl,'0' ; < '0'?
436 jb .negate
437 cmp bl,10 ; <= '9'?
438 jb .compare_base
_NUMBER.compare_base () at nasmjf.asm:444
444 cmp bl,dl ; >= BASE?
445 jge .negate
This line is the most crucial of all. It's where we
add the current digit's value to the total. This is
the line I was missing. :-O
448 add eax,ebx
(gdb) p $ebx
$3 = 4
449 dec ecx
450 jnz 1b
Each new digit means the previous sum is another place
value higher. So we mutiply our accumulated value by
the base.
_NUMBER.next_char () at nasmjf.asm:430
430 imul eax,edx ; eax *= BASE
431 mov bl,[edi] ; bl = next character in string
(gdb) p $eax
$4 = 40
Then the whole thing repeats for the '2' character.
432 inc edi
(gdb) p/c $bl
$5 = 50 '2'
_NUMBER.convert_char () at nasmjf.asm:435
435 sub bl,'0' ; < '0'?
436 jb .negate
437 cmp bl,10 ; <= '9'?
438 jb .compare_base
_NUMBER.compare_base () at nasmjf.asm:444
444 cmp bl,dl ; >= BASE?
445 jge .negate
448 add eax,ebx
449 dec ecx
Do we have the correct total? Yes! 42 is correct.
NUMBER returns with the value in eax.
(gdb) p $eax
$6 = 42
(gdb) s
450 jnz 1b
_NUMBER.negate () at nasmjf.asm:453
453 pop ebx
_NUMBER.negate () at nasmjf.asm:454
454 test ebx,ebx
455 jz .return
_NUMBER.return () at nasmjf.asm:459
459 ret
Back in INTERPRET now, we check to see if NUMBER was
successful. And it was (ecx is zero).
We are currently in "immediate mode" (STATE is zero), which
means that the word is executed as soon as it's entered.
However, you can see that the address of the LIT word is being
saved in eax so that if we were in "compiling mode", the
interpreter would have it available.
code_INTERPRET.try_literal () at nasmjf.asm:232
232 test ecx,ecx
233 jnz .parse_error
234 mov ebx,eax
235 mov eax,LIT ; The word is now LIT
code_INTERPRET.check_state () at nasmjf.asm:238
238 mov edx,[var_STATE]
239 test edx,edx
240 jz .execute ; Jump if executing.
code_INTERPRET.execute () at nasmjf.asm:253
253 mov ecx,[interpret_is_lit] ; Literal?
254 test ecx,ecx ; Literal?
255 jnz .do_literal
And how do we immediately "execute" the provided
numerical literal value in Forth? By pushing it on
the stack!
code_INTERPRET.do_literal () at nasmjf.asm:262
262 push ebx
So that works. :-)