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