Initial commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1c5e908
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+*.html
+*.COM
diff --git a/DICTNRY.ASM b/DICTNRY.ASM
new file mode 100644
index 0000000..d7320bd
--- /dev/null
+++ b/DICTNRY.ASM
@@ -0,0 +1,307 @@
+;;; Dictionary manipulation & memory management words
+
+ ;; ( addr len -- entry? )
+ DEFWORD_RAW FIND, 'FIND'
+ POP CX ; String length
+ POP DI ; Start pointer
+ MOV BX, WORD [VAR_LATEST]
+
+.LOOP:
+ ;; BX and DI are clobbered
+ PUSH BX
+ PUSH DI
+ CALL WORD_MATCHES
+ POP DI
+ POP BX
+
+ TEST AX, AX
+ JNZ .MATCH
+
+ MOV BX, WORD [BX] ; Offset 0 = *LINK
+ TEST BX, BX
+ JNZ .LOOP ; If BX is 0 (end) fall through
+
+.MATCH:
+ PUSH BX ; BX holds dict entry
+ NEXT
+
+
+ ;; BX - Word
+ ;; CX - Length
+ ;; DI - Name
+ ;;
+ ;; All three parameter registers may be clobbered.
+ ;;
+ ;; Return: AX - 0 or 1
+WORD_MATCHES:
+ MOV AL, BYTE [BX + WORDSZ] ; Word length
+ AND AL, LENGTH_MASK | HIDDEN_BIT
+ CMP AL, CL
+
+ JE .EQUAL
+
+ XOR AX, AX
+ RET
+
+.EQUAL:
+ PUSH SI
+ LEA SI, [BX + 3] ; Point to the dict entry name
+
+.LOOP:
+ CMPSB
+ JNE .END
+ LOOP .LOOP
+
+ MOV AX, 1
+ POP SI
+ RET
+
+.END:
+ XOR AX, AX
+ POP SI
+ RET
+
+
+ ;; ( *addr -- value )
+ DEFWORD_RAW GET, '@'
+ POP BX
+ PUSH WORD [BX]
+ NEXT
+
+
+ ;; ( value *addr -- )
+ DEFWORD_RAW SET, '!'
+ POP BX
+ POP AX
+ MOV WORD [BX], AX
+ NEXT
+
+
+ DEFWORD_RAW GETCHAR, 'C@'
+ POP BX
+ XOR AX, AX
+ MOV AL, BYTE [BX]
+ PUSH AX
+ NEXT
+
+
+ ;; Code field address
+ DEFWORD_RAW CFA, '>CFA'
+ POP BX
+ ADD BX, 2
+
+ XOR CH, CH
+ MOV CL, BYTE [BX] ; String length
+ AND CL, LENGTH_MASK
+ ADD BX, CX ; Code field address
+ ADD BX, 2 ; 1 to round up, 1 to skip length
+ AND BX, (~1) ; Zero the last bit
+
+
+ PUSH BX
+ NEXT
+
+
+ DEFWORD_THREADED DFA, '>DFA'
+ DW CFA, ADD2, EXIT
+
+
+ DEFWORD_RAW CREATE, 'CREATE'
+ POP CX ; Length
+ POP BX ; String
+
+ CALL DO_CREATE
+ NEXT
+
+ ;; CX = Length
+ ;; BX = Address
+ ;;
+ ;; AX, BX, CX, DX, DI clobbered
+DO_CREATE:
+ PUSH SI ; Save SI
+
+ MOV SI, BX
+ MOV DI, [VAR_HERE] ; Top of dictionary
+ MOV DX, DI ; New LATEST
+
+ MOV AX, [VAR_LATEST]
+ STOSW ; Link pointer
+
+ MOV AX, CX ; Length
+ STOSB
+
+ REP MOVSB ; Copy string
+
+ TEST DI, 1
+ JZ .DONE
+
+ INC DI ; Pad
+
+.DONE:
+ MOV [VAR_HERE], DI
+ MOV [VAR_LATEST], DX
+
+ POP SI
+ RET
+
+
+ DEFWORD_RAW COMMA, ','
+ POP AX
+ MOV DI, [VAR_HERE]
+ STOSW
+ MOV [VAR_HERE], DI
+ NEXT
+
+
+ ;; Switch to interpret mode
+ DEFWORD_RAW_IMMEDIATE LEFTBRACKET, '['
+ MOV WORD [VAR_STATE], 0
+ NEXT
+
+
+ DEFWORD_RAW_IMMEDIATE RIGHTBRACKET, ']'
+ MOV WORD [VAR_STATE], 1
+ NEXT
+
+
+ DEFWORD_RAW IMMEDIATE, 'IMMEDIATE'
+ MOV BX, [VAR_LATEST]
+ XOR BYTE [BX + 2], IMMEDIATE_BIT
+ NEXT
+
+
+ ;; LATEST HIDDEN
+ DEFWORD_RAW HIDDEN, 'HIDDEN'
+ POP BX
+ XOR BYTE [BX + 2], HIDDEN_BIT
+ NEXT
+
+
+ ;; HIDE DUP
+ DEFWORD_THREADED HIDE, 'HIDE'
+ DW _WORD, FIND, HIDDEN, EXIT
+
+
+ DEFWORD_THREADED_IMMED TICK, "'"
+ DW _WORD, FIND, CFA, EXIT
+
+
+ DEFWORD_RAW BRANCH, 'BRANCH'
+ LODSW
+ ADD SI, AX
+ NEXT
+
+
+ DEFWORD_RAW ZEROBRANCH, '0BRANCH'
+ POP DX
+ LODSW
+ TEST DX, DX
+ JNZ .NOTZERO
+ ADD SI, AX
+
+.NOTZERO:
+ NEXT
+
+
+ DEFWORD_RAW LITSTRING, 'LITSTRING'
+ LODSW ; Length
+ PUSH SI
+ ADD SI, AX
+ INC SI ; Round up
+ AND SI, (~1)
+ PUSH AX
+ NEXT
+
+
+ DEFWORD_THREADED INTERPRET, 'INTERPRET'
+ DW _WORD ; ( addr len )
+ DW _2DUP, FIND ; ( addr len entry? )
+ DW DUP ; ( addr len entry? entry? )
+ DW ZEROBRANCH, 26 ; ( addr len entry? ); jump to .NUM if
+ ; the entry was not found.
+
+ DW CFA ; ( addr len cfa )
+
+ DW SWAP, DROP ; ( addr cfa )
+ DW SWAP, DROP ; ( cfa )
+ DW STATE, GET ; ( cfa 0|1 )
+
+ DW ZEROBRANCH, 4
+ DW COMMA ; Add to HERE
+ DW EXIT
+
+.WORD_IMMED:
+ DW EXECUTE ; ( )
+ DW EXIT
+
+.NUM: ; ( addr len 0 )
+ DW DROP ; ( addr len )
+ DW NUMBER ; ( number unparsed )
+ DW ZEROBRANCH, 4 ; ( number ); jump to .NUMOK
+
+ DW BRANCH, 18 ; jump to .FAILED
+
+.NUMOK:
+ ;; ( number )
+ DW STATE, GET ; ( number STATE )
+ DW ZEROBRANCH, 8 ; ( number )
+
+ DW LITERAL, LITERAL ; ( number LITERAL )
+ DW COMMA, COMMA ; ( )
+
+.NUM_IMMED: ; ( number )
+ DW EXIT
+
+.FAILED: ; ( number )
+ INCLUDE_STRING $, 'Word is neither defined nor a number'
+ DW TYPE, CR ; ( number )
+ DW DROP, EXIT ; ( )
+
+
+ DEFWORD_RAW EXECUTE, 'EXECUTE'
+ POP AX
+ MOV BX, AX
+ JMP [BX]
+
+
+ ;; TODO: await newline
+ DEFWORD_THREADED QUIT, 'QUIT'
+ DW INTERPRET
+ DW BRANCH, -6
+
+
+ DEFWORD_THREADED COLON, ':'
+ DW _WORD, CREATE
+ DW LITERAL, DOCOL, COMMA
+ DW LATEST, GET, HIDDEN
+ DW RIGHTBRACKET
+ DW EXIT
+
+
+ DEFWORD_THREADED_IMMED SEMICOLON, ';'
+ DW LITERAL, EXIT, COMMA
+ DW LATEST, GET, HIDDEN
+ DW LEFTBRACKET
+ DW EXIT
+
+
+ ;; ( *entry -- len *string )
+ DEFWORD_THREADED ENTRY_NAME, ''
+ DW DUP ; ( *entry *entry )
+ DW LITERAL, 2, PLUS ; ( *entry *len )
+ DW GETCHAR ; ( *entry len )
+ DW SWAP ; ( len *entry )
+ DW LITERAL, 3, PLUS ; ( len *string )
+ DW SWAP
+ DW EXIT
+
+
+ DEFWORD_THREADED SHOW_DICT, '.d'
+ DW LATEST, GET ; ( *entry )
+ DW DUP, ENTRY_NAME ; ( *entry len *string)
+ DW TYPE, CR ; ( *entry )
+ DW GET ; ( *prev-entry )
+ DW DUP ; ( *prev-entry *prev-entry )
+ DW ZEROBRANCH, 2
+ DW BRANCH, -24 ; Back to start!
+ DW EXIT
diff --git a/DOS.ASM b/DOS.ASM
new file mode 100644
index 0000000..646ac7a
--- /dev/null
+++ b/DOS.ASM
@@ -0,0 +1,34 @@
+ %MACRO WRITESOUT 0
+ MOV AH, 09h
+ INT 21h
+ %ENDMACRO
+
+ %MACRO FLUSH 0
+ MOV AX, 0C00h
+ INT 21h
+ %ENDMACRO
+
+ %MACRO QUIT_PROC 0
+ MOV AX, 4C00h
+ INT 21h
+ %ENDMACRO
+
+ %MACRO GETSTDINSTATUS 0
+ MOV AH, 0Bh
+ INT 21h
+ %ENDMACRO
+
+ %MACRO READCIN 0
+ MOV AH, 1
+ INT 21h
+ %ENDMACRO
+
+ %MACRO WRITECOUT 0
+ MOV AH, 02h
+ INT 21h
+ %ENDMACRO
+
+ %DEFINE WORDSZ 2
+
+ %DEFINE ASCII_RETURN 0Dh
+ %DEFINE ASCII_NEWLINE 0Ah
diff --git a/FORTH.ASM b/FORTH.ASM
new file mode 100644
index 0000000..26576b5
--- /dev/null
+++ b/FORTH.ASM
@@ -0,0 +1,288 @@
+ ;; FORTH.ASM -- Forth system for Microsoft (R) DOS
+
+ BITS 16
+
+ ;; DOS loads .COM executables here
+ ORG 100h
+
+ %INCLUDE "DOS.ASM"
+
+;;; MACROS ;;;
+
+ ;; Step indirect threaded code to next word. Call this in a raw
+ ;; word to effectively return. In an interpreted word SI first
+ ;; needs to be reset to the calling value.
+ %MACRO NEXT 0
+ LODSW
+ MOV BX, AX ; [ax] is invalid in 16 bits
+ JMP [BX]
+ %ENDMACRO
+
+
+ ;; Push register operand to return stack
+ %MACRO RSPUSH 1
+ SUB BP, WORDSZ
+ MOV [BP], %1
+ %ENDMACRO
+
+
+ ;; Pop from return stack to register operand
+ %MACRO RSPOP 1
+ MOV %1, [BP]
+ ADD BP, WORDSZ
+ %ENDMACRO
+
+
+ ;; Used for the compile-time dictionary linked list. Not used at
+ ;; runtime.
+ %DEFINE LINK 0
+
+
+ IMMEDIATE_BIT EQU 1 << 6
+ HIDDEN_BIT EQU 1 << 5
+ LENGTH_MASK EQU 0b11111
+
+
+ ;; Define a threaded word. The arguments should be the symbol for
+ ;; the word, followed by the string version. e.g.:
+ ;;
+ ;; DEFWORD DUP, 'DUP', IMMEDIATE_BIT
+ %MACRO DEFWORD 3
+ ALIGN 2
+
+WORD_%1:
+ DW LINK
+ %DEFINE LINK WORD_%1
+ DB WORDLEN_%1 | %3 ; Length | Flags
+
+NAME_%1:
+ DB %2,
+ WORDLEN_%1 EQU $ - NAME_%1
+
+ ALIGN 2
+
+%1:
+ %ENDMACRO
+
+
+ %MACRO DEFWORD_THREADED 2
+ DEFWORD %1, %2, 0
+ DW DOCOL
+ %ENDMACRO
+
+
+ %MACRO DEFWORD_THREADED_IMMED 2
+ DEFWORD %1, %2, IMMEDIATE_BIT
+ DW DOCOL
+ %ENDMACRO
+
+
+ ;; Same as DEFWORD_THREADED but this time with raw code
+ %MACRO DEFWORD_RAW 2
+ DEFWORD %1, %2, 0
+ DW INTRAW ; Raw interpreter codeword
+
+ ;; Callable from assembly
+CODE_%1:
+ %ENDMACRO
+
+
+ %MACRO DEFWORD_RAW_IMMEDIATE 2
+ DEFWORD %1, %2, IMMEDIATE_BIT
+ DW INTRAW
+
+ ;; Callable from assembly
+CODE_%1:
+ %ENDMACRO
+
+
+ ;; DEFVAR name, 'name'
+ ;; dw 0
+ %MACRO DEFVAR 2
+ DEFWORD_RAW %1, %2
+ PUSH VAR_%1
+ NEXT
+
+VAR_%1:
+ %ENDMACRO
+
+ %MACRO DEFCONST 3
+ DEFWORD_RAW %1, %2
+ PUSH CONST_%1
+ NEXT
+ CONST_%1 EQU %3
+ %ENDMACRO
+
+
+ %MACRO INCLUDE_STRING 2
+ DW LITSTRING
+ DW STRINGLEN_%1
+.BEFORE_STRING_%1:
+ DB %2
+ STRINGLEN_%1 EQU $ - .BEFORE_STRING_%1
+ ALIGN WORDSZ
+ %ENDMACRO
+
+
+ ;; TODO: This doesn't work for some reason
+ %MACRO RELATIVE_ADDRESS 1
+ DW (%1 - $)
+ %ENDMACRO
+
+
+;;; PROGRAM CODE ;;;
+
+_START:
+ ;; Progran begins
+ MOV BP, SP
+ SUB BP, 1024 ; why can't I use SP as a base for
+ ; load effective address?
+ MOV SI, INDIRECT_START
+ NEXT
+
+ ALIGN 2
+
+
+ ;; DO COLon definition -- Codeword for indirect threaded code
+ ;; ax: indirect execution address
+DOCOL:
+ RSPUSH si
+ ADD AX, WORDSZ ; Point to the first word address
+ MOV SI, AX ; Enter the function body (set si)
+ NEXT
+
+
+ ;; Interpret raw code (plain machine code)
+INTRAW:
+ ADD AX, WORDSZ
+ JMP AX
+
+
+INDIRECT_START:
+ DW SETUP
+ DW QUIT
+ DW BYE
+
+
+SETUP:
+ DW INTRAW
+
+ MOV DX, MSG
+ WRITESOUT
+
+ NEXT
+
+LITERAL:
+ DW INTRAW
+
+ LODSW ; Load the next word
+ PUSH AX
+
+ NEXT
+
+
+EXIT:
+ DW INTRAW
+ RSPOP SI
+ NEXT
+
+TEST_PRINTING:
+ DW INTRAW
+ MOV AX, 5723
+ JMP DOT_INT
+
+
+ DEFWORD_RAW BYE, 'BYE'
+ FLUSH
+ QUIT_PROC
+
+
+ DEFWORD_RAW LIT, 'LIT'
+ LODSW ; Read next word from input to AX
+ PUSH AX
+ NEXT
+
+
+ DEFWORD_RAW DROP, 'DROP'
+ ADD SP, WORDSZ
+ NEXT
+
+
+ DEFWORD_RAW SWAP, 'SWAP'
+ POP AX
+ POP BX
+ PUSH AX
+ PUSH BX
+ NEXT
+
+
+ DEFWORD_RAW DUP, 'DUP'
+ ;; This is stupid, [SP] is invalid
+ POP AX
+ PUSH AX
+ PUSH AX
+ NEXT
+
+
+ DEFWORD_RAW PLUS, '+'
+ POP AX
+ POP BX
+ ADD AX, BX
+ PUSH AX
+ NEXT
+
+
+ DEFWORD_RAW MINUS, '-' ; ( DX AX -- DX-AX )
+ POP AX
+ POP DX
+ SUB DX, AX
+ PUSH DX
+ NEXT
+
+
+ DEFWORD_RAW ADD1, '1+'
+ POP AX
+ ADD AX, 1
+ PUSH AX
+ NEXT
+
+
+ DEFWORD_RAW ADD2, '2+'
+ POP AX
+ ADD AX, 2
+ PUSH AX
+ NEXT
+
+
+ ;; This kind of sucks
+ DEFWORD_RAW _2DUP, '2DUP' ; ( a b -- a b a b )
+ POP AX
+ POP BX
+ PUSH BX
+ PUSH AX
+ PUSH BX
+ PUSH AX
+ NEXT
+
+
+ %INCLUDE "IOWORDS.ASM"
+ %INCLUDE "DICTNRY.ASM"
+
+;;; LATE-INIT VARIABLES ;;;
+ DEFVAR STATE, 'STATE'
+ DW 0 ; Interpret
+
+ DEFVAR HERE, 'HERE'
+ DW HERE_START
+
+ ;; LATEST must be the last word defined in FORTH.ASM!
+ DEFVAR LATEST, 'LATEST'
+ DW LINK
+
+
+;;; PROGRAM DATA ;;;
+ MSG DB 'DOS FORTH', 0Dh, 0Ah, '$'
+
+
+;;; FREE DATA ;;;
+HERE_START:
diff --git a/IOWORDS.ASM b/IOWORDS.ASM
new file mode 100644
index 0000000..65f9307
--- /dev/null
+++ b/IOWORDS.ASM
@@ -0,0 +1,208 @@
+;;; Assembly definitions of built-in Forth words
+;;; Assume this is included after all the relevant macros
+
+
+;;; INPUT & OUTPUT ROUTINES ;;;
+ ;; Read a key from the input. If STDIN is blank wait for a key
+ ;; press.
+ ;;
+ ;; TODO: Keep an internal buffer until RETURN is pressed, allow
+ ;; some line editing.
+ ;;
+ ;; Actually, that could be implemented in Forth for simplicity.
+ DEFWORD_RAW KEY, 'KEY'
+ CALL READ_KEY
+ PUSH AX
+ NEXT
+
+ ;; This routine returns the key in AL, but Forth wants it on the
+ ;; stack, so we have a helper function.
+READ_KEY:
+ GETSTDINSTATUS
+ TEST AL, AL
+ JZ .EOF
+
+ READCIN
+ XOR AH, AH ; We get the result in AL but we want
+ ; the whole word to be the correct
+ ; char.
+ RET
+
+.EOF: ; End of STDIN
+ ;; Check if line buffer is empty
+ READCIN
+ XOR AH, AH ; We don't care about the scan code
+ RET
+
+
+ %MACRO WHITESPACE 2
+ CMP %1, ' '
+ JE %2
+
+ CMP %1, 09h ; \t
+ JE %2
+
+ CMP %1, 0Ah ; \n
+ JE %2
+
+ CMP %1, 0Dh ; \r
+ JE %2
+ %ENDMACRO
+
+
+ ;; Read a word from the input, max 32 bytes. WORD is reserved in
+ ;; NASM sadly.
+ DEFWORD_RAW _WORD, 'WORD'
+READ_WORD:
+ MOV DI, WORD_BUFFER
+
+.START:
+ ;; First skip whitespace
+ CALL READ_KEY ; Char in AL
+
+ WHITESPACE AL, .START
+ CMP AL, '\'
+ JE .COMMENT
+
+.LOOP:
+ CMP AL, 'a'
+ JL .STORE
+ CMP AL, 'z'
+ JG .STORE
+
+ SUB AL, ('a' - 'A') ; To upper case
+
+.STORE:
+ STOSB ; Buffer char
+
+ CALL READ_KEY
+ WHITESPACE AL, .DONE
+ JMP .LOOP
+
+.COMMENT:
+ CALL READ_KEY
+ CMP AL, ASCII_RETURN
+ JNE .COMMENT
+ JE .START
+
+.DONE:
+ SUB DI, WORD_BUFFER ; Length
+ PUSH WORD_BUFFER
+ PUSH DI
+
+ NEXT
+
+
+ ;; ( string len -- num unparsed )
+ DEFWORD_RAW NUMBER, 'NUMBER'
+ POP DX ; Length
+ POP BX ; Index
+ ADD DX, BX ; End pointer
+ XOR AX, AX ; The number
+
+ XOR CX, CX ; CL - used for char
+
+.LOOP:
+ MOV CL, BYTE [BX]
+ CMP CL, '0'
+ JL .DONE
+ CMP CL, '9'
+ JG .DONE
+
+ SUB CL, '0'
+ MOV CH, 10 ; This needs to be reset each time
+ ; which is annoying
+ IMUL CH ; 8-bit IMUL operand means that the
+ ; result is just in AX, not extended
+ ; by DX. Perfect
+ XOR CH, CH
+ ADD AX, CX
+ INC BX
+ CMP BX, DX
+ JL .LOOP
+
+.DONE:
+ SUB DX, BX ; Number of chars unread
+ PUSH AX
+ PUSH DX
+ NEXT
+
+
+ ;; Emit a char from the stack
+ DEFWORD_RAW EMIT, 'EMIT'
+ POP DX
+ WRITECOUT
+ NEXT
+
+
+ DEFWORD_RAW CR, 'CR'
+ MOV DX, CRLF_MSG
+ WRITESOUT
+ NEXT
+
+
+ DEFWORD_RAW TYPE, 'TYPE'
+TYPE_STRING:
+ POP CX ; Length
+ POP BX ; Index
+ ADD CX, BX ; End pointer
+
+.LOOP:
+ MOV DL, BYTE [BX]
+ WRITECOUT
+
+ INC BX
+ CMP BX, CX
+ JNE .LOOP
+
+.DONE:
+ NEXT
+
+
+ ;; ( n -- )
+ DEFWORD_RAW DOT, '.'
+ POP AX ; The number
+
+DOT_INT:
+ TEST AX, AX
+ JNZ .START
+
+ MOV DX, '0'
+ WRITECOUT
+ NEXT
+
+.START:
+ MOV BX, 10 ; The base
+
+ ;; TODO: BUG: Depending on this value there is a maximum number
+ ;; that this routine will format, which is weird. For the value of
+ ;; 7 it is 1280.
+ MOV CX, 7
+.LOOP:
+ XOR DX, DX
+ DIV BX ; AX = quotient; DX = remainder
+ PUSH DX
+
+ LOOP .LOOP
+
+ MOV CX, 7
+ XOR BX, BX ; At start
+.REVERSE:
+ POP DX
+ OR BL, DL
+ JZ .END
+
+ ADD DL, '0'
+ WRITECOUT
+
+.END:
+ LOOP .REVERSE
+
+ NEXT
+
+
+;;; DATA ;;;
+ CRLF_MSG DB ASCII_RETURN, ASCII_NEWLINE, '$'
+
+ WORD_BUFFER TIMES 32 DB 0
+WORD_BUFFER_END:
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..b296c5d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,5 @@
+FORTH.COM: FORTH.ASM DOS.ASM IOWORDS.ASM DICTNRY.ASM
+ nasm $< -fbin -o$@
+
+run: FORTH.COM
+ dosbox $^
diff --git a/README.TXT b/README.TXT
new file mode 100644
index 0000000..8931e35
--- /dev/null
+++ b/README.TXT
@@ -0,0 +1,52 @@
+ ..............................................
+ ... ...
+ ... : DOS FORTH ; ...
+ ... ...
+ ..............................................
+
+
+This is my attempt at a FORTH implementation in 8086 Assembler for
+Microsoft DOS.
+
+
+
+ HOW TO COMPILE
+
+Run `make' on a UNIX host system with NASM installed.
+
+
+
+ HOW TO RUN
+
+Run `make run' to start DOSBox automatically, or run FORTH.COM on a
+real MS DOS machine or emulator.
+
+
+
+ DOCUMENTATION
+
+Please consult <WORDS.TXT> for a list of words and their use.
+
+
+
+ IMPLEMENTATION DETAILS
+
+The structure of a dictionary entry is shown in Fig. 1.
+
+ +--------+-------------------------+
+ |Byte |Field name |
+ +--------+-------------------------+
+ |0-1 |Link pointer |
+ +--------+-------------------------+
+ |2 |Length |
+ +--------+-------------------------+
+ |3..N |Name |
+ +--------+-------------------------+
+ |N+1.. |Body (aligned to 2 bytes)|
+ +--------+-------------------------+
+ Fig. 1: Dictionary entry
+
+
+
+
+ Copyright (C) 2021 swissChili -- http://swisschili.sh
diff --git a/WORDS.TXT b/WORDS.TXT
new file mode 100644
index 0000000..8c4d638
--- /dev/null
+++ b/WORDS.TXT
@@ -0,0 +1,257 @@
+ DOS FORTH WORD REFERENCE
+
+
+CONTENTS
+
+1. [STACK MANIPULATION]
+2. [ARITHMETIC]
+3. [DICTIONARY/MEMORY ACCESS]
+4. [STRING MANIPULATION & INPUT]
+5. [CONTROL FLOW & EXECUTION]
+6. [VARIABLES]
+
+
+
+ STACK MANIPULATION
+
+DUP ( A -- A A )
+
+2DUP ( A B -- A B A B )
+
+DROP ( A -- )
+
+SWAP ( A B -- B A )
+
+
+
+ ARITHMETIC
+
++ ( A B -- A+B )
+
+- (A B -- A-B )
+
++1 (A -- A+1 )
+
++2 (A -- A+2 )
+
+
+
+ DICTIONARY/MEMORY ACCESS
+
+FIND ( ADDRESS LENGTH -- ENTRY )
+
+ Search through the dictionary for a word with the given
+ name. Address and name together make up a string.
+
+ ENTRY = address of the matching dictionary entry if one was found,
+ 0 otherwise.
+
+
+>CFA ( BASE -- ADDRESS )
+
+ ADDRESS = the address of the codeword for the dictionary entry
+ starting at BASE.
+
+ SEE ALSO <README.TXT> Fig. 1: Dictionary entry
+
+
+>DFA ( BASE -- ADDRESS )
+
+ ADDRESS = the address of the first byte in the body of the
+ dictionary entry, i.e. the first byte after the codeword.
+
+ SEE ALSO >CFA, <README.TXT> Fig. 1: Dictionary entry
+
+
+@ ( ADDRESS -- VALUE )
+
+ VALUE = the cell at ADDRESS.
+
+
+! ( ADDRESS VALUE -- )
+
+ Sets the cell at ADDRESS to VALUE.
+
+
+C@ ( ADDRESS -- CHAR )
+
+ CHAR = the byte at ADDRESS.
+
+
+CREATE ( ADDR LENGTH -- )
+
+ Creates a new dictionary entry with the name specified by ADDR and
+ LENGTH. HERE and LATEST are updated accordingly.
+
+
+, ( VALUE -- )
+
+ Stores the cell VALUE at HERE, adds 2 to HERE (word size).
+
+ This does the same thing as
+ VALUE HERE @ ! HERE 2+ HERE !
+
+
+[ ( -- )
+
+ Switches to interpret mode. I.e. sets STATE to 0.
+
+
+] ( -- )
+
+ Switches to compile mode. I.e. sets STATE to 1.
+
+
+IMMEDIATE ( -- )
+
+ Toggles if the most recently defined word (the dictionary entry
+ pointed to by LATEST) is immediate.
+
+
+HIDDEN ( ENTRY -- )
+
+ Toggles if the dictionary entry starting at ENTRY is hidden.
+
+
+HIDE WORD ( -- )
+
+ Looks up WORD and toggles if it is hidden.
+
+ This could be defined as
+ : HIDE WORD FIND HIDDEN ;
+
+
+' WORD ( -- CFA )
+
+ Looks up WORD in the dictionary and returns its CFA. ' reads WORD
+ at runtime.
+
+
+ STRING MANIPULATION & INPUT
+
+KEY ( -- CHAR )
+
+ Reads one character from the input. KEY blocks until a key press
+ if no input data is buffered.
+
+ CHAR = the character read.
+
+
+EMIT ( CHAR -- )
+
+ Writes the lower byte of CHAR as an ASCII character to standard
+ output.
+
+
+CR ( -- )
+
+ Write a carriage return followed by a line feed ('\r\n') to
+ standard output.
+
+
+TYPE ( ADDR LENGTH -- )
+
+ Writes the string starting at ADDR with length LENGTH to standard
+ output. The string does not need to be terminated with anything
+ and may contain any byte values.
+
+
+WORD ( -- ADDR LENGTH )
+
+ Reads a word from the input. Calls KEY internally.
+
+ ADDR = the address of the internal buffer holding the word. This
+ is overridden every time WORD is called, so copy the string if you
+ need it.
+
+ LENGTH = the length in bytes of the parsed word. The buffer holds
+ at most 32 bytes.
+
+
+NUMBER ( ADDR LENGTH -- NUMBER UNPARSED )
+
+ Parses the string specified by ADDR and LENGTH as a base-10
+ integer.
+
+ NUMBER = the parsed number, or 0 if one could not be parsed.
+
+ UNPARSED = the number of bytes in the string that could not be
+ parsed. I.e. 0 if everything parsed successfully, LENGTH if
+ nothing could be parsed.
+
+
+. ( NUMBER -- )
+
+ Write NUMBER as a base-10 integer to standard output.
+
+ For example: 123 .
+ 123 ok
+
+
+LITSTRING LENGTH BYTES... ( -- ADDR LENGTH )
+
+ Compiles to a literal string. You should rarely have to use this
+ manually.
+
+ LENGTH = the length of the string in bytes. 1 cell wide.
+
+ BYTES = the actual string, padded at the end to 2 bytes (1 cell).
+
+ ADDR = the address of the string.
+
+ LENGTH = the length of the string.
+
+
+
+ CONTROL FLOW & EXECUTION
+
+BRANCH BYTES ( -- )
+
+ Branches BYTES bytes forwards or backwards, depending on the
+ sign. BRANCH 0 does nothing, BRANCH 2 skips the following word,
+ BRANCH -4 causes an infinite loop.
+
+
+0BRANCH BYTES ( PREDICATE -- )
+
+ Identical to BRANCH if PREDICATE is 0. Otherwise 0BRANCH does
+ nothing.
+
+
+EXECUTE ( CFA -- )
+
+ Transfers execution to the word defined by CFA. This can be used
+ with '.
+
+ SEE ALSO '
+
+ For example, ' DUP EXECUTE is identical to DUP
+
+
+BYE ( -- )
+
+ Quit FORTH.
+
+
+
+ VARIABLES
+
+STATE
+
+ The current interpreter state.
+
+ 0 = Interpret
+ 1 = Compile
+
+
+HERE
+
+ The address of the first free byte of the dictionary. This is
+ where newly compiled words are added.
+
+
+LATEST
+
+ The address of the dictionary for the most recently added word.
+
+ SEE ALSO <README.TXT> Fig. 1: Dictionary entry