2007-10-22 / 14:13 / dave


Revisiting RetroForth

Jeff’s email (from Part 2) clued me in that RetroForth is not ANS compatible. Jeff pointed to an ANS compatibility layer [ZIP]. There also seems to be one included with the 9.2.10 binary release (under Cygwin):

$ ./rf-windows.exe -f extensions/ans-compat 10 5 max . bye
10

Running files directly (with the -f switch) and passing commands directly to stdin is my preferred method, so I’ll use RetroForth for my noodling but leave Win32Forth installed for the interactive help.

Writing Max

In part 1, RetroForth’s default ans-incompatibility lead to my failed 30 second attempt at writing a MAX word. I knew I needed to duplicate values from the stack to simulate temp variables for the comparison, but didn’t know how to dup more than one value. And if the top value was the max, how did I discard the bottom value? I moved on to Win32Forth… and had all weekend to think about how easy that function must be.

This morning I checked the docs and found:

2dup ( x y -- x y x y ) .forth
Duplicate the top two items on the stack
...
drop ( x -- ) .inline
Discard the top element of the stack

nip ( x y -- y ) .inline
Discard the second element on the stack

First attempt:

: max 2dup > if drop else nip then ;
*** ERROR: else was not found

Try again with -f extensions/ans-compat:

: max 2dup > if drop else nip then ;
5 10 max .
10

It works, but max was already defined (as part of ANS) so it’s not a very good test. More docs:

if ( flag -- ) .macro
If the flag is true, execute the code between if and then. If not
true, skips ahead to then.

then ( -- ) .macro
Terminate any of the if constructs. This patches the conditional jump
to point to the proper offset in the compiled definition.

2nd attempt:

2 3 max .
*** ERROR: max was not found
: max 2dup > if drop then nip ;
2 3 max .
3

So max fails before it’s defined then works once defined. Success!

Checking Max

Since the -f switch loads files, extensions/ans-compat is probably written in RetroForth’s Forth dialect. It is, and the author’s–Neal Bridges–implementation of MAX is almost the same:

: max  2dup >if drop ;then nip ;

>if is a shortcut for ; if and ;then is a short-cut for ;; then. ;; is:

;; ( -- ) .macro
Compile an exit to the current word without ending the definition
   Technical note:
     This word will compile either a "ret" ($c3 opcode) or change
     the last compiled call opcode to a jump opcode. This provides
     inherent tail call elimination and allows for safe recursion.
      As ; uses this, the same applies to it.

This makes me think that my code has a bug: since I don’t have an exit inside the if, the nip will always execute (and trash the stack). But tests show otherwise:

: max 2dup > if drop then nip ;
: max2 2dup >if drop ;then nip ;
1 2 3 4 max . . .
4 2 1
1 2 3 4 max2 . . .
4 2 1

So I’m still not sure why my if is acting like an if-else and exactly what ;; does, but my lunch break has been over for half an hour, so I should probably get back to doing work that pays.


Update: I’m an idiot

Oh, of course:

: max 2dup > if drop then nip ;
1 2 4 3 max . . .
4 1 0
: max2 2dup >if drop ;then nip ;
1 2 4 3 max2 . . .
4 2 1

So ;then is required to make the equivalent of an ANS IF ELSE THEN.