Lots of typos fixed.
[jonesforth.git] / jonesforth.S.11
diff --git a/jonesforth.S.11 b/jonesforth.S.11
deleted file mode 100644 (file)
index 3f87d36..0000000
+++ /dev/null
@@ -1,914 +0,0 @@
-/* A 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
- */
-
-#include <asm-i386/unistd.h>
-
-/* NOTES-------------------------------------------------------------------------------------------------------------------
-
-Need to say something about $ before constants.
-
-And about je/jne/ja/jb/jbe/etc
-
-
-
-
-*/
-
-/* NEXT macro. */
-       .macro NEXT
-       lodsl
-       jmp *(%eax)
-       .endm
-
-/* Macros to deal with the return stack. */
-       .macro PUSHRSP reg
-       lea -4(%ebp),%ebp       // push reg on to return stack
-       movl \reg,(%ebp)
-       .endm
-
-       .macro POPRSP reg
-       mov (%ebp),\reg         // pop top of return stack to reg
-       lea 4(%ebp),%ebp
-       .endm
-
-/* ELF entry point. */
-       .text
-       .globl _start
-_start:
-       cld
-       mov $return_stack,%ebp  // Initialise the return stack.
-
-       mov $cold_start,%esi    // Initialise interpreter.
-       NEXT                    // Run interpreter!
-
-       .section .rodata
-cold_start:                    // High-level code without a codeword.
-       .int COLD
-
-/* DOCOL - the interpreter! */
-       .text
-       .align 4
-DOCOL:
-       PUSHRSP %esi            // push %esi on to the return stack
-       addl $4,%eax            // %eax points to codeword, so make
-       movl %eax,%esi          // %esi point to first data word
-       NEXT
-
-/*----------------------------------------------------------------------
- * Fixed sized buffers for everything.
- */
-       .bss
-
-/* FORTH return stack. */
-#define RETURN_STACK_SIZE 8192
-       .align 4096
-       .space RETURN_STACK_SIZE
-return_stack:
-
-/* Space for user-defined words. */
-#define USER_DEFS_SIZE 16384
-       .align 4096
-user_defs_start:
-       .space USER_DEFS_SIZE
-
-
-
-
-
-
-/*----------------------------------------------------------------------
- * Built-in words defined the long way.
- */
-#define F_IMMED 0x80
-#define F_HIDDEN 0x20
-
-       // Store the chain of links.
-       .set link,0
-
-       .macro defcode name, namelen, flags=0, label
-       .section .rodata
-       .align 4
-       .globl name_\label
-name_\label :
-       .int link               // link
-       .set link,name_\label
-       .byte \flags+\namelen   // flags + length byte
-       .ascii "\name"          // the name
-       .align 4
-       .globl \label
-\label :
-       .int code_\label        // codeword
-       .text
-       .align 4
-       .globl code_\label
-code_\label :                  // assembler code follows
-       .endm
-
-       .macro defword name, namelen, flags=0, label
-       .section .rodata
-       .align 4
-       .globl name_\label
-name_\label :
-       .int link               // link
-       .set link,name_\label
-       .byte \flags+\namelen   // flags + length byte
-       .ascii "\name"          // the name
-       .align 4
-       .globl \label
-\label :
-       .int DOCOL              // codeword - the interpreter
-       // list of word pointers follow
-       .endm
-
-       .macro defvar name, namelen, flags=0, label, initial=0
-       defcode \name,\namelen,\flags,\label
-       push $var_\name
-       NEXT
-       .data
-       .align 4
-var_\name :
-       .int \initial
-       .endm
-
-       // Some easy ones, written in assembly for speed
-       defcode "DROP",4,,DROP
-       pop %eax                // drop top of stack
-       NEXT
-
-       defcode "DUP",3,,DUP
-       pop %eax                // duplicate top of stack
-       push %eax
-       push %eax
-       NEXT
-
-       defcode "SWAP",4,,SWAP
-       pop %eax                // swap top of stack
-       pop %ebx
-       push %eax
-       push %ebx
-       NEXT
-
-       defcode "OVER",4,,OVER
-       mov 4(%esp),%eax        // get the second element of stack
-       push %eax               // and push it on top
-       NEXT
-
-       defcode "1+",2,,INCR
-       incl (%esp)             // increment top of stack
-       NEXT
-
-       defcode "1-",2,,DECR
-       decl (%esp)             // decrement top of stack
-       NEXT
-
-       defcode "+",1,,ADD
-       pop %eax
-       addl %eax,(%esp)
-       NEXT
-
-       defcode "-",1,,SUB
-       pop %eax
-       subl %eax,(%esp)
-       NEXT
-
-       defcode "*",1,,MUL
-       pop %eax
-       pop %ebx
-       imull %ebx,%eax
-       push %eax               // ignore overflow
-       NEXT
-
-       defcode "/",1,,DIV
-       xor %edx,%edx
-       pop %ebx
-       pop %eax
-       idivl %ebx
-       push %eax               // push quotient
-       NEXT
-
-       defcode "MOD",3,,MOD
-       xor %edx,%edx
-       pop %ebx
-       pop %eax
-       idivl %ebx
-       push %edx               // push remainder
-       NEXT
-
-       defcode "=",1,,EQU      // top two words are equal?
-       pop %eax
-       pop %ebx
-       cmp %ebx,%eax
-       je 1f
-       pushl $0
-       NEXT
-1:     pushl $1
-       NEXT
-
-       defcode "<>",2,,NEQU    // top two words are not equal?
-       pop %eax
-       pop %ebx
-       cmp %ebx,%eax
-       je 1f
-       pushl $1
-       NEXT
-1:     pushl $0
-       NEXT
-
-       defcode "0=",2,,ZEQU    // top of stack equals 0?
-       pop %eax
-       test %eax,%eax
-       jz 1f
-       pushl $0
-       NEXT
-1:     pushl $1
-       NEXT
-
-       defcode "AND",3,,AND
-       pop %eax
-       andl %eax,(%esp)
-       NEXT
-
-       defcode "OR",2,,OR
-       pop %eax
-       orl %eax,(%esp)
-       NEXT
-
-       defcode "INVERT",6,,INVERT
-       notl (%esp)
-       NEXT
-
-       // COLD must not return (ie. must not call EXIT).
-       defword "COLD",4,,COLD
-       // XXX reinitialisation of the interpreter
-       .int INTERPRETER        // call the interpreter loop (never returns)
-       .int LIT,1,SYSEXIT      // hmmm, but in case it does, exit(1).
-
-       defcode "EXIT",4,,EXIT
-       POPRSP %esi             // pop return stack into %esi
-       NEXT
-
-       defcode "LIT",3,,LIT
-       // %esi points to the next command, but in this case it points to the next
-       // literal 32 bit integer.  Get that literal into %eax and increment %esi.
-       // On x86, it's a convenient single byte instruction!  (cf. NEXT macro)
-       lodsl
-       push %eax               // push the literal number on to stack
-       NEXT
-
-       defcode "LITSTRING",9,,LITSTRING
-       lodsl                   // get the length of the string
-       push %eax               // push it on the stack
-       push %esi               // push the address of the start of the string
-       addl %eax,%esi          // skip past the string
-       addl $3,%esi            // but round up to next 4 byte boundary
-       andl $~3,%esi
-       NEXT
-
-       defcode "BRANCH",6,,BRANCH
-       add (%esi),%esi         // add the offset to the instruction pointer
-       NEXT
-
-       defcode "0BRANCH",7,,ZBRANCH
-       pop %eax
-       test %eax,%eax          // top of stack is zero?
-       jz code_BRANCH          // if so, jump back to the branch function above
-       lodsl                   // otherwise we need to skip the offset
-       NEXT
-
-       defcode "!",1,,STORE
-       pop %ebx                // address to store at
-       pop %eax                // data to store there
-       mov %eax,(%ebx)         // store it
-       NEXT
-
-       defcode "@",1,,FETCH
-       pop %ebx                // address to fetch
-       mov (%ebx),%eax         // fetch it
-       push %eax               // push value onto stack
-       NEXT
-
-/* ! and @ (STORE and FETCH) store 32-bit words.  It's also useful to be able to read and write bytes.
- * I don't know whether FORTH has these words, so I invented my own, called !b and @b.
- * Byte-oriented operations only work on architectures which permit them (i386 is one of those).
- */
-       defcode "!b",2,,STOREBYTE
-       pop %ebx                // address to store at
-       pop %eax                // data to store there
-       movb %al,(%ebx)         // store it
-       NEXT
-
-       defcode "@b",2,,FETCHBYTE
-       pop %ebx                // address to fetch
-       xor %eax,%eax
-       movb (%ebx),%al         // fetch it
-       push %eax               // push value onto stack
-       NEXT
-
-       // The STATE variable is 0 for execute mode, != 0 for compile mode
-       defvar "STATE",5,,STATE
-
-       // This points to where compiled words go.
-       defvar "HERE",4,,HERE,user_defs_start
-
-       // This is the last definition in the dictionary.
-       defvar "LATEST",6,,LATEST,name_SYSEXIT // SYSEXIT must be last in built-in dictionary
-
-       // _X, _Y and _Z are scratch variables used by standard words.
-       defvar "_X",2,,TX
-       defvar "_Y",2,,TY
-       defvar "_Z",2,,TZ
-
-       defcode "DSP@",4,,DSPFETCH
-       mov %esp,%eax
-       push %eax
-       NEXT
-
-       defcode "DSP!",4,,DSPSTORE
-       pop %esp
-       NEXT
-
-       defcode ">R",2,,TOR
-       pop %eax                // pop parameter stack into %eax
-       PUSHRSP %eax            // push it on to the return stack
-       NEXT
-
-       defcode "R>",2,,FROMR
-       POPRSP %eax             // pop return stack on to %eax
-       push %eax               // and push on to parameter stack
-       NEXT
-
-       defcode "RSP@",4,,RSPFETCH
-       push %ebp
-       NEXT
-
-       defcode "RSP!",4,,RSPSTORE
-       pop %ebp
-       NEXT
-
-       defcode "RDROP",5,,RDROP
-       lea 4(%ebp),%ebp        // pop return stack and throw away
-       NEXT
-
-       defcode "KEY",3,,KEY
-       call _KEY
-       push %eax               // push return value on stack
-       NEXT
-_KEY:
-       mov (currkey),%ebx
-       cmp (bufftop),%ebx
-       jge 1f
-       xor %eax,%eax
-       mov (%ebx),%al
-       inc %ebx
-       mov %ebx,(currkey)
-       ret
-
-1:     // out of input; use read(2) to fetch more input from stdin
-       xor %ebx,%ebx           // 1st param: stdin
-       mov $buffer,%ecx        // 2nd param: buffer
-       mov %ecx,currkey
-       mov $buffend-buffer,%edx // 3rd param: max length
-       mov $__NR_read,%eax     // syscall: read
-       int $0x80
-       test %eax,%eax          // If %eax <= 0, then exit.
-       jbe 2f
-       addl %eax,%ecx          // buffer+%eax = bufftop
-       mov %ecx,bufftop
-       jmp _KEY
-
-2:     // error or out of input: exit
-       xor %ebx,%ebx
-       mov $__NR_exit,%eax     // syscall: exit
-       int $0x80
-
-       defcode "EMIT",4,,EMIT
-       pop %eax
-       call _EMIT
-       NEXT
-_EMIT:
-       mov $1,%ebx             // 1st param: stdout
-
-       // write needs the address of the byte to write
-       mov %al,(2f)
-       mov $2f,%ecx            // 2nd param: address
-
-       mov $1,%edx             // 3rd param: nbytes = 1
-
-       mov $__NR_write,%eax    // write syscall
-       int $0x80
-       ret
-
-       .bss
-2:     .space 1                // scratch used by EMIT
-
-       defcode "WORD",4,,WORD
-       call _WORD
-       push %ecx               // push length
-       push %edi               // push base address
-       NEXT
-
-_WORD:
-       /* Search for first non-blank character.  Also skip \ comments. */
-1:
-       call _KEY               // get next key, returned in %eax
-       cmpb $'\\',%al          // start of a comment?
-       je 3f                   // if so, skip the comment
-       cmpb $' ',%al
-       jbe 1b                  // if so, keep looking
-
-       /* Search for the end of the word, storing chars as we go. */
-       mov $5f,%edi            // pointer to return buffer
-2:
-       stosb                   // add character to return buffer
-       call _KEY               // get next key, returned in %al
-       cmpb $' ',%al           // is blank?
-       ja 2b                   // if not, keep looping
-
-       /* Return the word (well, the static buffer) and length. */
-       sub $5f,%edi
-       mov %edi,%ecx           // return length of the word
-       mov $5f,%edi            // return address of the word
-       ret
-
-       /* Code to skip \ comments to end of the current line. */
-3:
-       call _KEY
-       cmpb $'\n',%al          // end of line yet?
-       jne 3b
-       jmp 1b
-
-       .bss
-       // A static buffer where WORD returns.  Subsequent calls
-       // overwrite this buffer.  Maximum word length is 32 chars.
-5:     .space 32
-
-       defcode "EMITSTRING",10,,EMITSTRING
-       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
-
-       defcode ".",1,,DOT
-       pop %eax                // Get the number to print into %eax
-       call _DOT               // Easier to do this recursively ...
-       NEXT
-_DOT:
-       mov $10,%ecx            // Base 10
-1:
-       cmp %ecx,%eax
-       jb 2f
-       xor %edx,%edx           // %edx:%eax / %ecx -> quotient %eax, remainder %edx
-       idivl %ecx
-       pushl %edx
-       call _DOT
-       popl %eax
-       jmp 1b
-2:
-       xor %ah,%ah
-       aam $10
-       cwde
-       addl $'0',%eax
-       call _EMIT
-       ret
-
-       // Parse a number from a string on the stack -- almost the opposite of . (DOT)
-       // Note that there is absolutely no error checking.  In particular the length of the
-       // string must be >= 1 bytes.
-       defcode "SNUMBER",7,,SNUMBER
-       pop %edi
-       pop %ecx
-       call _SNUMBER
-       push %eax
-       NEXT
-_SNUMBER:
-       xor %eax,%eax
-       xor %ebx,%ebx
-1:
-       imull $10,%eax          // %eax *= 10
-       movb (%edi),%bl
-       inc %edi
-       subb $'0',%bl           // ASCII -> digit
-       add %ebx,%eax
-       dec %ecx
-       jnz 1b
-       ret
-
-       defcode "FIND",4,,FIND
-       pop %edi                // %edi = address
-       pop %ecx                // %ecx = length
-       call _FIND
-       push %eax
-       NEXT
-
-_FIND:
-       push %esi               // Save %esi so we can use it in string comparison.
-
-       // Now we start searching backwards through the dictionary for this word.
-       mov var_LATEST,%edx     // LATEST points to name header of the latest word in the dictionary
-1:
-       test %edx,%edx          // NULL pointer?  (end of the linked list)
-       je 4f
-
-       // Compare the length expected and the length of the word.
-       // Note that if the F_HIDDEN flag is set on the word, then by a bit of trickery
-       // this won't pick the word (the length will appear to be wrong).
-       xor %eax,%eax
-       movb 4(%edx),%al        // %al = flags+length field
-       andb $(F_HIDDEN|0x1f),%al // %al = name length
-       cmpb %cl,%al            // Length is the same?
-       jne 2f
-
-       // Compare the strings in detail.
-       push %ecx               // Save the length
-       push %edi               // Save the address (repe cmpsb will move this pointer)
-       lea 5(%edx),%esi        // Dictionary string we are checking against.
-       repe cmpsb              // Compare the strings.
-       pop %edi
-       pop %ecx
-       jne 2f                  // Not the same.
-
-       // The strings are the same - return the header pointer in %eax
-       pop %esi
-       mov %edx,%eax
-       ret
-
-2:
-       mov (%edx),%edx         // Move back through the link field to the previous word
-       jmp 1b                  // .. and loop.
-
-4:     // Not found.
-       pop %esi
-       xor %eax,%eax           // Return zero to indicate not found.
-       ret
-
-       defcode ">CFA",4,,TCFA  // DEA -> Codeword address
-       pop %edi
-       call _TCFA
-       push %edi
-       NEXT
-_TCFA:
-       xor %eax,%eax
-       add $4,%edi             // Skip link pointer.
-       movb (%edi),%al         // Load flags+len into %al.
-       inc %edi                // Skip flags+len byte.
-       andb $0x1f,%al          // Just the length, not the flags.
-       add %eax,%edi           // Skip the name.
-       addl $3,%edi            // The codeword is 4-byte aligned.
-       andl $~3,%edi
-       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
-
-       defcode ":",1,,COLON
-
-       // Get the word and create a dictionary entry header for it.
-       call _WORD              // Returns %ecx = length, %edi = pointer to word.
-       mov %edi,%ebx           // %ebx = address of the word
-
-       movl var_HERE,%edi      // %edi is the address of the header
-       movl var_LATEST,%eax    // Get link pointer
-       stosl                   // and store it in the header.
-
-       mov %cl,%al             // Get the length.
-       orb $F_HIDDEN,%al       // Set the HIDDEN flag on this entry.
-       stosb                   // Store the length/flags byte.
-       push %esi
-       mov %ebx,%esi           // %esi = word
-       rep movsb               // Copy the word
-       pop %esi
-       addl $3,%edi            // Align to next 4 byte boundary.
-       andl $~3,%edi
-
-       movl $DOCOL,%eax        // The codeword for user-created words is always DOCOL (the interpreter)
-       stosl
-
-       // Header built, so now update LATEST and HERE.
-       // We'll be compiling words and putting them HERE.
-       movl var_HERE,%eax
-       movl %eax,var_LATEST
-       movl %edi,var_HERE
-
-       // And go into compile mode by setting STATE to 1.
-       movl $1,var_STATE
-       NEXT
-
-       defcode ",",1,,COMMA
-       pop %eax                // Code pointer to store.
-       call _COMMA
-       NEXT
-_COMMA:
-       movl var_HERE,%edi      // HERE
-       stosl                   // Store it.
-       movl %edi,var_HERE      // Update HERE (incremented)
-       ret
-
-       defcode "HIDDEN",6,,HIDDEN
-       call _HIDDEN
-       NEXT
-_HIDDEN:
-       movl var_LATEST,%edi    // LATEST word.
-       addl $4,%edi            // Point to name/flags byte.
-       xorb $F_HIDDEN,(%edi)   // Toggle the HIDDEN bit.
-       ret
-
-       defcode "IMMEDIATE",9,F_IMMED,IMMEDIATE
-       call _IMMEDIATE
-       NEXT
-_IMMEDIATE:
-       movl var_LATEST,%edi    // LATEST word.
-       addl $4,%edi            // Point to name/flags byte.
-       xorb $F_IMMED,(%edi)    // Toggle the IMMED bit.
-       ret
-
-       defcode ";",1,F_IMMED,SEMICOLON
-       movl $EXIT,%eax         // EXIT is the final codeword in compiled words.
-       call _COMMA             // Store it.
-       call _HIDDEN            // Toggle the HIDDEN flag (unhides the new word).
-       xor %eax,%eax           // Set STATE to 0 (back to execute mode).
-       movl %eax,var_STATE
-       NEXT
-
-/* This definiton of ' (TICK) is strictly cheating - it also 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
-
-/* This interpreter is pretty simple, but remember that in FORTH you can always override
- * it later with a more powerful one!
- */
-       defword "INTERPRETER",11,,INTERPRETER
-       .int INTERPRET,RDROP,INTERPRETER
-
-       defcode "INTERPRET",9,,INTERPRET
-       call _WORD              // Returns %ecx = length, %edi = pointer to word.
-
-       // Is it in the dictionary?
-       xor %eax,%eax
-       movl %eax,interpret_is_lit // Not a literal number (not yet anyway ...)
-       call _FIND              // Returns %eax = pointer to header or 0 if not found.
-       test %eax,%eax          // Found?
-       jz 1f
-
-       // In the dictionary.  Is it an IMMEDIATE codeword?
-       mov %eax,%edi           // %edi = dictionary entry
-       movb 4(%edi),%al        // Get name+flags.
-       push %ax                // Just save it for now.
-       call _TCFA              // Convert dictionary entry (in %edi) to codeword pointer.
-       pop %ax
-       andb $F_IMMED,%al       // Is IMMED flag set?
-       mov %edi,%eax
-       jnz 4f                  // If IMMED, jump straight to executing.
-
-       jmp 2f
-
-1:     // Not in the dictionary (not a word) so assume it's a literal number.
-       incl interpret_is_lit
-       call _SNUMBER           // Returns the parsed number in %eax
-       mov %eax,%ebx
-       mov $LIT,%eax           // The word is LIT
-
-2:     // Are we compiling or executing?
-       movl var_STATE,%edx
-       test %edx,%edx
-       jz 4f                   // Jump if executing.
-
-       // Compiling - just append the word to the current dictionary definition.
-       call _COMMA
-       mov interpret_is_lit,%ecx // Was it a literal?
-       test %ecx,%ecx
-       jz 3f
-       mov %ebx,%eax           // Yes, so LIT is followed by a number.
-       call _COMMA
-3:     NEXT
-
-4:     // Executing - run it!
-       mov interpret_is_lit,%ecx // Literal?
-       test %ecx,%ecx          // Literal?
-       jnz 5f
-
-       // Not a literal, execute it now.  This never returns, but the codeword will
-       // eventually call NEXT which will reenter the loop in INTERPRETER.
-       jmp *(%eax)
-
-5:     // Executing a literal, which means push it on the stack.
-       push %ebx
-       NEXT
-
-       .data
-       .align 4
-interpret_is_lit:
-       .int 0                  // Flag used to record if reading a literal
-
-       // 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.
- */
-       .data
-       .align 4096
-buffer:
-       // XXX gives 'Warning: unterminated string; newline inserted' messages which you can ignore
-       .ascii "\
-\\ Define some character constants
-: '\\n'   10 ;
-: 'SPACE' 32 ;
-: '\"'    34 ;
-: ':'     58 ;
-
-\\ CR prints a carriage return
-: CR '\\n' EMIT ;
-
-\\ SPACE prints a space
-: SPACE 'SPACE' EMIT ;
-
-\\ Primitive . (DOT) function doesn't follow with a blank, so redefine it to behave like FORTH.
-\\ Notice how we can trivially redefine existing functions.
-: . . SPACE ;
-
-\\ 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.
-\\ : DUP _X ! _X @ _X @ ;
-\\ : DROP _X ! ;
-
-\\ [ and ] allow you to break into immediate mode while compiling a word.
-: [ IMMEDIATE          \\ define [ as an immediate word
-       0 STATE !       \\ go into immediate mode
-       ;
-
-: ]
-       1 STATE !       \\ go back to compile mode
-       ;
-
-\\ LITERAL takes whatever is on the stack and compiles LIT <foo>
-: LITERAL IMMEDIATE
-       ' LIT ,         \\ compile LIT
-       ,               \\ compile the literal itself (from the stack)
-       ;
-
-\\ condition IF true-part THEN rest
-\\   compiles to:
-\\ condition 0BRANCH OFFSET true-part rest
-\\   where OFFSET is the offset of 'rest'
-\\ condition IF true-part ELSE false-part THEN
-\\   compiles to:
-\\ condition 0BRANCH OFFSET true-part BRANCH OFFSET2 false-part rest
-\\   where OFFSET if the offset of false-part and OFFSET2 is the offset of rest
-
-\\ IF is an IMMEDIATE word which compiles 0BRANCH followed by a dummy offset, and places
-\\ the address of the 0BRANCH on the stack.  Later when we see THEN, we pop that address
-\\ off the stack, calculate the offset, and back-fill the offset.
-: IF IMMEDIATE
-       ' 0BRANCH ,     \\ compile 0BRANCH
-       HERE @          \\ save location of the offset on the stack
-       0 ,             \\ compile a dummy offset
-;
-
-: THEN IMMEDIATE
-       DUP
-       HERE @ SWAP -   \\ calculate the offset from the address saved on the stack
-       SWAP !          \\ store the offset in the back-filled location
-;
-
-: ELSE IMMEDIATE
-       ' BRANCH ,      \\ definite branch to just over the false-part
-       HERE @          \\ save location of the offset on the stack
-       0 ,             \\ compile a dummy offset
-       SWAP            \\ now back-fill the original (IF) offset
-       DUP             \\ same as for THEN word above
-       HERE @ SWAP -
-       SWAP !
-;
-
-\\ BEGIN loop-part condition UNTIL
-\\   compiles to:
-\\ loop-part condition 0BRANCH OFFSET
-\\   where OFFSET points back to the loop-part
-\\ This is like do { loop-part } while (condition) in the C language
-: BEGIN IMMEDIATE
-       HERE @          \\ save location on the stack
-;
-
-: UNTIL IMMEDIATE
-       ' 0BRANCH ,     \\ compile 0BRANCH
-       HERE @ -        \\ calculate the offset from the address saved on the stack
-       ,               \\ compile the offset here
-;
-
-\\ BEGIN loop-part AGAIN
-\\   compiles to:
-\\ loop-part BRANCH OFFSET
-\\   where OFFSET points back to the loop-part
-\\ In other words, an infinite loop which can only be returned from with EXIT
-: AGAIN IMMEDIATE
-       ' BRANCH ,      \\ compile BRANCH
-       HERE @ -        \\ calculate the offset back
-       ,               \\ compile the offset here
-;
-
-\\ BEGIN condition WHILE loop-part REPEAT
-\\   compiles to:
-\\ condition 0BRANCH OFFSET2 loop-part BRANCH OFFSET
-\\   where OFFSET points back to condition (the beginning) and OFFSET2 points to after the whole piece of code
-\\ So this is like a while (condition) { loop-part } loop in the C language
-: WHILE IMMEDIATE
-       ' 0BRANCH ,     \\ compile 0BRANCH
-       HERE @          \\ save location of the offset2 on the stack
-       0 ,             \\ compile a dummy offset2
-;
-
-: REPEAT IMMEDIATE
-       ' BRANCH ,      \\ compile BRANCH
-       SWAP            \\ get the original offset (from BEGIN)
-       HERE @ - ,      \\ and compile it after BRANCH
-       DUP
-       HERE @ SWAP -   \\ calculate the offset2
-       SWAP !          \\ and back-fill it in the original location
-;
-
-\\ With the looping constructs, we can now write SPACES, which writes n spaces to stdout.
-: SPACES
-       BEGIN
-               SPACE   \\ print a space
-               1-      \\ until we count down to 0
-               DUP 0=
-       UNTIL
-;
-
-\\ .\" is the print string operator in FORTH.  Example: .\" Something to print\"
-\\ The space after the operator is the ordinary space required between words.
-\\ This is tricky to define because it has to do different things depending on whether
-\\ we are compiling or in immediate mode.  (Thus the word is marked IMMEDIATE so it can
-\\ detect this and do different things).
-\\ In immediate mode we just keep reading characters and printing them until we get to
-\\ the next double quote.
-\\ In compile mode we have the problem of where we're going to store the string (remember
-\\ that the input buffer where the string comes from may be overwritten by the time we
-\\ come round to running the function).  We store the string in the compiled function
-\\ like this:
-\\   LITSTRING, string length, string rounded up to 4 bytes, EMITSTRING, ...
-: .\" IMMEDIATE
-       STATE @         \\ compiling?
-       IF
-               ' 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 @ !b       \\ store the character in the compiled image
-                       HERE @ 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
-               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
-               HERE @          \\ round up to next multiple of 4 bytes for the remaining code
-               3 +
-               3 INVERT AND
-               HERE !
-               ' EMITSTRING ,  \\ compile the final EMITSTRING
-       ELSE
-               \\ In immediate mode, just read characters and print them until we get
-               \\ to the ending double quote.  Much simpler!
-               BEGIN
-                       KEY
-                       DUP '\"' = IF EXIT THEN
-                       EMIT
-               AGAIN
-       THEN
-;
-
-: TEST .\" hello, world..!\" CR ;
-
-
-\\ Finally print the welcome prompt.
-.\" OK \"
-"
-
-_initbufftop:
-       .align 4096
-buffend:
-
-currkey:
-       .int buffer
-bufftop:
-       .int _initbufftop