Linux system calls added and SYSEXIT removed.
[rrq/jonesforth.git] / jonesforth.S
index 5c1249c13ebc5d472464427fdd88cd1b0acc83e1..624eba46460c14f1e560a7eaeaa5072afc854a9c 100644 (file)
@@ -1,11 +1,11 @@
 /*     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.S,v 1.36 2007-09-27 23:09:39 rich Exp $
+       $Id: jonesforth.S,v 1.40 2007-09-29 22:12:07 rich Exp $
 
        gcc -m32 -nostdlib -static -Wl,-Ttext,0 -o jonesforth jonesforth.S
 */
-       .set JONES_VERSION,36
+       .set JONES_VERSION,39
 /*
        INTRODUCTION ----------------------------------------------------------------------
 
@@ -757,6 +757,14 @@ code_\label :                      // assembler code follows
        push %ecx
        NEXT
 
+       defcode "?DUP",4,,QDUP  // duplicate top of stack if non-zero
+       pop %eax
+       test %eax,%eax
+       jz 1f
+       push %eax
+1:     push %eax
+       NEXT
+
        defcode "1+",2,,INCR
        incl (%esp)             // increment top of stack
        NEXT
@@ -1111,7 +1119,7 @@ var_\name :
 */
        defvar "STATE",5,,STATE
        defvar "HERE",4,,HERE,user_defs_start
-       defvar "LATEST",6,,LATEST,name_SYSEXIT // SYSEXIT must be last in built-in dictionary
+       defvar "LATEST",6,,LATEST,name_SYSCALL3 // SYSCALL3 must be last in built-in dictionary
        defvar "_X",2,,TX
        defvar "_Y",2,,TY
        defvar "_Z",2,,TZ
@@ -1132,8 +1140,13 @@ var_\name :
        F_IMMED         The IMMEDIATE flag's actual value.
        F_HIDDEN        The HIDDEN flag's actual value.
        F_LENMASK       The length mask in the flags/len byte.
+
+       SYS_*           and the numeric codes of various Linux syscalls (from <asm/unistd.h>)
 */
 
+//#include <asm-i386/unistd.h> // you might need this instead
+#include <asm/unistd.h>
+
        .macro defconst name, namelen, flags=0, label, value
        defcode \name,\namelen,\flags,\label
        push $\value
@@ -1147,6 +1160,13 @@ var_\name :
        defconst "F_HIDDEN",8,,__F_HIDDEN,F_HIDDEN
        defconst "F_LENMASK",9,,__F_LENMASK,F_LENMASK
 
+       defconst "SYS_EXIT",8,,SYS_EXIT,__NR_exit
+       defconst "SYS_OPEN",8,,SYS_OPEN,__NR_open
+       defconst "SYS_CLOSE",9,,SYS_CLOSE,__NR_close
+       defconst "SYS_READ",8,,SYS_READ,__NR_read
+       defconst "SYS_WRITE",9,,SYS_WRITE,__NR_write
+       defconst "SYS_CREAT",9,,SYS_CREAT,__NR_creat
+
 /*
        RETURN STACK ----------------------------------------------------------------------
 
@@ -1218,8 +1238,6 @@ var_\name :
        exits the program, which is why when you hit ^D the FORTH system cleanly exits.
 */
 
-#include <asm-i386/unistd.h>
-
        defcode "KEY",3,,KEY
        call _KEY
        push %eax               // push return value on stack
@@ -1924,10 +1942,13 @@ _COMMA:
        NEXT
 
 /*
-       PRINTING STRINGS ----------------------------------------------------------------------
+       LITERAL STRINGS ----------------------------------------------------------------------
 
-       LITSTRING and EMITSTRING are primitives used to implement the ." and S" operators
-       (which are written in FORTH).  See the definition of those operators below.
+       LITSTRING is a primitive used to implement the ." and S" operators (which are written in
+       FORTH).  See the definition of those operators later.
+
+       TELL just prints a string.  It's more efficient to define this in assembly because we
+       can make it a single Linux syscall.
 */
 
        defcode "LITSTRING",9,,LITSTRING
@@ -1939,7 +1960,7 @@ _COMMA:
        andl $~3,%esi
        NEXT
 
-       defcode "EMITSTRING",10,,EMITSTRING
+       defcode "TELL",4,,TELL
        mov $1,%ebx             // 1st param: stdout
        pop %edx                // 3rd param: length of string
        pop %ecx                // 2nd param: address of string
@@ -1959,7 +1980,6 @@ _COMMA:
        // COLD must not return (ie. must not call EXIT).
        defword "COLD",4,,COLD
        .int INTERPRETER        // call the interpreter loop (never returns)
-       .int LIT,1,SYSEXIT      // hmmm, but in case it does, exit(1).
 
 /* This interpreter is pretty simple, but remember that in FORTH you can always override
  * it later with a more powerful one!
@@ -2033,11 +2053,12 @@ interpret_is_lit:
        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 exits the process using Linux exit syscall.
+       SYSCALL3 makes a standard Linux system call.  (See <asm/unistd.h> for a list of system call
+       numbers).  This is the form to use when the function takes up to three parameters.
 
-       In this FORTH, SYSEXIT must be the last word in the built-in (assembler) dictionary because we
+       In this FORTH, SYSCALL3 must be the last word in the built-in (assembler) dictionary because we
        initialise the LATEST variable to point to it.  This means that if you want to extend the assembler
-       part, you must put new words before SYSEXIT, or else change how LATEST is initialised.
+       part, you must put new words before SYSCALL3, or else change how LATEST is initialised.
 */
 
        defcode "CHAR",4,,CHAR
@@ -2047,11 +2068,14 @@ interpret_is_lit:
        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
+       defcode "SYSCALL3",8,,SYSCALL3
+       pop %eax                // System call number (see <asm/unistd.h>)
+       pop %ebx                // First parameter.
+       pop %ecx                // Second parameter
+       pop %edx                // Third parameter
        int $0x80
+       push %eax               // Result (negative for -errno)
+       NEXT
 
 /*
        START OF FORTH CODE ----------------------------------------------------------------------