2007-10-18 / 18:16 /

Inspired by Reg’s quoting from Richard Jonessometimes minimal FORTH compiler and tutorial, I decided to learn a little Forth. To start: the Wikipedia article.

A low-level stack-based language… RPN… this all makes sense theoretically. But the first example:

: FLOOR5 ( n -- n' )   DUP 6 < IF DROP 5 ELSE 1 - THEN ;

threw me. It too me a minute to remember that each operation--I mean word--automatically pops the correct number of operands off the stack and that the results are pushed back to the stack. Since the results are already on the stack they are automatically "returned" at the end of the subroutine.

The problem

Now I've got the basics, or at least enough to think that this is a bug:

: FLOOR5 ( n -- n' ) 1- 5 MAX ;

Forth words are separated by spaces, so what is "1-"? Forth notation for -1? But that means the function would return (in C) MAX(-1, 5) or--drum roll--5. If this is an example of a Forth word, I am not impressed.

I figure it was supposed to be

: FLOOR5 ( n -- n' ) 1 - 5 MAX ;

Which seems like it would be the C equivalent:

int floor5 (int val) {return MAX(val - 1, 5);}

(aside: I wrote a quick test program to see the output and holy crap I forgot how picky C is. I had to google to get rid of all the warnings!)

Checking myself

I'd like to double check myself before I edit the Wikipedia. Googling for "forth compiler" turns up the authoritative looking Forth Compilers Page--possibly unchanged since 1996 when it appears to have been written.

First try: RetroForth. The 9.2.10 binaries [tar.gz] include a Win32 executable. It starts with this rather helpful screen:

RetroForth Start Screen

I guess this is the interpreter. And sure enough 10 5 + . returns 15. Unfortunately, : FLOOR5 ( n -- n' ) 1- 5 MAX ; returns *** ERROR: MAX was not found. After disappointing myself by not quickly coming up with a stack based implementation of MAX, I decided to move on.

PFE, the Portable Forth Environment looked good... even if it hadn't been updated since 2006. The stable 0.32.94 version failed with lots of errors:

In file included from /usr/lib/gcc/i686-pc-cygwin/3.4.4/../../../../include/w32api/oleauto.h:158,
                 from /usr/lib/gcc/i686-pc-cygwin/3.4.4/../../../../include/w32api/ole2.h:11,
                 from /usr/lib/gcc/i686-pc-cygwin/3.4.4/../../../../include/w32api/windows.h:114,
                 from ../../pfe/posix-ext.c:44:
/usr/lib/gcc/i686-pc-cygwin/3.4.4/../../../../include/w32api/oaidl.h:256: error: parse error before string constant
/usr/lib/gcc/i686-pc-cygwin/3.4.4/../../../../include/w32api/oaidl.h:333: error: parse error before "VARIANTARG"
/usr/lib/gcc/i686-pc-cygwin/3.4.4/../../../../include/w32api/oaidl.h:336: error: parse error before "LPPARAMDESCEX"

This happened as soon as it tried to use my local headers, so I figure it's a problem with Cygwin (it's our work platform *sigh*). But the Google, it tells me nothing.

The "preview" (0.33.61) failed even sooner:

../../pfe/exception-sub.c: In function `p4_catch':
../../pfe/exception-sub.c:242: error: too many arguments to function `setjmp'

PFE #defines a macro that calls setjmp with two arguments. The error message & the internet seemed to think this was a problem so I fixed it. The build then failed as it had with the old version.

Maybe it's fixed in the tip...

cvs -d:pserver:anonymous@pfe.cvs.sourceforge.net:/cvsroot/pfe login
cvs -z3 -d:pserver:anonymous@pfe.cvs.sourceforge.net:/cvsroot/pfe co -P pfe-33

...and the build died doing something with /usr/X11R6/include. The HEAD had the same problem with setjmp so I made a patch and submitted. First I sent it to the maintainer's email address from the homepage (well first I looked for patch submission instructions...) and got back this helpful reply:

Hi. This is the qmail-send program at mx0.gmx.net.
I'm afraid I wasn't able to deliver your message to the following addresses.
This is a permanent error; I've given up. Sorry it didn't work out.


Hardcore diss. So I sent a message via Sourceforge. Not sure if the maintainer will ever get the fruits of my labor, so here's the incredibly complex patch for the legions of PFE users reading this:

Index: pfe/os-setjmp.h
RCS file: /cvsroot/pfe/pfe-33/pfe/os-setjmp.h,v
retrieving revision 1.2
diff -r1.2 os-setjmp.h
< #define p4_setjmp(_buf) setjmp((_buf), 1)
> #define p4_setjmp(_buf) setjmp((_buf))

More Googling turned up a list on dmoz which lead to Win32Forth whose install screen gave the first hint as to its age:

W32Forth Install Screen

But the actual interpreter is a little friendly than RetroForth.

W32Forth Interpreter

And--most importantly--10 5 max . returns 10 ok.

The results?

So now is the time. I can put in my changes and see if it works.

: FLOOR5_DAG ( n -- n' ) 1 - 5 MAX ;  ok.
3 FLOOR5_DAG . 5  ok.
5 FLOOR5_DAG . 5  ok.
9 FLOOR5_DAG . 8  ok.

My function works!

Let's go see if the old one was a typo:

: FLOOR5 ( n -- n' ) 1- 5 MAX ;  ok
3 FLOOR5 . 5  ok
5 FLOOR5 . 5  ok
9 FLOOR5 . 8  ok

Yay! So the old results were... er... right all along. So no bug on the Wikipedia, at least in Win32Forth. I might try to get RetroForth installed later and see what it thinks.

And what now?

So no Wikipedia edit, but I've got a working Forth compiler. And I even wrote my own absolutely useless function. Back to reading tutorials...