Started writing.
[rrq/jonesforth.git] / jonesforth.S
index 4277547e95f76aed79244fd108fb2008a0d56991..dfa173ca71e3af883bc0fddc2867c1b908f843b2 100644 (file)
-/* A sometimes minimal FORTH interpreter for Linux / i386 systems. -*- asm -*-
- * By Richard W.M. Jones <rich@annexia.org>
- *
- * gcc -m32 -nostdlib -static -Wl,-Ttext,0 -o jonesforth jonesforth.S
- */
+/*     A sometimes minimal FORTH compiler and tutorial for Linux / i386 systems. -*- asm -*-
+       By Richard W.M. Jones <rich@annexia.org> http://annexia.org/forth
+
+       gcc -m32 -nostdlib -static -Wl,-Ttext,0 -o jonesforth jonesforth.S
+
+       INTRODUCTION ----------------------------------------------------------------------
+
+       FORTH is one of those alien languages which most working programmers regard in the same
+       way as Haskell, LISP, and so on.  Something so strange that they'd rather any thoughts
+       of it just go away so they can get on with writing this paying code.  But that's wrong
+       and if you care at all about programming then you should at least understand all these
+       languages, even if you will never use them.
+
+       LISP is the ultimate high-level language, and features from LISP are being added every
+       decade to the more common languages.  But FORTH is in some ways the ultimate in low level
+       programming.  Out of the box it lacks features like dynamic memory management and even
+       strings.  In fact, at its primitive level it lacks even basic concepts like IF-statements
+       and loops.
+
+       Why then would you want to learn FORTH?  There are several very good reasons.  First
+       and foremost, FORTH is minimal.  You really can write a complete FORTH in, say, 2000
+       lines of code.  I don't just mean a FORTH program, I mean a complete FORTH operating
+       system, environment and language.  You could boot such a FORTH on a bare PC and it would
+       come up with a prompt where you could start doing useful work.  The FORTH you have here
+       isn't minimal and uses a Linux process as its 'base PC' (both for the purposes of making
+       it a good tutorial). It's possible to completely understand the system.  Who can say they
+       completely understand how Linux works, or gcc?
+
+       Secondly FORTH has a peculiar bootstrapping property.  By that I mean that after writing
+       a little bit of assembly to talk to the hardware and implement a few primitives, all the
+       rest of the language and compiler is written in FORTH itself.  Remember I said before
+       that FORTH lacked IF-statements and loops?  Well of course it doesn't really because
+       such a lanuage would be useless, but my point was rather that IF-statements and loops are
+       written in FORTH itself.
+
+       Now of course this is common in other languages as well, and in those languages we call
+       them 'libraries'.  For example in C, 'printf' is a library function written in C.  But
+       in FORTH this goes way beyond mere libraries.  Can you imagine writing C's 'if' in C?
+       And that brings me to my third reason: If you can write 'if' in FORTH, then why restrict
+       yourself to the usual if/while/for/switch constructs?  You want a construct that iterates
+       over every other element in a list of numbers?  You can add it to the language.  What
+       about an operator which pulls in variables directly from a configuration file and makes
+       them available as FORTH variables?  Or how about adding Makefile-like dependencies to
+       the language?  No problem in FORTH.  This concept isn't common in programming languages,
+       but it has a name (in fact two names): "macros" (by which I mean LISP-style macros, not
+       the lame C preprocessor) and "domain specific languages" (DSLs).
+
+       This tutorial isn't about learning FORTH as the language.  I'll point you to some references
+       you should read if you're not familiar with using FORTH.  This tutorial is about how to
+       write FORTH.  In fact, until you understand how FORTH is written, you'll have only a very
+       superficial understanding of how to use it.
+
+       So if you're not familiar with FORTH or want to refresh your memory here are some online
+       references to read:
+
+       http://en.wikipedia.org/wiki/Forth_%28programming_language%29
+
+       http://galileo.phys.virginia.edu/classes/551.jvn.fall01/primer.htm
+
+       http://wiki.laptop.org/go/Forth_Lessons
+
+       Here is another "Why FORTH?" essay: http://www.jwdt.com/~paysan/why-forth.html
+
+       SETTING UP ----------------------------------------------------------------------
+
+       Let's get a few housekeeping things out of the way.  Firstly because I need to draw lots of
+       ASCII-art diagrams to explain concepts, the best way to look at this is using a window which
+       uses a fixed width font and is at least this wide:
+
+ <------------------------------------------------------------------------------------------------------------------------>
+
+       ASSEMBLING ----------------------------------------------------------------------
+
+       If you want to actually run this FORTH, rather than just read it, you will need Linux on an
+       i386.  Linux because instead of programming directly to the hardware on a bare PC which I
+       could have done, I went for a simpler tutorial by assuming that the 'hardware' is a Linux
+       process with a few basic system calls (read, write and exit and that's about all).  i386
+       is needed because I had to write the assembly for a processor, and i386 is by far the most
+       common.  (Of course when I say 'i386', any 32- or 64-bit x86 processor will do.  I'm compiling
+       this on a 64 bit AMD Opteron).
+
+       Again, to assemble this you will need gcc and gas (the GNU assembler).  The commands to
+       assemble and run the code (save this file as 'jonesforth.S') are:
+
+       gcc -m32 -nostdlib -static -Wl,-Ttext,0 -o jonesforth jonesforth.S
+       ./jonesforth
+
+       You will see lots of 'Warning: unterminated string; newline inserted' messages from the
+       assembler.  That's just because the GNU assembler doesn't have a good syntax for multi-line
+       strings (or rather it used to, but the developers removed it!) so I've abused the syntax
+       slightly to make things readable.  Ignore these warnings.
+
+       ASSEMBLER ----------------------------------------------------------------------
+
+       (You can just skip to the next section -- you don't need to be able to read assembler to
+       follow this tutorial).
+
+       However if you do want to read the assembly code here are a few notes about gas (the GNU assembler):
+
+       (1) Register names are prefixed with '%', so %eax is the 32 bit i386 accumulator.  The registers
+           available on i386 are: %eax, %ebx, %ecx, %edx, %esi, %edi, %ebp and %esp, and most of them
+           have special purposes.
+
+       (2) Add, mov, etc. take arguments in the form SRC,DEST.  So mov %eax,%ecx moves %eax -> %ecx
+
+       (3) Constants are prefixed with '$', and you mustn't forget it!  If you forget it then it
+           causes a read from memory instead, so:
+           mov $2,%eax         moves number 2 into %eax
+           mov 2,%eax          reads the 32 bit word from address 2 into %eax (ie. most likely a mistake)
+
+       (4) gas has a funky syntax for local labels, where '1f' (etc.) means label '1:' "forwards"
+           and '1b' (etc.) means label '1:' "backwards".
+
+       (5) 'ja' is "jump if above", 'jb' for "jump if below", 'je' "jump if equal" etc.
+
+       (6) gas has a reasonably nice .macro syntax, and I use them a lot to make the code shorter and
+           less repetitive.
+
+       For more help reading the assembler, do "info gas" at the Linux prompt.
+
+       Now the tutorial starts in earnest.
+
+       INDIRECT THREADED CODE ----------------------------------------------------------------------
+
+       
 
 
-#include <asm-i386/unistd.h>
 
 
-/* NOTES-------------------------------------------------------------------------------------------------------------------
 
 
-Need to say something about $ before constants.
 
 
-And about je/jne/ja/jb/jbe/etc
 
 
 
 
 
 
@@ -317,13 +433,13 @@ var_\name :
        push %eax               // push value onto stack
        NEXT
 
        push %eax               // push value onto stack
        NEXT
 
-       defcode "+!",2,ADDSTORE
+       defcode "+!",2,,ADDSTORE
        pop %ebx                // address
        pop %eax                // the amount to add
        addl %eax,(%ebx)        // add it
        NEXT
 
        pop %ebx                // address
        pop %eax                // the amount to add
        addl %eax,(%ebx)        // add it
        NEXT
 
-       defcode "-!",2,SUBSTORE
+       defcode "-!",2,,SUBSTORE
        pop %ebx                // address
        pop %eax                // the amount to subtract
        subl %eax,(%ebx)        // add it
        pop %ebx                // address
        pop %eax                // the amount to subtract
        subl %eax,(%ebx)        // add it
@@ -398,6 +514,8 @@ var_\name :
        lea 4(%ebp),%ebp        // pop return stack and throw away
        NEXT
 
        lea 4(%ebp),%ebp        // pop return stack and throw away
        NEXT
 
+#include <asm-i386/unistd.h>
+
        defcode "KEY",3,,KEY
        call _KEY
        push %eax               // push return value on stack
        defcode "KEY",3,,KEY
        call _KEY
        push %eax               // push return value on stack
@@ -943,7 +1061,7 @@ buffer:
                        DUP '\"' <>
                WHILE
                        HERE @ !b       \\ store the character in the compiled image
                        DUP '\"' <>
                WHILE
                        HERE @ !b       \\ store the character in the compiled image
-                       HERE 1 +!       \\ increment HERE pointer by 1 byte
+                       1 HERE +!       \\ increment HERE pointer by 1 byte
                REPEAT
                DROP            \\ drop the double quote character at the end
                DUP             \\ get the saved address of the length word
                REPEAT
                DROP            \\ drop the double quote character at the end
                DUP             \\ get the saved address of the length word