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: