PD release.
[jonesforth.git] / jonesforth.S
index 20f6e69..6ad3e92 100644 (file)
@@ -1,5 +1,6 @@
 /*     A sometimes minimal FORTH compiler and tutorial for Linux / i386 systems. -*- asm -*-
        By Richard W.M. Jones <rich@annexia.org> http://annexia.org/forth
+       This is PUBLIC DOMAIN (see public domain release statement below).
 
        gcc -m32 -nostdlib -static -Wl,-Ttext,0 -o jonesforth jonesforth.S
 
        Also I used this document (http://ftp.funet.fi/pub/doc/IOCCC/1992/buzzard.2.design) which really
        defies easy explanation.
 
+       PUBLIC DOMAIN ----------------------------------------------------------------------
+
+       I, the copyright holder of this work, hereby release it into the public domain. This applies worldwide.
+
+       In case this is not legally possible, I grant any entity the right to use this work for any purpose,
+       without any conditions, unless such conditions are required by law.
+
        SETTING UP ----------------------------------------------------------------------
 
        Let's get a few housekeeping things out of the way.  Firstly because I need to draw lots of
        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.
 
+       If you want to run your own FORTH programs you can do:
+
+       ./jonesforth < myprog.f
+
+       If you want to load your own FORTH code and then continue reading user commands, you can do:
+
+       cat myfunctions.f - | ./jonesforth
+
        ASSEMBLER ----------------------------------------------------------------------
 
        (You can just skip to the next section -- you don't need to be able to read assembler to
@@ -1384,10 +1400,10 @@ _TCFA:
        FORTH solves this rather elegantly and as you might expect in a very low-level way which
        allows you to change how the compiler works in your own code.
 
-       FORTH has an interpreter function (a true interpreter this time, not DOCOL) which runs in a
+       FORTH has an INTERPRETER function (a true interpreter this time, not DOCOL) which runs in a
        loop, reading words (using WORD), looking them up (using FIND), turning them into codeword
        points (using >CFA) and deciding what to do with them.  What it does depends on the mode
-       of the interpreter (in variable STATE).  When STATE is zero, the interpreter just calls
+       of the interpreter (in variable STATE).  When STATE is zero, the interpreter just runs
        each word as it looks them up.  (Known as immediate mode).
 
        The interesting stuff happens when STATE is non-zero -- compiling mode.  In this mode the
@@ -1441,8 +1457,35 @@ _TCFA:
        +---------+---+---+---+---+---+---+---+---+------------+------------+------------+
                    len                         pad  codeword
 
+       The issue is what happens next.  Obviously what we _don't_ want to happen is that we
+       read ; and compile it and go on compiling everything afterwards.
+
+       At this point, FORTH uses a trick.  Remember the length byte in the dictionary definition
+       isn't just a plain length byte, but can also contain flags.  One flag is called the
+       IMMEDIATE flag (F_IMMED in this code).  If a word in the dictionary is flagged as
+       IMMEDIATE then the interpreter runs it immediately _even if it's in compile mode_.
+
+       I hope I don't need to explain that ; (SEMICOLON) is an IMMEDIATE flagged word.  And
+       all it does is append the codeword for EXIT on to the current definition and switch
+       back to immediate mode (set STATE back to 0).  After executing ; we get this:
+
+       +---------+---+---+---+---+---+---+---+---+------------+------------+------------+------------+
+       | LINK    | 6 | D | O | U | B | L | E | 0 | DOCOL      | DUP        | +          | EXIT       |
+       +---------+---+---+---+---+---+---+---+---+------------+------------+------------+------------+
+                   len                         pad  codeword                                          ^
+                                                                                                      |
+                                                                                                     HERE
 
+       And that's it, job done, our new definition is compiled.
 
+       The only last wrinkle in this is that while our word was being compiled, it was in a
+       half-finished state.  We certainly wouldn't want DOUBLE to be called somehow during
+       this time.  There are several ways to stop this from happening, but in FORTH what we
+       do is flag the word with the HIDDEN flag (F_HIDDEN in this code) just while it is
+       being compiled.  This prevents FIND from finding it, and thus in theory stops any
+       chance of it being called.
+
+       Compared to the description above, the actual definition of : (COLON) is comparatively simple:
 */
 
        defcode ":",1,,COLON
@@ -1478,6 +1521,11 @@ _TCFA:
        movl $1,var_STATE
        NEXT
 
+/*
+       , (COMMA) is a standard FORTH word which appends a 32 bit integer (normally a codeword
+       pointer) to the user data area pointed to by HERE, and adds 4 to HERE.
+*/
+
        defcode ",",1,,COMMA
        pop %eax                // Code pointer to store.
        call _COMMA
@@ -1488,6 +1536,10 @@ _COMMA:
        movl %edi,var_HERE      // Update HERE (incremented)
        ret
 
+/*
+       ; (SEMICOLON) is also elegantly simple.  Notice the F_IMMED flag.
+*/
+
        defcode ";",1,F_IMMED,SEMICOLON
        movl $EXIT,%eax         // EXIT is the final codeword in compiled words.
        call _COMMA             // Store it.
@@ -1496,6 +1548,27 @@ _COMMA:
        movl %eax,var_STATE
        NEXT
 
+/*
+       IMMEDIATE mode words aren't just for the FORTH compiler to use.  You can define your
+       own IMMEDIATE words too.  The IMMEDIATE word toggles the F_IMMED (IMMEDIATE flag) on the
+       most recently defined word, or on the current word if you call it in the middle of a
+       definition.
+
+       Typical usage is:
+
+       : MYIMMEDWORD IMMEDIATE
+               ...definition...
+       ;
+
+       but some FORTH programmers write this instead:
+
+       : MYIMMEDWORD
+               ...definition...
+       ; IMMEDIATE
+
+       The two are basically equivalent.
+*/
+
        defcode "IMMEDIATE",9,F_IMMED,IMMEDIATE
        call _IMMEDIATE
        NEXT
@@ -1505,6 +1578,11 @@ _IMMEDIATE:
        xorb $F_IMMED,(%edi)    // Toggle the IMMED bit.
        ret
 
+/*
+       HIDDEN toggles the other flag, F_HIDDEN, of the latest word.  Note that words flagged
+       as hidden are defined but cannot be called, so this is rarely used.
+*/
+
        defcode "HIDDEN",6,,HIDDEN
        call _HIDDEN
        NEXT
@@ -1514,19 +1592,78 @@ _HIDDEN:
        xorb $F_HIDDEN,(%edi)   // Toggle the HIDDEN bit.
        ret
 
-       defcode "CHAR",4,,CHAR
-       call _WORD              // Returns %ecx = length, %edi = pointer to word.
-       xor %eax,%eax
-       movb (%edi),%al         // Get the first character of the word.
-       push %eax               // Push it onto the stack.
-       NEXT
+/*
+       ' (TICK) is a standard FORTH word which returns the codeword pointer of the next word.
+
+       The common usage is:
+
+       ' FOO ,
+
+       which appends the codeword of FOO to the current word we are defining (this only works in compiled code).
+
+       You tend to use ' in IMMEDIATE words.  For example an alternate (and rather useless) way to define
+       a literal 2 might be:
+
+       : LIT2 IMMEDIATE
+               ' LIT ,         \ Appends LIT to the currently-being-defined word
+               2 ,             \ Appends the number 2 to the currently-being-defined word
+       ;
+
+       So you could do:
+
+       : DOUBLE LIT2 * ;
 
-/* This definiton of ' (TICK) is strictly cheating - it also only works in compiled code. */
+       (If you don't understand how LIT2 works, then you should review the material about compiling words
+       and immediate mode).
+
+       This definition of ' uses a cheat which I copied from buzzard92.  As a result it only works in
+       compiled code.
+*/
        defcode "'",1,,TICK
        lodsl                   // Get the address of the next word and skip it.
        pushl %eax              // Push it on the stack.
        NEXT
 
+/*
+       BRANCHING ----------------------------------------------------------------------
+
+       It turns out that all you need in order to define looping constructs, IF-statements, etc.
+       are two primitives.
+
+       BRANCH is an unconditional branch. 0BRANCH is a conditional branch (it only branches if the
+       top of stack is zero).
+
+       This is how BRANCH works.  When BRANCH executes, %esi starts by pointing to the offset:
+
+       +---------------------+-------+---- - - ---+------------+------------+---- - - - ----+------------+
+       | (Dictionary header) | DOCOL |            | BRANCH     | offset     | (skipped)     | word       |
+       +---------------------+-------+---- - - ---+------------+-----|------+---- - - - ----+------------+
+                                                                  ^  |                       ^
+                                                                  |  |                       |
+                                                                  |  +-----------------------+
+                                                                 %esi added to offset
+
+       The offset is added to %esi to make the new %esi, and the result is that when NEXT runs, execution
+       continues at the branch target.  Negative offsets work as expected.
+
+       0BRANCH is the same except the branch happens conditionally.
+
+       Now standard FORTH words such as IF, THEN, ELSE, WHILE, REPEAT, etc. are implemented entirely
+       in FORTH.  They are IMMEDIATE words which append various combinations of BRANCH or 0BRANCH
+       into the word currently being compiled.
+
+       As an example, code written like this:
+
+               condition-code IF true-part THEN rest-code
+
+       compiles to:
+
+               condition-code 0BRANCH OFFSET true-part rest-code
+                                         |             ^
+                                         |             |
+                                         +-------------+
+*/
+
        defcode "BRANCH",6,,BRANCH
        add (%esi),%esi         // add the offset to the instruction pointer
        NEXT
@@ -1538,6 +1675,13 @@ _HIDDEN:
        lodsl                   // otherwise we need to skip the offset
        NEXT
 
+/*
+       PRINTING STRINGS ----------------------------------------------------------------------
+
+       LITSTRING and EMITSTRING are primitives used to implement the ." operator (which is
+       written in FORTH).  See the definition of that operator below.
+*/
+
        defcode "LITSTRING",9,,LITSTRING
        lodsl                   // get the length of the string
        push %eax               // push it on the stack
@@ -1551,12 +1695,20 @@ _HIDDEN:
        mov $1,%ebx             // 1st param: stdout
        pop %ecx                // 2nd param: address of string
        pop %edx                // 3rd param: length of string
-
        mov $__NR_write,%eax    // write syscall
        int $0x80
-
        NEXT
 
+/*
+       COLD START AND INTERPRETER ----------------------------------------------------------------------
+
+       COLD is the first FORTH function called, almost immediately after the FORTH system "boots".
+
+       INTERPRETER is the FORTH interpreter ("toploop", "toplevel" or REPL might be a more accurate
+       description).
+*/
+
+
        // COLD must not return (ie. must not call EXIT).
        defword "COLD",4,,COLD
        .int INTERPRETER        // call the interpreter loop (never returns)
@@ -1628,19 +1780,60 @@ _HIDDEN:
 interpret_is_lit:
        .int 0                  // Flag used to record if reading a literal
 
+/*
+       ODDS AND ENDS ----------------------------------------------------------------------
+
+       CHAR puts the ASCII code of the first character of the following word on the stack.  For example
+       CHAR A puts 65 on the stack.
+
+       SYSEXIT pops the status off the stack and exits the process (using Linux exit syscall).
+*/
+
+       defcode "CHAR",4,,CHAR
+       call _WORD              // Returns %ecx = length, %edi = pointer to word.
+       xor %eax,%eax
+       movb (%edi),%al         // Get the first character of the word.
+       push %eax               // Push it onto the stack.
+       NEXT
+
        // NB: SYSEXIT must be the last entry in the built-in dictionary.
        defcode SYSEXIT,7,,SYSEXIT
        pop %ebx
        mov $__NR_exit,%eax
        int $0x80
 
-/*----------------------------------------------------------------------
- * Input buffer & initial input.
- */
+/*
+       START OF FORTH CODE ----------------------------------------------------------------------
+
+       We've now reached the stage where the FORTH system is running and self-hosting.  All further
+       words can be written as FORTH itself, including words like IF, THEN, .", etc which in most
+       languages would be considered rather fundamental.
+
+       As a kind of trick, I prefill the input buffer with the initial FORTH code.  Once this code
+       has run (when we get to the "OK" prompt), this input buffer is reused for reading user input.
+
+       Some notes about the code:
+
+       \ (backslash) is the FORTH way to start a comment which goes up to the next newline.  However
+       because this is a C-style string, I have to escape the backslash, which is why they appear as
+       \\ comment.
+
+       Similarly, any backslashes in the code are doubled, and " becomes \" (eg. the definition of ."
+       is written as : .\" ... ;)
+
+       I use indenting to show structure.  The amount of whitespace has no meaning to FORTH however
+       except that you must use at least one whitespace character between words, and words themselves
+       cannot contain whitespace.
+
+       FORTH is case-sensitive.  Use capslock!
+
+       Enjoy!
+*/
+
        .data
        .align 4096
 buffer:
-       // XXX gives 'Warning: unterminated string; newline inserted' messages which you can ignore
+       // Multi-line constant gives 'Warning: unterminated string; newline inserted' messages which you can ignore
        .ascii "\
 \\ Define some character constants
 : '\\n'   10 ;
@@ -1869,3 +2062,5 @@ currkey:
        .int buffer
 bufftop:
        .int _initbufftop
+
+/* END OF jonesforth.S */