Dave's nasmjf Dev Log 20
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.
So log19.txt had a major breakthrough with my
understanding of Forth variables. And it solved a
previous mystery of the need for FETCH after the
variable LATEST.
Unfortunately, I'm still crashing with a segfault and
getting PARSE ERROR on some jonesforth.f source.
So I'll set my lines to read to just before that error
and continue reading and testing the Forth source:
%assign __lines_of_jf_to_read 704
After RECURSE comes IF ... THEN statements! Oooh,
exiciting stuff. With this, I can *finally* write
programs with branching and logic!
So the format is:
<condition on stack> IF true-statements THEN
Oh, and the Jones implementation of these only works in
compiled words. These are crazy enough as it is (THEN
supplies the address to branch to in a way that reminds
me of INTERCAL's "come from" statement. Ha ha.) but
apparently you can make some that work in immediate mode
too.
The gymnastics in the definition are amazing. And I can
finally see 0BRANCH used properly. No wonder I wasn't
having much luck on my own with that.
Okay, let's see if it works:
: foo IF 1 . THEN 2 . ;
1 foo
1 2
0 foo
2
That worked. Oh, and Forth is one of those languages
which use numeric 1 for true, 0 for false,
Now an ELSE:
: bar IF 1 . ELSE 2. THEN 3 . ;
1 foo
1 3
0 foo
2 3
Excellent.
Next night: This is what I'm *most* excited about:
looping!
BEGIN <put condition on stack>
WHILE
<still true, do this>
REPEAT
Here goes nothing:
: foo BEGIN DUP 0> WHILE DUP . 1- REPEAT ;
10 foo
10 9 8 7 6 5 4 3 2 1
(By the way, I had a bit of a challenge remembering how
to use 0> and 1- because they _look_ like, "zero is
greater than..." and, "backwards negative one." But the
Forth way to read them is: "push 0 onto the stack and
check if first item is greater than that," and, "push 1
onto the stack and subtract.")
Ooh, and I wonder if I can use IF to make a RECURSEive
word that actually works correctly?
: foo DUP . DUP 0> IF 1- RECURSE THEN ;
5 foo
5 4 3 2 1 0
Yes! Now I'm getting somewhere.
UNLESS is just NOT IF...but it uses that wild [COMPILE]
word to include the immediate IF word as part of its
definition. Usage is simple. And I just remembered that
we have TRUE and FALSE defined, so I can use those
instead of 1 and 0 (take that, Perl!):
: foo UNLESS 'A' EMIT ELSE 'A' 1+ EMIT THEN ;
TRUE foo
B
FALSE foo
A
And yeah, I just realized I can get 'B' by incrementing
'A'. It seems my mind is in top form tonight, ha ha.
Next is comments with ( parens ). And since '(' is an
immediate word, it can be programmed to be smart and
handle nested parethesis correcty:
: foo ( this is foo ) 42 . ;
( now some foo ) foo
42
( can we nest
as much ( as we want?
( of course! )
) )
foo
42
Amazing. Not too many languages let you change the
comment syntax mid-stream. :-)
Now that we have comments, Jones demonstrates some stack
effect comments (which are a standard documentation
format for words in Forth):
: NIP ( x y -- y ) SWAP DROP ;
: TUCK ( x y -- y x y ) SWAP OVER ;
: PICK ( x_u ... x_1 x_0 u -- x_u ... x_1 x_0 x_u )
1+ ( add one because of 'u' on the stack )
4 * ( multiply by the word size )
DSP@ + ( add to the stack pointer )
@ ( and fetch )
;
Let's try them out. NIP shows that it takes two items
from the stack and returns the first:
1 2 3 NIP . .
3 1
Tuck takes two items and puts a copy of the first after
the second:
1 2 3 TUCK . . . .
3 2 3 1
The notation for PICK looks insane until you stare at it
for a while. We get the element in the 'u'th position on
the stack where 'u' is on top of the stack.
50 40 30 20 10 4 PICK . . . . . .
50 10 20 30 40 50
Clever notation and did you see the definition above for
PICK? It had not occurred to me to fetch items on the
stack via the stack pointer!
Makes me want to try:
1 2 3 4
DSP@ @ .
4
DSP@ 4+ @ .
3
DSP@ 8 + @ .
2
Of course that works. You can do anything in Forth.
Which reminds me: long ago, I remember hearing about
this legendary programming language called Forth where
you could even redefine _numbers_ to have different
meanings.
I can't believe it's taken me this long to try it:
: 4 12 ;
4 1 + .
13
LOL. Of course. But, wow. That's really something to
see, isn't it?
Then a word that takes advantage of the ability to loop:
SPACES.
0 SPACES
20 SPACES
20 SPACES
And then HEX and DECIMAL, which simply set BASE:
15 HEX .
F
FF00 DECIMAL .
65280
Next up is number printing. If you've been reading this
far, you know that I added '.' DOT (prints number from
the top of the stack) in assembly as a debugging tool.
The one defined in jonesforth.f will, of course
"overwrite" mine.
Before '.' itself, Jones defines these:
U.R ( u width -- )
U. ( u -- )
.R ( n width -- )
Where U stands for "unsigned" number and R indicates
that the word will print the number padded to a width.
Let's try it out. Here's normal unsigned number
printing:
50 U. CR 5000 U. CR
50
5000
Now with a width of 10 characters:
50 10 U.R CR 500 10 U.R CR 5000000 10 U.R CR
50
500
5000000
Compare signed and unsigned printing:
-50 U.
4294967246
-50 10 .R
-50
And now one that I'm very excited about: '.S' which
prints everything on the stack without removing anything
on the stack.
1 2 3 4 5 .S
5 4 3 2 1 1 3215633123 0 3215633132 3215633148 3215633162 3215634118 3215634127
3215634178 3215634191 3215634203 3215634213 3215634256 3215634264 3215634275 3215634288
...6649441 791559519 1836278126 771778154 1935765039 6711917 0 Segmentation fault
Oh no! Let's take a look at the definition:
: .S ( -- )
DSP@ ( get current stack pointer )
BEGIN
DUP S0 @ <
WHILE
DUP @ U. ( print the stack element )
SPACE
4+ ( move up )
REPEAT
DROP
;
I'd say my S0 variable isn't set correctly.
A couple nights later: Yes, that's exactly what was
wrong. But it took me a while to figure out why.
Long story, but in my DEFVAR macro, if I try to set the
labels by "name" macro parameter like JonesForth does,
NASM won't like it:
var_"S0":
(Since a quote isn't a legal label character, it just
sees the first part, 'var_' and complains about
redefining 'var_' over and over.)
And maybe I figured that out before, because I was
setting them to the unquoted "label" macro parameter
instead:
var_SZ
Which is great, except where we initially store the
stack pointer in S0, it was by name (as in JonesForth).
The fix was easy. Just change:
mov [var_S0], esp ; save stack pointer
to:
mov [var_SZ], esp ; save stack pointer
In case you're wondering why it didn't just crash
before, since the macro wasn't defining a 'var_S0'
label, I had apparently made one. Maybe I meant it as a
temporary measure. I don't know, but it certainly was
confusing to piece together later.
With the fix in, let's test DSP@ and S0. They should
start off the same:
HEX
DSP@ U.
BFFFF990
S0 @ U.
BFFFF990
Then let's put something on the stack:
1
DSP@ U.
BFFFF98C
And another thing:
2
DSP@ U.
BFFFF988
Then pop the stack:
.
2
DSP@ U.
BFFFF98C
Again, and we should be back where we started:
.
1
DSP@ U.
BFFFF990
Hooray! Now .S should work, right?
1 2 3
.S
3 2 1
.S
3 2 1
Excellent! It non-destructively prints the stack!
(Now that I've made another fix to my port, I wonder if
_now_ I can run jonesforth.f without errors? Darn it,
nope. Same parse error on the the same spot as before.
Oh well, I'll keep going until I hit that spot...)
Next word is UWIDTH. It prints the width of a printed
number:
5 UWIDTH .
1
42 UWIDTH .
2
777 UWIDTH .
3
That works, but it's no surprise since U.R and .R depend
on it.
Oh, and I thought this was fun. To add a space to U.,
Jones redefines it by calling the previous definition
and adding a space:
( The real U., note the trailing space. )
: U. U. SPACE ;
I'll close out this log with another tiny one:
( ? fetches the integer at an address and prints it. )
: ? ( addr -- ) @ . ;
Which will be easy to test:
555 DSP@ ?
555
Looks like there's a real mix of stuff coming up next.
See you in the next log!