Dave's nasmjf Dev Log 20

Created: 2022-07-22

nasmjf home

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.

← Previous 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Next →
    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!
← Previous 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Next →