1 Third time's the charm. First time, I might have...
2 ...fallen asleep while stepping through the program.
3 The second time, I got to the end to discover that
4 I'd either missed a line of instruction or accidentally
5 deleted it. Either way, I was missing the crucial
6 instruction to add the parsed digit to the total
7 in the NUMBER word.
8
9 And in between each of those attempts were sleepy
10 nights when I just booted up, read some source,
11 poked around in the debugger, and then logged off
12 again.
13
14 These things ebb and flow.
15
16 But if you just keep coming back to them day after
17 day, they DO get completed. I'm increasingly able
18 to make use of this obvious truth.
19
20 Anyway, parsing literal numbers in the interpreter.
21
22 Note the '42' below is me typing at the Forth
23 prompt (via STDIN):
24
25 Reading symbols from nasmjf...
26 (gdb) break _NUMBER
27 Breakpoint 3 at 0x80491ac: file nasmjf.asm, line 407.
28 (gdb) c
29 Continuing.
30 42
31
32 Breakpoint 3, _NUMBER () at nasmjf.asm:407
33 407 xor eax,eax
34 408 xor ebx,ebx
35 410 test ecx,ecx ; trying to parse a zero-length string is an error, but returns
36 411 jz .return
37
38 GDB prints the _next_ line of source to be executed
39 after a step. So below, I'm checking the BASE (radix)
40 for number parsing right after line 413 executes.
41
42 As we can see, base 10 is the default.
43
44 413 mov edx, [var_BASE] ; get BASE (in dl)
45 416 mov bl,[edi] ; bl = first character in string
46 (gdb) p $dl
47 $1 = 10
48 417 inc edi
49
50 And now let's examine the first character of input from
51 the "42" I typed. Yup, it's a '4'.
52
53 (gdb) p/c $bl
54 $2 = 52 '4'
55
56 Now we convert the ASCII char '4' to the actual
57 value 4 using the base.
58
59 418 push eax ; push 0 on stack
60 _NUMBER () at nasmjf.asm:419
61 419 cmp bl,'-' ; negative number?
62 420 jnz .convert_char
63 _NUMBER.convert_char () at nasmjf.asm:435
64 435 sub bl,'0' ; < '0'?
65 436 jb .negate
66 437 cmp bl,10 ; <= '9'?
67 438 jb .compare_base
68 _NUMBER.compare_base () at nasmjf.asm:444
69 444 cmp bl,dl ; >= BASE?
70 445 jge .negate
71
72 This line is the most crucial of all. It's where we
73 add the current digit's value to the total. This is
74 the line I was missing. :-O
75
76 448 add eax,ebx
77 (gdb) p $ebx
78 $3 = 4
79 449 dec ecx
80 450 jnz 1b
81
82 Each new digit means the previous sum is another place
83 value higher. So we mutiply our accumulated value by
84 the base.
85
86 _NUMBER.next_char () at nasmjf.asm:430
87 430 imul eax,edx ; eax *= BASE
88 431 mov bl,[edi] ; bl = next character in string
89 (gdb) p $eax
90 $4 = 40
91
92 Then the whole thing repeats for the '2' character.
93
94 432 inc edi
95 (gdb) p/c $bl
96 $5 = 50 '2'
97 _NUMBER.convert_char () at nasmjf.asm:435
98 435 sub bl,'0' ; < '0'?
99 436 jb .negate
100 437 cmp bl,10 ; <= '9'?
101 438 jb .compare_base
102 _NUMBER.compare_base () at nasmjf.asm:444
103 444 cmp bl,dl ; >= BASE?
104 445 jge .negate
105 448 add eax,ebx
106 449 dec ecx
107
108 Do we have the correct total? Yes! 42 is correct.
109 NUMBER returns with the value in eax.
110
111 (gdb) p $eax
112 $6 = 42
113 (gdb) s
114 450 jnz 1b
115 _NUMBER.negate () at nasmjf.asm:453
116 453 pop ebx
117 _NUMBER.negate () at nasmjf.asm:454
118 454 test ebx,ebx
119 455 jz .return
120 _NUMBER.return () at nasmjf.asm:459
121 459 ret
122
123 Back in INTERPRET now, we check to see if NUMBER was
124 successful. And it was (ecx is zero).
125
126 We are currently in "immediate mode" (STATE is zero), which
127 means that the word is executed as soon as it's entered.
128 However, you can see that the address of the LIT word is being
129 saved in eax so that if we were in "compiling mode", the
130 interpreter would have it available.
131
132 code_INTERPRET.try_literal () at nasmjf.asm:232
133 232 test ecx,ecx
134 233 jnz .parse_error
135 234 mov ebx,eax
136 235 mov eax,LIT ; The word is now LIT
137 code_INTERPRET.check_state () at nasmjf.asm:238
138 238 mov edx,[var_STATE]
139 239 test edx,edx
140 240 jz .execute ; Jump if executing.
141 code_INTERPRET.execute () at nasmjf.asm:253
142 253 mov ecx,[interpret_is_lit] ; Literal?
143 254 test ecx,ecx ; Literal?
144 255 jnz .do_literal
145
146 And how do we immediately "execute" the provided
147 numerical literal value in Forth? By pushing it on
148 the stack!
149
150 code_INTERPRET.do_literal () at nasmjf.asm:262
151 262 push ebx
152
153 So that works. :-)