Adventures in Forth, part 3 (writing MAX)
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.
