Initial commit
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