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