blob: de10e86b936ac07f224197ac539440be950ba0a6 [file] [log] [blame]
;; 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. At runtime
;; LATEST is used instead
%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 2
DEFWORD_RAW %1, %2
PUSH WORD [CONST_%1]
NEXT
CONST_%1:
%ENDMACRO
%MACRO INCLUDE_STRING 1
DW LITSTRING
DW %%STRINGLEN
%%BEFORE_STRING:
DB %1
%%STRINGLEN EQU $ - %%BEFORE_STRING
ALIGN WORDSZ
%ENDMACRO
%MACRO RELATIVE_ADDRESS 1
DW (%1 - $ - 2)
%ENDMACRO
;;; PROGRAM CODE ;;;
_START:
;; Progran begins
MOV BP, SP
SUB BP, 1024
MOV WORD [CONST_SP_INITIAL], SP
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
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
DEFWORD_RAW BYE, 'BYE'
MOV DX, .BYE_MSG
WRITESOUT
FLUSH
QUIT_PROC
.BYE_MSG:
DB 'Bye!', 0Dh, 0Ah, '$'
DEFWORD_THREADED IMG_DUMPED, 'IMG-DUMPED'
INCLUDE_STRING 'Image dumped!'
DW TYPE, CR, EXIT
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 SLASHMOD, '/MOD'
POP AX
POP DX
IDIV DX
PUSH DX ; Remainder
PUSH AX ; Quotient
NEXT
DEFWORD_RAW _TIMES, '*'
POP AX
POP DX
IMUL DX
PUSH AX
NEXT
DEFWORD_RAW AND, 'AND'
POP AX
POP DX
AND AX, DX
PUSH AX
NEXT
DEFWORD_RAW XOR, 'XOR'
POP DX
POP AX
XOR AX, DX
PUSH AX
NEXT
DEFWORD_RAW NOT, 'NOT'
POP AX
NOT AX
PUSH AX
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; ( cfa )
DEFVAR HERE, 'HERE'
DW HERE_START
DEFCONST SP_INITIAL, 'S0'
DW 0
;; 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 ;;;
ALIGN 4
HERE_START: