'SPACE' -> BL (standard FORTH word)
[rrq/jonesforth.git] / jonesforth.f
index b2cf98ac55483c03709a21bb11f16ae231f13745..711cf8521930c2d17146ad73c2467805cd550531 100644 (file)
@@ -2,7 +2,7 @@
 \      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).
-\      $Id: jonesforth.f,v 1.8 2007-09-28 19:39:21 rich Exp $
+\      $Id: jonesforth.f,v 1.11 2007-09-29 23:13:45 rich Exp $
 \
 \      The first part of this tutorial is in jonesforth.S.  Get if from http://annexia.org/forth
 \
 : MOD /MOD DROP ;
 
 \ Define some character constants
-: '\n'   10 ;
-: 'SPACE' 32 ;
+: '\n' 10 ;
+: BL   32 ; \ BL (BLank) is a standard FORTH word for space.
 
 \ CR prints a carriage return
 : CR '\n' EMIT ;
 
 \ SPACE prints a space
-: SPACE 'SPACE' EMIT ;
+: SPACE BL EMIT ;
 
 \ DUP, DROP are defined in assembly for speed, but this is how you might define them
 \ in FORTH.  Notice use of the scratch variables _X and _Y.
        ,               \ compile it
 ;
 
+\      CONTROL STRUCTURES ----------------------------------------------------------------------
+\
 \ So far we have defined only very simple definitions.  Before we can go further, we really need to
 \ make some control structures, like IF ... THEN and loops.  Luckily we can define arbitrary control
 \ structures directly in FORTH.
        SWAP !          \ and back-fill it in the original location
 ;
 
+\      COMMENTS ----------------------------------------------------------------------
+\
 \ FORTH allows ( ... ) as comments within function definitions.  This works by having an IMMEDIATE
 \ word called ( which just drops input characters until it hits the corresponding ).
 : ( IMMEDIATE
 (
        From now on we can use ( ... ) for comments.
 
+       STACK NOTATION ----------------------------------------------------------------------
+
        In FORTH style we can also use ( ... -- ... ) to show the effects that a word has on the
        parameter stack.  For example:
 
 : HEX ( -- ) 16 BASE ! ;
 
 (
+       PRINTING NUMBERS ----------------------------------------------------------------------
+
        The standard FORTH word . (DOT) is very important.  It takes the number at the top
        of the stack and prints it out.  However first I'm going to implement some lower-level
        FORTH words:
 ( This is the underlying recursive definition of U. )
 : U.           ( u -- )
        BASE @ /MOD     ( width rem quot )
-       DUP 0<> IF      ( if quotient <> 0 then )
+       ?DUP IF                 ( if quotient <> 0 then )
                RECURSE         ( print the quotient )
-       ELSE
-               DROP            ( drop the zero quotient )
        THEN
 
        ( print the remainder )
 ( This word returns the width (in characters) of an unsigned number in the current base )
 : UWIDTH       ( u -- width )
        BASE @ /        ( rem quot )
-       DUP 0<> IF      ( if quotient <> 0 then )
+       ?DUP IF         ( if quotient <> 0 then )
                RECURSE 1+      ( return 1+recursive call )
        ELSE
-               DROP            ( drop the zero quotient )
                1               ( return 1 )
        THEN
 ;
 : ALIGN HERE @ ALIGNED HERE ! ;
 
 (
+       STRINGS ----------------------------------------------------------------------
+
        S" string" is used in FORTH to define strings.  It leaves the address of the string and
        its length on the stack, (length at the top of stack).  The space following S" is the normal
        space between FORTH words and is not a part of the string.
 ;
 
 (
+       CONSTANTS AND VARIABLES ----------------------------------------------------------------------
+
        In FORTH, global constants and variables are defined like this:
 
        10 CONSTANT TEN         when TEN is executed, it leaves the integer 10 on the stack
        is the natural size for integers on this machine architecture.  On this 32 bit machine therefore
        CELLS just multiplies the top of stack by 4.
 )
-: CELLS ( n -- n ) 4* ;
+: CELLS ( n -- n ) 4 * ;
 
 (
        So now we can define VARIABLE easily in much the same way as CONSTANT above.  Refer to the
 ;
 
 (
+       VALUES ----------------------------------------------------------------------
+
        VALUEs are like VARIABLEs but with a simpler syntax.  You would generally use them when you
        want a variable which is read often, and written infrequently.
 
 ;
 
 (
+       PRINTING THE DICTIONARY ----------------------------------------------------------------------
+
        ID. takes an address of a dictionary entry and prints the word's name.
 
        For example: LATEST @ ID. would print the name of the last word that was defined.
 : WORDS
        LATEST @        ( start at LATEST dictionary entry )
        BEGIN
-               DUP 0<>         ( while link pointer is not null )
+               ?DUP            ( while link pointer is not null )
        WHILE
                DUP ?HIDDEN NOT IF      ( ignore hidden words )
                        DUP ID.         ( but if not hidden, print the word )
                SPACE
                @               ( dereference the link pointer - go to previous word )
        REPEAT
-       DROP
        CR
 ;
 
 (
+       FORGET ----------------------------------------------------------------------
+
        So far we have only allocated words and memory.  FORTH provides a rather primitive method
        to deallocate.
 
 ;
 
 (
+       DUMP ----------------------------------------------------------------------
+
        DUMP is used to dump out the contents of memory, in the 'traditional' hexdump format.
 
        Notice that the parameters to DUMP (address, length) are compatible with string words
        BEGIN
                DUP 0>          ( while len > 0 )
        WHILE
-               OVER 8 .R       ( print the address )
+               OVER 8 U.R      ( print the address )
                SPACE
 
                ( print up to 16 words on this line )
 ;
 
 (
+       CASE ----------------------------------------------------------------------
+
        CASE...ENDCASE is how we do switch statements in FORTH.  There is no generally
        agreed syntax for this, so I've gone for the syntax mandated by the ISO standard
        FORTH (ANS-FORTH).
 ;
 
 (
+       DECOMPILER ----------------------------------------------------------------------
+
        CFA> is the opposite of >CFA.  It takes a codeword and tries to find the matching
        dictionary definition.
 
 : CFA>
        LATEST @        ( start at LATEST dictionary entry )
        BEGIN
-               DUP 0<>         ( while link pointer is not null )
+               ?DUP            ( while link pointer is not null )
        WHILE
                DUP >CFA        ( cfa curr curr-cfa )
                2 PICK          ( cfa curr curr-cfa cfa )
                THEN
                @               ( follow link pointer back )
        REPEAT
-       2DROP           ( restore stack )
+       DROP            ( restore stack )
        0               ( sorry, nothing found )
 ;
 
 (
-       SEE disassembles a FORTH word.
+       SEE decompiles a FORTH word.
 
        We search for the dictionary entry of the word, then search again for the next
        word (effectively, the end of the compiled word).  This results in two pointers:
 : SEE
        WORD FIND       ( find the dictionary entry to decompile )
 
-       ( now we search again, looking for the next word )
+       ( Now we search again, looking for the next word in the dictionary.  This gives us
+         the length of the word that we will be decompiling.  (Well, mostly it does). )
        HERE @          ( address of the end of the last compiled word )
        LATEST @        ( word last curr )
        BEGIN
 
                CASE
                ' LIT OF                ( is it LIT ? )
-                       4 + DUP @       ( get next word which is the integer constant )
-                       .               ( and print it )
+                       4 + DUP @               ( get next word which is the integer constant )
+                       .                       ( and print it )
                ENDOF
                ' LITSTRING OF          ( is it LITSTRING ? )
                        [ CHAR S ] LITERAL EMIT '"' EMIT SPACE ( print S"<space> )
-                       4 + DUP @       ( get the length word )
-                       SWAP 4 + SWAP   ( end start+4 length )
-                       2DUP TELL       ( print the string )
-                       '"' EMIT SPACE  ( finish the string with a final quote )
-                       + ALIGNED       ( end start+4+len, aligned )
-                       4 -             ( because we're about to add 4 below )
+                       4 + DUP @               ( get the length word )
+                       SWAP 4 + SWAP           ( end start+4 length )
+                       2DUP TELL               ( print the string )
+                       '"' EMIT SPACE          ( finish the string with a final quote )
+                       + ALIGNED               ( end start+4+len, aligned )
+                       4 -                     ( because we're about to add 4 below )
                ENDOF
                ' 0BRANCH OF            ( is it 0BRANCH ? )
                        ." 0BRANCH ( "
-                       4 + DUP @       ( print the offset )
+                       4 + DUP @               ( print the offset )
                        .
-                       ')' EMIT SPACE
+                       ." ) "
                ENDOF
                ' BRANCH OF             ( is it BRANCH ? )
                        ." BRANCH ( "
-                       4 + DUP @       ( print the offset )
+                       4 + DUP @               ( print the offset )
                        .
-                       ')' EMIT SPACE
+                       ." ) "
                ENDOF
                ' ' OF                  ( is it ' (TICK) ? )
                        [ CHAR ' ] LITERAL EMIT SPACE
-                       4 + DUP @       ( get the next codeword )
-                       CFA>            ( and force it to be printed as a dictionary entry )
+                       4 + DUP @               ( get the next codeword )
+                       CFA>                    ( and force it to be printed as a dictionary entry )
                        ID. SPACE
                ENDOF
                ' EXIT OF               ( is it EXIT? )
                        ( We expect the last word to be EXIT, and if it is then we don't print it
                          because EXIT is normally implied by ;.  EXIT can also appear in the middle
                          of words, and then it needs to be printed. )
-                       2DUP            ( end start end start )
-                       4 +             ( end start end start+4 )
-                       <> IF           ( end start | we're not at the end )
+                       2DUP                    ( end start end start )
+                       4 +                     ( end start end start+4 )
+                       <> IF                   ( end start | we're not at the end )
                                ." EXIT "
                        THEN
                ENDOF
-               ( default case: )
-                       DUP             ( in the default case we always need to DUP before using )
-                       CFA>            ( look up the codeword to get the dictionary entry )
-                       ID. SPACE       ( and print it )
+                                       ( default case: )
+                       DUP                     ( in the default case we always need to DUP before using )
+                       CFA>                    ( look up the codeword to get the dictionary entry )
+                       ID. SPACE               ( and print it )
                ENDCASE
 
                4 +             ( end start+4 )
        2DROP           ( restore stack )
 ;
 
-( Finally print the welcome prompt. )
+(
+       C STRINGS ----------------------------------------------------------------------
+
+       FORTH strings are represented by a start address and length kept on the stack or in memory.
+
+       Most FORTHs don't handle C strings, but we need them in order to access the process arguments
+       and environment left on the stack by the Linux kernel.
+
+       The main function we need is STRLEN which works out the length of a C string.  DUP STRLEN is
+       a common idiom which 'converts' a C string into a FORTH string.  (For example, DUP STRLEN TELL
+       prints a C string).
+)
+
+(
+       Z" .." is like S" ..." except that the string is terminated by an ASCII NUL character.
+
+       To make it more like a C string, at runtime Z" just leaves the address of the string
+       on the stack (not address & length as with S").  To implement this we need to add the
+       extra NUL to the string and also a DROP instruction afterwards.  Apart from that the
+       implementation just a modified S".
+)
+: Z" IMMEDIATE
+       STATE @ IF      ( compiling? )
+               ' LITSTRING ,   ( compile LITSTRING )
+               HERE @          ( save the address of the length word on the stack )
+               0 ,             ( dummy length - we don't know what it is yet )
+               BEGIN
+                       KEY             ( get next character of the string )
+                       DUP '"' <>
+               WHILE
+                       HERE @ C!       ( store the character in the compiled image )
+                       1 HERE +!       ( increment HERE pointer by 1 byte )
+               REPEAT
+               0 HERE @ C!     ( add the ASCII NUL byte )
+               1 HERE +!
+               DROP            ( drop the double quote character at the end )
+               DUP             ( get the saved address of the length word )
+               HERE @ SWAP -   ( calculate the length )
+               4-              ( subtract 4 (because we measured from the start of the length word) )
+               SWAP !          ( and back-fill the length location )
+               ALIGN           ( round up to next multiple of 4 bytes for the remaining code )
+               ' DROP ,        ( compile DROP (to drop the length) )
+       ELSE            ( immediate mode )
+               HERE @          ( get the start address of the temporary space )
+               BEGIN
+                       KEY
+                       DUP '"' <>
+               WHILE
+                       OVER C!         ( save next character )
+                       1+              ( increment address )
+               REPEAT
+               DROP            ( drop the final " character )
+               0 SWAP C!       ( store final ASCII NUL )
+               HERE @          ( push the start address )
+       THEN
+;
+
+( STRLEN returns the length of a C string )
+: STRLEN       ( str -- len )
+       DUP             ( save start address )
+       BEGIN
+               DUP C@ 0<>      ( zero byte found? )
+       WHILE
+               1+
+       REPEAT
+
+       SWAP -          ( calculate the length )
+;
+
+(
+       STRNCMP compares two strings up to a length.  As with C's strncmp it returns 0 if they
+       are equal, or a number > 0 or < 0 indicating their order.
+)
+: STRNCMP      ( str1 str2 len -- eq? )
+       BEGIN
+               ?DUP
+       WHILE
+               ROT             ( len str1 str2 )
+               DUP C@          ( len str1 str2 char2 )
+               2 PICK C@       ( len str1 str2 char2 char1 )
+               OVER            ( len str1 str2 char2 char1 char2 )
+               -               ( len str1 str2 char2 char1-char2 )
+
+               ?DUP IF         ( strings not the same at this position? )
+                       NIP             ( len str1 str2 diff )
+                       ROT             ( len diff str1 str2 )
+                       DROP DROP       ( len diff )
+                       NIP             ( diff )
+                       EXIT
+               THEN
+
+               0= IF           ( characters are equal, but is this the end of the C string? )
+                       DROP DROP DROP
+                       0
+                       EXIT
+               THEN
+
+               1+              ( len str1 str2+1 )
+               ROT             ( str2+1 len str1 )
+               1+ ROT          ( str1+1 str2+1 len )
+               1-              ( str1+1 str2+1 len-1 )
+       REPEAT
+
+       2DROP           ( restore stack )
+       0               ( equal )
+;
+
+(
+       THE ENVIRONMENT ----------------------------------------------------------------------
+
+       Linux makes the process arguments and environment available to us on the stack.
+
+       The top of stack pointer is saved by the early assembler code when we start up in the FORTH
+       variable S0, and starting at this pointer we can read out the command line arguments and the
+       environment.
+
+       Starting at S0, S0 itself points to argc (the number of command line arguments).
+
+       S0+4 points to argv[0], S0+8 points to argv[1] etc up to argv[argc-1].
+
+       argv[argc] is a NULL pointer.
+
+       After that the stack contains environment variables, a set of pointers to strings of the
+       form NAME=VALUE and on until we get to another NULL pointer.
+
+       The first word that we define, ARGC, pushes the number of command line arguments (note that
+       as with C argc, this includes the name of the command).
+)
+: ARGC
+       S0 @ @
+;
+
+(
+       n ARGV gets the nth command line argument.
+
+       For example to print the command name you would do:
+               0 ARGV TELL CR
+)
+: ARGV ( n -- str u )
+       1+ CELLS S0 @ + ( get the address of argv[n] entry )
+       @               ( get the address of the string )
+       DUP STRLEN      ( and get its length / turn it into a FORTH string )
+;
+
+(
+       ENVIRON returns the address of the first environment string.  The list of strings ends
+       with a NULL pointer.
+
+       For example to print the first string in the environment you could do:
+               ENVIRON @ DUP STRLEN TELL
+)
+: ENVIRON      ( -- addr )
+       ARGC            ( number of command line parameters on the stack to skip )
+       2 +             ( skip command line count and NULL pointer after the command line args )
+       CELLS           ( convert to an offset )
+       S0 @ +          ( add to base stack address )
+;
+
+(
+       SYSTEM CALLS ----------------------------------------------------------------------
+
+       Some wrappers around Linux system calls
+)
+
+( BYE exits by calling the Linux exit(2) syscall. )
+: BYE          ( -- )
+       0
+       0
+       0               ( return code (0) )
+       SYS_EXIT        ( system call number )
+       SYSCALL3
+;
+
+(
+       OPEN, CREAT and CLOSE are just like the Linux syscalls open(2), creat(2) and close(2).
+
+       Notice that they take C strings and may return error codes (-errno).
+)
+: OPEN         ( mode flags c-pathname -- ret )
+       SYS_OPEN
+       SYSCALL3
+;
+
+: CREAT                ( mode c-pathname -- ret )
+       0 ROT
+       SYS_CREAT
+       SYSCALL3
+;
+
+: CLOSE                ( fd -- ret )
+       0 ROT 0 ROT
+       SYS_CLOSE
+       SYSCALL3
+;
+
+( READ and WRITE system calls. )
+: READ         ( len buffer fd -- ret )
+       SYS_READ
+       SYSCALL3
+;      
+
+: WRITE                ( len buffer fd -- ret )
+       SYS_WRITE
+       SYSCALL3
+;      
+
+(
+       ANS FORTH ----------------------------------------------------------------------
+
+       From this point we're trying to fill in the missing parts of the ISO standard, commonly
+       referred to as ANS FORTH.
+
+       http://www.taygeta.com/forth/dpans.html
+       http://www.taygeta.com/forth/dpansf.htm (list of words)
+)
+
+( C, writes a byte at the HERE pointer. )
+: C, HERE @ C! 1 HERE +! ;
+
+
+
+
+
+
+
+
+
+(
+       NOTES ----------------------------------------------------------------------
+
+       DOES> isn't possible to implement with this FORTH because we don't have a separate
+       data pointer.
+)
+
+(
+       WELCOME MESSAGE ----------------------------------------------------------------------
+
+       Print the version and OK prompt.
+)
+
 ." JONESFORTH VERSION " VERSION . CR
 ." OK "