5313 lines
122 KiB
ArmAsm
5313 lines
122 KiB
ArmAsm
|
; %help
|
||
|
; Davex Forth interpeter.
|
||
|
;
|
||
|
; syntax: dxforth
|
||
|
;
|
||
|
; This is a Forth system implementing the Forth 2012 Core Word Set
|
||
|
; a subset of the Core Extensions word set, the Exception word set,
|
||
|
; and select words from other word sets.
|
||
|
;
|
||
|
; Additionally, words supporting Davex and ProDOS are provided.
|
||
|
;
|
||
|
; See the full documentation (not written yet) for complete information.
|
||
|
; %hend
|
||
|
|
||
|
; This is a byte-addressed, direct-threaded, 16-bit Forth. The stack
|
||
|
; can't be on the zero page due to Davex only giving us 32 bytes there.
|
||
|
; so it's a bit slower than some Forths due to that.
|
||
|
|
||
|
; PSP = X register
|
||
|
; RSP = 6502 stack
|
||
|
; IP = self-modified in fetch_IP
|
||
|
; DLAST = xczpage+0,1 - last dictionary word defined, HBYTE=0, none
|
||
|
; CHERE = xczpage+2,3 - next address to be compiled
|
||
|
; WR = xczpage+4,5 - working register
|
||
|
; XR = xczpage+6,7 - working register
|
||
|
; YR = xczpage+8,9 - working register
|
||
|
; ZR = xczpage+10,11 - working register
|
||
|
; ZSTATE = xczpage+12,13 - compilation state
|
||
|
; ZBASE = xczpage+14,15 - system number base
|
||
|
; ZACC = xczpage+16..19 - mixed-precision multiply accumulator
|
||
|
; SPTMP = xcpage+20,21 - place to save X register for operations that use it
|
||
|
; HIMEM = SPTMP+22,23 - highest available memory address+1
|
||
|
; RSSVE = xczpage+24,25 - saves the 6502 stack pointer upon system entry
|
||
|
; IHERE = xczpage+26,27 - start of user dictionary space
|
||
|
; INBUF = xczpage+28,29 - location of input buffer
|
||
|
; TRADR = xczpage+$1E - trace address (when assembled with tracing enabled)
|
||
|
|
||
|
; ***** Options & Debugging *****
|
||
|
.define TRACE 0
|
||
|
.define TRACE_WORDS 0
|
||
|
|
||
|
; ***** Firmware *****
|
||
|
COut = $fded
|
||
|
COut1 = $fdf0
|
||
|
TabV = $fb5b
|
||
|
PrByte = $fdda
|
||
|
Home = $fc58
|
||
|
VTab = $fc22
|
||
|
;KeyIn = $fd1b ; do not use for davex
|
||
|
PrntAX = $f941
|
||
|
|
||
|
; ***** Zero Page *****
|
||
|
|
||
|
CH = $24
|
||
|
CV = $25
|
||
|
|
||
|
.globalzp xczpage ; davex should do this
|
||
|
DLAST = xczpage
|
||
|
CHERE = DLAST+2
|
||
|
WR = CHERE+2 ; working register
|
||
|
XR = WR+2 ; second working reg
|
||
|
YR = XR+2 ; for saving Y register, usually
|
||
|
ZR = YR+2 ; used for searching dict
|
||
|
ZSTATE = ZR+2 ; nonzero = compiling
|
||
|
ZBASE = ZSTATE+2
|
||
|
ZACC = ZBASE+2 ; 4 bytes
|
||
|
ZACC1 = ZACC
|
||
|
ZACC2 = ZACC1+2
|
||
|
SPTMP = ZACC+4 ; for primitives to save data stack ptr for certain ops
|
||
|
HIMEM = SPTMP+2 ; maybe for memory management
|
||
|
|
||
|
RSSAV = xczpage+$18 ; for stuff we don't re-init
|
||
|
IHERE = RSSAV+2 ; start of user-defined dictionary words
|
||
|
INBUF = IHERE+2 ; location of input buffer
|
||
|
|
||
|
TRADR = xczpage+$1E
|
||
|
|
||
|
.assert (HIMEM+2)<=RSSAV,error,"fix zpage stuff!"
|
||
|
|
||
|
; ***** Constants *****
|
||
|
|
||
|
.define PSTK_SZ $80 ; size of parameter stack, some optimizations when $80
|
||
|
PSTK = $AF00 ; covered by the reserve
|
||
|
PSTKL = PSTK
|
||
|
PSTKH = PSTK+PSTK_SZ
|
||
|
|
||
|
.define PAD_SIZE 128 ; (for reference, not directly used yet)
|
||
|
; minimum required by standard is 84
|
||
|
.define WORD_SIZE 35 ; minimum required by standard is 33, but
|
||
|
; the pictured numeric output words share
|
||
|
; it and is required to be
|
||
|
; 2*(cell size in bits=1)+2 = 34 bytes, plus
|
||
|
; we want a count byte at the beginning
|
||
|
|
||
|
; compiler constants
|
||
|
.define opJSR $20 ; JSR opcode
|
||
|
.define opJMP $4C ; JMP opcode
|
||
|
.define opRTS $60 ; RTS opcode
|
||
|
.define opNOP $EA ; NOP opcode
|
||
|
|
||
|
; flags for words
|
||
|
.define F_IMMED %01000000 ; immediate
|
||
|
.define F_CONLY %00100000 ; compile-only
|
||
|
.define F_SMUDG %00010000 ; smudged, invisible in search
|
||
|
|
||
|
; Control chars
|
||
|
AFF = $0C
|
||
|
ACR = $0D
|
||
|
|
||
|
; Non-keyboard/eval source IDs (high byte)
|
||
|
.define SRC_REDIR $FE ; Input redirected
|
||
|
.define SRC_ARG $FD ; File given on command line
|
||
|
|
||
|
; Misc
|
||
|
P8_ER_RNG = $FE ; range of P8 errors for exception numbers
|
||
|
|
||
|
.macro ENTER
|
||
|
jsr enter
|
||
|
.endmacro
|
||
|
|
||
|
.macro EXIT
|
||
|
.addr exit_next
|
||
|
.endmacro
|
||
|
|
||
|
.macro CODE
|
||
|
.addr exit_code
|
||
|
.endmacro
|
||
|
|
||
|
.macro NEXT
|
||
|
jmp next
|
||
|
.endmacro
|
||
|
|
||
|
; optimization for the common case where
|
||
|
; a native word would end with jsr pushay followed by NEXT
|
||
|
.macro PUSHNEXT
|
||
|
jmp next::fast_num
|
||
|
.endmacro
|
||
|
|
||
|
.macro RUN
|
||
|
jmp next::run
|
||
|
.endmacro
|
||
|
|
||
|
.macro NLIT num
|
||
|
.if num & $FF00
|
||
|
.word LIT::xt
|
||
|
.endif
|
||
|
.byte <(num) ; can't do .word because ca65 won't allow negative
|
||
|
.byte >(num)
|
||
|
.endmacro
|
||
|
|
||
|
.macro SLIT str
|
||
|
.local target,addr
|
||
|
.addr _JUMP
|
||
|
.addr target
|
||
|
addr:
|
||
|
.byte str
|
||
|
target:
|
||
|
NLIT addr
|
||
|
NLIT .strlen(str)
|
||
|
.endmacro
|
||
|
|
||
|
.macro CSLIT str
|
||
|
.local target,addr
|
||
|
.addr _JUMP::xt
|
||
|
.addr target
|
||
|
addr:
|
||
|
.byte .strlen(str)
|
||
|
.byte str
|
||
|
target:
|
||
|
NLIT addr
|
||
|
.endmacro
|
||
|
|
||
|
; dictionary macros
|
||
|
|
||
|
; Dictionary format:
|
||
|
; Bytes Purpose
|
||
|
; 2 Link to previous
|
||
|
; 1 flags & Name length, high bit always set
|
||
|
; n Name (low ASCII)
|
||
|
; m Code field (direct threaded, CFA is eliminated)
|
||
|
; ... next entry or HERE
|
||
|
|
||
|
;
|
||
|
print_dict .set 1
|
||
|
;
|
||
|
.macro dstart
|
||
|
__dstart = 0
|
||
|
.define l_dword __dstart
|
||
|
.endmacro
|
||
|
|
||
|
; define a headerless word
|
||
|
; fname is there so that a word can be switched back and
|
||
|
; forth between a headerless and normal. Flags are irrelevant
|
||
|
; because headerless words can't be used by the user.
|
||
|
.macro hword dname,fname,flags
|
||
|
.ifnblank flags
|
||
|
.out "Warning: flags used on headerless word"
|
||
|
.endif
|
||
|
.ifdef c_dword
|
||
|
.error .sprintf("%s definition not closed",.string(c_dword))
|
||
|
.endif
|
||
|
.ifdef c_hword
|
||
|
.error .sprintf("%s definition not closed",.string(c_hword))
|
||
|
.endif
|
||
|
.if print_dict
|
||
|
.if .const(*)
|
||
|
.out .concat(fname, .sprintf(" (headerless) starts at $%x", *))
|
||
|
.endif
|
||
|
.endif
|
||
|
.define c_hword dname
|
||
|
.proc dname
|
||
|
xt:
|
||
|
.if TRACE_WORDS
|
||
|
trace fname
|
||
|
.endif
|
||
|
.endmacro
|
||
|
|
||
|
.macro dword dname,fname,flags
|
||
|
.ifdef c_dword
|
||
|
.error .sprintf("%s definition not closed",.string(c_dword))
|
||
|
.endif
|
||
|
.ifdef c_hword
|
||
|
.error .sprintf("%s definition not closed",.string(c_hword))
|
||
|
.endif
|
||
|
.if print_dict
|
||
|
.if .const(*)
|
||
|
.out .concat(fname, .sprintf(" starts at $%x", *))
|
||
|
.endif
|
||
|
.endif
|
||
|
.define c_dword dname
|
||
|
.proc dname
|
||
|
.addr l_dword
|
||
|
.ifblank flags
|
||
|
.byte $80+.strlen(fname)
|
||
|
.else
|
||
|
.byte ($80|flags)+.strlen(fname)
|
||
|
.endif
|
||
|
.byte fname
|
||
|
xt:
|
||
|
.if TRACE_WORDS
|
||
|
jsr trace_word
|
||
|
.endif
|
||
|
;.if print_dict
|
||
|
; .out .concat(fname, .sprintf(" entry at $%x", xt))
|
||
|
;.endif
|
||
|
.endmacro
|
||
|
|
||
|
.macro dwordq dname,fname,flags
|
||
|
.charmap $27,$22 ; temporarily map ' -> "
|
||
|
dword dname,fname,flags
|
||
|
.charmap $27,$27 ; undo mapping
|
||
|
.endmacro
|
||
|
|
||
|
.macro dchain dname
|
||
|
.ifdef l_dword
|
||
|
.undefine l_dword
|
||
|
.endif
|
||
|
.define l_dword dname
|
||
|
.endmacro
|
||
|
|
||
|
.macro eword
|
||
|
.endproc
|
||
|
.ifdef c_dword
|
||
|
dchain c_dword
|
||
|
.undefine c_dword
|
||
|
.endif
|
||
|
.ifdef c_hword
|
||
|
.undefine c_hword
|
||
|
.endif
|
||
|
.endmacro
|
||
|
|
||
|
.macro dconst dname,fname,value,flags
|
||
|
dword dname,fname,flags
|
||
|
ldy #<value
|
||
|
lda #>value
|
||
|
PUSHNEXT
|
||
|
eword
|
||
|
.endmacro
|
||
|
|
||
|
.macro hconst dname,fname,value,flags
|
||
|
hword dname,fname,flags
|
||
|
ldy #<(value)
|
||
|
lda #>(value)
|
||
|
PUSHNEXT
|
||
|
eword
|
||
|
.endmacro
|
||
|
|
||
|
.macro dvar dname,fname,value,flags
|
||
|
dword dname,fname,flags
|
||
|
jsr pushda
|
||
|
val: .word value
|
||
|
eword
|
||
|
.endmacro
|
||
|
|
||
|
.macro hvar dname,fname,value,flags
|
||
|
hword dname,fname,flags
|
||
|
jsr pushda
|
||
|
val: .word value
|
||
|
eword
|
||
|
.endmacro
|
||
|
|
||
|
.macro dvalue dname,fname,value,flags
|
||
|
dword dname,fname,flags
|
||
|
jsr pushconst
|
||
|
val: .word value
|
||
|
eword
|
||
|
.endmacro
|
||
|
|
||
|
.macro hvalue dname,fname,value,flags
|
||
|
hword dname,fname,flags
|
||
|
jsr pushconst
|
||
|
val: .word value
|
||
|
eword
|
||
|
.endmacro
|
||
|
|
||
|
|
||
|
.macro dend
|
||
|
ELAST = l_dword
|
||
|
.endmacro
|
||
|
|
||
|
.macro trace name
|
||
|
.if TRACE_WORDS
|
||
|
jsr tr_save_regs
|
||
|
jsr xmess
|
||
|
.byte '{',name,'}',$00
|
||
|
jsr tr_rest_regs
|
||
|
.endif
|
||
|
.endmacro
|
||
|
|
||
|
.p02
|
||
|
.include "davex-mg.inc"
|
||
|
|
||
|
;DX_start dx_mg_auto_origin,$100 ; load address & top reserve
|
||
|
DX_start $8E00,$100 ; load address & top reserve for stack
|
||
|
version: DX_info $01,$12,dx_cc_any,$00
|
||
|
DX_ptab
|
||
|
DX_parm 0,t_path
|
||
|
DX_end_ptab
|
||
|
DX_desc "Forth interpreter."
|
||
|
DX_main
|
||
|
; first init the immutables
|
||
|
tsx
|
||
|
stx RSSAV ; save 6502 stack ptr
|
||
|
ldx #mli_read
|
||
|
jsr xmmgr ; get # free pages
|
||
|
cmp #$03 ; need at least 3 pages...
|
||
|
bcc nomem
|
||
|
tax ; work around mmgr bug
|
||
|
dex
|
||
|
txa
|
||
|
ldx #mli_open
|
||
|
jsr xmmgr
|
||
|
bcs nomem
|
||
|
sta INBUF+1
|
||
|
sta IHERE+1
|
||
|
inc IHERE+1 ; make room for input buffer
|
||
|
lda #$00
|
||
|
sta SOURCE_ID
|
||
|
sta SOURCE_ID+1
|
||
|
jsr xgetparm_n ; see if file given
|
||
|
sta WR+1
|
||
|
sty WR
|
||
|
ldy #$00
|
||
|
lda (WR),y
|
||
|
beq :+
|
||
|
lda #SRC_ARG
|
||
|
sta SOURCE_ID+1 ; flag initial source ID
|
||
|
: ldx #$00 ; init stack ptr
|
||
|
stx INBUF
|
||
|
stx IHERE
|
||
|
jmp _cold ; now cold-start the interpreter
|
||
|
nomem: jsr xmess
|
||
|
.byte "Not enough memory!",$0D,$00
|
||
|
jmp xerr
|
||
|
|
||
|
.if TRACE
|
||
|
.proc _dtrace
|
||
|
txa
|
||
|
pha
|
||
|
lda TRADR+1
|
||
|
jsr PrByte
|
||
|
lda TRADR
|
||
|
jsr PrByte
|
||
|
pla
|
||
|
tax
|
||
|
rts
|
||
|
.endproc
|
||
|
.endif
|
||
|
|
||
|
.if TRACE_WORDS
|
||
|
.proc trace_word
|
||
|
jsr tr_save_regs
|
||
|
tsx
|
||
|
pla
|
||
|
tay
|
||
|
pla
|
||
|
txs
|
||
|
sta TRADR+1
|
||
|
sty TRADR
|
||
|
; TRADR now points at the last byte of the JSR
|
||
|
jsr dec_tradr ; to middle byte
|
||
|
jsr dec_tradr ; to first byte
|
||
|
: jsr dec_tradr ; last byte of name, or flags+len if anon
|
||
|
bpl :-
|
||
|
and #$0F
|
||
|
beq anon ; anonymous
|
||
|
sta check
|
||
|
lda #'{'
|
||
|
jsr _emit
|
||
|
ldy #$00
|
||
|
: iny
|
||
|
lda (TRADR),y
|
||
|
cmp #' '
|
||
|
bcc nono
|
||
|
jsr _emit
|
||
|
nono: cpy #$00 ; self-modified
|
||
|
check = * - 1
|
||
|
bcc :-
|
||
|
lda #'}'
|
||
|
jsr _emit
|
||
|
jsr tr_rest_regs
|
||
|
rts
|
||
|
anon: jsr xmess
|
||
|
.byte "{noname}",$00
|
||
|
; fall-through to tr_rest_regs
|
||
|
.endproc
|
||
|
|
||
|
.proc tr_rest_regs
|
||
|
lda #$00
|
||
|
psave = * - 1
|
||
|
pha
|
||
|
lda #$00
|
||
|
asave = * - 1
|
||
|
ldx #$00
|
||
|
xsave = * - 1
|
||
|
ldy #$00
|
||
|
ysave = * - 1
|
||
|
plp
|
||
|
rts
|
||
|
.endproc
|
||
|
|
||
|
.proc tr_save_regs
|
||
|
php
|
||
|
sta tr_rest_regs::asave
|
||
|
stx tr_rest_regs::xsave
|
||
|
sty tr_rest_regs::ysave
|
||
|
pla
|
||
|
sta tr_rest_regs::psave
|
||
|
rts
|
||
|
.endproc
|
||
|
|
||
|
.proc dec_tradr
|
||
|
lda TRADR
|
||
|
bne :+
|
||
|
dec TRADR+1
|
||
|
: dec TRADR
|
||
|
ldy #$00
|
||
|
lda (TRADR),y
|
||
|
rts
|
||
|
.endproc
|
||
|
.endif
|
||
|
|
||
|
; inner interpreter entry
|
||
|
; pops caller from 6502 stack and initializes IP with it
|
||
|
; saving previous IP on 6502 stack.
|
||
|
.proc enter
|
||
|
.if TRACE
|
||
|
txa
|
||
|
pha
|
||
|
jsr xmess
|
||
|
.byte $8D,"[ENTER]",$00
|
||
|
jsr xcheck_wait
|
||
|
pla
|
||
|
tax
|
||
|
.endif
|
||
|
lda IP
|
||
|
sta WR
|
||
|
lda IP+1
|
||
|
sta WR+1
|
||
|
pla
|
||
|
sta IP
|
||
|
pla
|
||
|
sta IP+1
|
||
|
lda WR+1
|
||
|
pha
|
||
|
lda WR
|
||
|
pha
|
||
|
; fall-through
|
||
|
.endproc
|
||
|
|
||
|
; fetch and execute next instruction
|
||
|
.proc next
|
||
|
jsr fetch_IP ; fetch low byte
|
||
|
tay
|
||
|
jsr fetch_IP
|
||
|
.if TRACE
|
||
|
beq fast_num1
|
||
|
.else
|
||
|
beq fast_num
|
||
|
.endif
|
||
|
run: sta target+1
|
||
|
sty target
|
||
|
.if TRACE
|
||
|
sta TRADR+1
|
||
|
sty TRADR
|
||
|
txa
|
||
|
pha
|
||
|
lda #'>'
|
||
|
jsr _emit
|
||
|
jsr _dtrace
|
||
|
pla
|
||
|
tax
|
||
|
.endif
|
||
|
jmp * ; self-modified, dispatch xt
|
||
|
target = * - 2
|
||
|
.if TRACE
|
||
|
fast_num1:
|
||
|
sta TRADR+1
|
||
|
sty TRADR
|
||
|
txa
|
||
|
pha
|
||
|
lda #'^'
|
||
|
jsr _emit
|
||
|
pla
|
||
|
tax
|
||
|
lda TRADR+1
|
||
|
ldy TRADR
|
||
|
.endif
|
||
|
fast_num:
|
||
|
jsr pushay ; throw on stack
|
||
|
jmp next ; and immediately do next insn
|
||
|
.endproc
|
||
|
|
||
|
.proc fetch_IP
|
||
|
inc IP
|
||
|
bne :+
|
||
|
inc IP+1
|
||
|
:
|
||
|
.if TRACE
|
||
|
lda IP
|
||
|
sta TRADR
|
||
|
lda IP+1
|
||
|
sta TRADR+1
|
||
|
txa
|
||
|
pha
|
||
|
lda #':'
|
||
|
jsr _emit
|
||
|
jsr _dtrace
|
||
|
pla
|
||
|
tax
|
||
|
.endif
|
||
|
lda * ; self-modified
|
||
|
IP = * - 2
|
||
|
rts
|
||
|
.endproc
|
||
|
IP = fetch_IP::IP
|
||
|
|
||
|
; exit thread. restore previous IP
|
||
|
; and resume execution at Forth IP
|
||
|
.proc exit_next
|
||
|
.if TRACE
|
||
|
txa
|
||
|
pha
|
||
|
jsr xmess
|
||
|
.byte "[EXIT]",$8D,$00
|
||
|
jsr xcheck_wait
|
||
|
pla
|
||
|
tax
|
||
|
.endif
|
||
|
pla ; and restore IP of caller's caller
|
||
|
sta IP
|
||
|
pla
|
||
|
sta IP+1
|
||
|
NEXT
|
||
|
.endproc
|
||
|
|
||
|
; exit thread, restore previous IP
|
||
|
; and resume 6502 after last executed IP
|
||
|
.proc exit_code
|
||
|
ldy IP ; save IP
|
||
|
sty YR
|
||
|
lda IP+1
|
||
|
sta YR+1
|
||
|
.if TRACE
|
||
|
sta TRADR+1
|
||
|
sty TRADR
|
||
|
txa
|
||
|
pha
|
||
|
jsr xmess
|
||
|
.byte ">CODE:",$00
|
||
|
jsr _dtrace
|
||
|
pla
|
||
|
tax
|
||
|
.endif
|
||
|
pla ; restore previous IP
|
||
|
sta IP
|
||
|
pla
|
||
|
sta IP+1
|
||
|
lda YR+1 ; old IP on 6502 stack
|
||
|
pha
|
||
|
lda YR
|
||
|
pha
|
||
|
rts ; rts resumes execution at IP+1
|
||
|
.endproc
|
||
|
|
||
|
; ***** stack primitives *****
|
||
|
|
||
|
.proc peekay
|
||
|
jsr popay
|
||
|
inx
|
||
|
rts
|
||
|
.endproc
|
||
|
|
||
|
; flags reflect the high byte of the popped word
|
||
|
.proc popay
|
||
|
dex
|
||
|
bmi stku_err
|
||
|
ldy PSTKL,x
|
||
|
lda PSTKH,x
|
||
|
.if TRACE
|
||
|
sta TRADR+1
|
||
|
sty TRADR
|
||
|
txa
|
||
|
pha
|
||
|
lda #'/'
|
||
|
jsr _emit
|
||
|
jsr _dtrace
|
||
|
pla
|
||
|
tax
|
||
|
ldy PSTKL,x
|
||
|
lda PSTKH,x
|
||
|
.endif
|
||
|
rts
|
||
|
.endproc
|
||
|
|
||
|
.proc popwr
|
||
|
jsr popay
|
||
|
sta WR+1
|
||
|
sty WR
|
||
|
rts
|
||
|
.endproc
|
||
|
|
||
|
.proc popxr
|
||
|
jsr popay
|
||
|
sta XR+1
|
||
|
sty XR
|
||
|
rts
|
||
|
.endproc
|
||
|
|
||
|
; stack pop routines for davex routines
|
||
|
; ( d -- ) -> A(high) XY(low) = d truncated to 24 bits
|
||
|
; X will be saved in SPTMP
|
||
|
.proc popaxy
|
||
|
jsr popay ; high cell
|
||
|
sty YR+1 ; to by A later
|
||
|
jsr popay ; low cell
|
||
|
stx SPTMP
|
||
|
tax
|
||
|
lda YR+1
|
||
|
rts
|
||
|
.endproc
|
||
|
|
||
|
.proc stku_err
|
||
|
ldx #$00
|
||
|
ldy #<-4
|
||
|
lda #>-4
|
||
|
jmp _throway
|
||
|
.endproc
|
||
|
|
||
|
.proc stko_err
|
||
|
ldx #PSTK_SZ-8 ; leave enough room for ops
|
||
|
ldy #<-3
|
||
|
lda #>-3
|
||
|
jmp _throway
|
||
|
.endproc
|
||
|
|
||
|
; push AY onto stack, preserves contents of AY
|
||
|
.proc pushay
|
||
|
.if TRACE
|
||
|
sta TRADR+1
|
||
|
sty TRADR
|
||
|
.endif
|
||
|
pha
|
||
|
sta PSTKH,x
|
||
|
tya
|
||
|
sta PSTKL,x
|
||
|
pla
|
||
|
inx
|
||
|
.if PSTK_SZ=$80
|
||
|
bmi stko_err
|
||
|
.else
|
||
|
cpx #PSTK_SZ
|
||
|
bcs stko_err
|
||
|
.endif
|
||
|
.if TRACE
|
||
|
pha
|
||
|
lda #'#'
|
||
|
jsr _emit ; these must preserve regs 'cept A
|
||
|
jsr _dtrace
|
||
|
pla
|
||
|
.endif
|
||
|
rts
|
||
|
.endproc
|
||
|
|
||
|
; preserves AY
|
||
|
.proc pusha
|
||
|
.if TRACE
|
||
|
sta TRADR
|
||
|
.endif
|
||
|
pha
|
||
|
sta PSTKL,x
|
||
|
lda #$00
|
||
|
sta PSTKH,x
|
||
|
pla
|
||
|
inx
|
||
|
.if PSTK_SZ=$80
|
||
|
bmi stko_err
|
||
|
.else
|
||
|
cpx #PSTK_SZ
|
||
|
bcs stko_err
|
||
|
.endif
|
||
|
.if TRACE
|
||
|
pha
|
||
|
lda #$00
|
||
|
sta TRADR+1
|
||
|
lda #'#'
|
||
|
jsr _emit
|
||
|
jsr _dtrace
|
||
|
pla
|
||
|
.endif
|
||
|
rts
|
||
|
.endproc
|
||
|
|
||
|
; ***** Interpretation Helpers *****
|
||
|
|
||
|
; push word data address
|
||
|
; this is the default routine used by CREATE
|
||
|
; call via JSR, pops return stack entry, pushes data addr onto stack, and
|
||
|
; exits via next
|
||
|
.proc pushda
|
||
|
pla ; get low byte
|
||
|
clc
|
||
|
adc #$01
|
||
|
tay
|
||
|
pla
|
||
|
adc #$00 ; in case page crossed
|
||
|
PUSHNEXT
|
||
|
.endproc
|
||
|
|
||
|
; push constant
|
||
|
; pushes the word following the JSR onto the stack
|
||
|
; and exits via next, this is also used by VALUE
|
||
|
.proc pushconst
|
||
|
pla ; low byte
|
||
|
clc
|
||
|
adc #$01 ; account for RTS PC-1
|
||
|
sta WR
|
||
|
pla
|
||
|
adc #$00
|
||
|
sta WR+1
|
||
|
ldy #$01
|
||
|
lda (WR),y
|
||
|
pha
|
||
|
dey
|
||
|
lda (WR),y
|
||
|
tay
|
||
|
pla
|
||
|
PUSHNEXT
|
||
|
.endproc
|
||
|
|
||
|
; ***** Compilation Helpers *****
|
||
|
|
||
|
.proc cworday
|
||
|
pha
|
||
|
tya
|
||
|
ldy #$00
|
||
|
sta (CHERE),y
|
||
|
iny
|
||
|
pla
|
||
|
sta (CHERE),y
|
||
|
jsr inchere
|
||
|
; fall-through
|
||
|
.endproc
|
||
|
|
||
|
.proc inchere
|
||
|
inc CHERE
|
||
|
bne :+
|
||
|
inc CHERE+1
|
||
|
: rts
|
||
|
.endproc
|
||
|
|
||
|
.proc cbytea
|
||
|
ldy #$00
|
||
|
sta (CHERE),y
|
||
|
jmp inchere
|
||
|
.endproc
|
||
|
|
||
|
; ***** Math Library *****
|
||
|
; save X before calling any of these
|
||
|
; use YR and ZR for the operands, ZACC for the results
|
||
|
|
||
|
; ZACC(32)=ZR(16)*YR(16)
|
||
|
; adapted from https://www.llx.com/~nparker/a2/mult.html
|
||
|
.proc _umult
|
||
|
lda #0
|
||
|
sta ZACC+2
|
||
|
ldx #16
|
||
|
l1: lsr YR+1
|
||
|
ror YR
|
||
|
bcc l2
|
||
|
tay
|
||
|
clc
|
||
|
lda ZR
|
||
|
adc ZACC+2
|
||
|
sta ZACC+2
|
||
|
tya
|
||
|
adc ZR+1
|
||
|
l2: ror
|
||
|
ror ZACC+2
|
||
|
ror ZACC+1
|
||
|
ror ZACC
|
||
|
dex
|
||
|
bne l1
|
||
|
sta ZACC+3
|
||
|
rts
|
||
|
.endproc
|
||
|
|
||
|
; ZR rem ZACC1=ZR/YR
|
||
|
; ibid.
|
||
|
.proc _udiv
|
||
|
lda #0
|
||
|
sta ZACC1
|
||
|
sta ZACC1+1
|
||
|
ldx #16
|
||
|
l1: asl ZR
|
||
|
rol ZR+1
|
||
|
rol ZACC1
|
||
|
rol ZACC1+1
|
||
|
lda ZACC1
|
||
|
sec
|
||
|
sbc YR
|
||
|
tay
|
||
|
lda ZACC1+1
|
||
|
sbc YR+1
|
||
|
bcc l2
|
||
|
sta ZACC1+1
|
||
|
sty ZACC1
|
||
|
inc ZR
|
||
|
l2: dex
|
||
|
bne l1
|
||
|
rts
|
||
|
.endproc
|
||
|
|
||
|
; ZACC(16) rem ZR(16)=ZR(32)/YR(16)
|
||
|
; adapted from Garth Wilson's routines
|
||
|
; N=0:YR 1:YR+1 2:ZR 3:ZR+1 4:ZACC 5:ZACC+1 6:ZACC+2 7:ZACC+3
|
||
|
.proc _umdiv
|
||
|
sec
|
||
|
lda ZR
|
||
|
sbc YR
|
||
|
lda ZR+1
|
||
|
sbc YR+1
|
||
|
bcc :+ ; no overflow
|
||
|
ldy #<-11
|
||
|
lda #>-11
|
||
|
jmp _throway ; result out of range
|
||
|
: ldx #$11
|
||
|
loop: rol ZACC
|
||
|
rol ZACC+1
|
||
|
dex
|
||
|
bne :+
|
||
|
rts
|
||
|
: rol ZR
|
||
|
rol ZR+1
|
||
|
lda #$00
|
||
|
sta ZACC+3 ; carry
|
||
|
rol ZACC+3
|
||
|
sec
|
||
|
lda ZR
|
||
|
sbc YR
|
||
|
sta ZACC+2
|
||
|
lda ZR+1
|
||
|
sbc YR+1
|
||
|
tay
|
||
|
lda ZACC+3
|
||
|
sbc #$0
|
||
|
bcc loop
|
||
|
lda ZACC+2
|
||
|
sta ZR
|
||
|
sty ZR+1
|
||
|
bcs loop
|
||
|
.endproc
|
||
|
|
||
|
; ***** DICTIONARY *****
|
||
|
|
||
|
dstart
|
||
|
|
||
|
; push a compiled literal at IP on the stack
|
||
|
; headerless native
|
||
|
hword LIT,"LIT"
|
||
|
jsr fetch_IP
|
||
|
tay
|
||
|
jsr fetch_IP
|
||
|
PUSHNEXT
|
||
|
eword
|
||
|
|
||
|
; directly compile a cell literal from IP to (HERE)
|
||
|
hword COMP_LIT,"COMP_LIT"
|
||
|
jsr fetch_IP
|
||
|
tay
|
||
|
jsr fetch_IP
|
||
|
jsr cworday
|
||
|
NEXT
|
||
|
eword
|
||
|
|
||
|
; directly compile a char literal from IP to (HERE)
|
||
|
hword COMP_CLIT,"COMP_CLIT"
|
||
|
jsr fetch_IP
|
||
|
jsr cbytea
|
||
|
NEXT
|
||
|
eword
|
||
|
|
||
|
; Programming-Tools 15.6.2.0830
|
||
|
; quit intepreter ( -- )
|
||
|
dword BYE,"BYE"
|
||
|
tya
|
||
|
ldx RSSAV
|
||
|
txs
|
||
|
tay
|
||
|
lda #mli_close
|
||
|
jsr xmmgr ; free all mem
|
||
|
rts
|
||
|
eword
|
||
|
|
||
|
; backing value for SOURCE-ID
|
||
|
hvar dSOURCEID,"$SOURCE-ID",0
|
||
|
SOURCE_ID = dSOURCEID::val
|
||
|
|
||
|
; backing values for string buffers
|
||
|
hconst SBUF1,"SBUF1",filebuff3
|
||
|
hconst SBUF2,"SBUF2",filebuff3+256
|
||
|
hvar CSBUF,"CSBUF",filebuff3
|
||
|
|
||
|
; non-standard
|
||
|
; coldstart interpreter ( * -- )
|
||
|
; resets to built-in dictionary, clear stack, etc.
|
||
|
dword COLD,"COLD"
|
||
|
jmp _cold
|
||
|
eword
|
||
|
|
||
|
; Core 6.1.2250
|
||
|
; really a variable, but address is constant
|
||
|
dconst STATE,"STATE",ZSTATE
|
||
|
|
||
|
; non-standard
|
||
|
hconst DMEMTOP,"$MEMTOP",X_DX_LOAD
|
||
|
|
||
|
; non-standard
|
||
|
; really a variable, but address is constant
|
||
|
hconst DHIMEM,"$HIMEM",HIMEM
|
||
|
|
||
|
.proc _emit
|
||
|
ora #$80
|
||
|
jmp COut ; it must preserve al registers
|
||
|
.endproc
|
||
|
|
||
|
; Core 6.1.1320
|
||
|
dword EMIT,"EMIT"
|
||
|
jsr popay
|
||
|
tya
|
||
|
jsr _emit
|
||
|
NEXT
|
||
|
eword
|
||
|
|
||
|
; ( c-addr u -- )
|
||
|
; Consume c-addr and u, applying routine at (ZR), inited from AY,
|
||
|
; to every char of the string.
|
||
|
; When (ZR) is called, next char is in A. (ZR) may trash
|
||
|
; any registers except X, and must not touch WR and XR
|
||
|
; when it's called, Y=0 and XR=address of string
|
||
|
.proc string_op_ay
|
||
|
sta ZR+1
|
||
|
sty ZR
|
||
|
op: jsr popwr ; length into WR
|
||
|
jsr popxr ; address into XR
|
||
|
lda WR ; now calculate ending pos into WR
|
||
|
clc
|
||
|
adc XR
|
||
|
sta WR
|
||
|
lda WR+1
|
||
|
adc XR+1
|
||
|
sta WR+1
|
||
|
lp: lda WR
|
||
|
cmp XR
|
||
|
bne :+
|
||
|
lda WR+1
|
||
|
cmp XR+1
|
||
|
bne :+
|
||
|
rts
|
||
|
: ldy #$00 ; here in case (XR) trashes it
|
||
|
lda (XR),y
|
||
|
jsr docall
|
||
|
inc XR
|
||
|
bne :+
|
||
|
inc XR+1
|
||
|
: jmp lp
|
||
|
docall: jmp (ZR)
|
||
|
.endproc
|
||
|
string_op = string_op_ay::op ; user sets ZR instead
|
||
|
|
||
|
; Core 6.1.2310
|
||
|
dword TYPE,"TYPE"
|
||
|
ldy #<_emit
|
||
|
lda #>_emit
|
||
|
jsr string_op_ay
|
||
|
NEXT
|
||
|
eword
|
||
|
|
||
|
; Core 6.1.1370
|
||
|
; ( xt -- * )
|
||
|
dword EXECUTE,"EXECUTE"
|
||
|
jsr popay
|
||
|
RUN
|
||
|
eword
|
||
|
|
||
|
; headlerless word to implement branches
|
||
|
hword _JUMP,"_JUMP"
|
||
|
jump2: jsr fetch_IP
|
||
|
tay
|
||
|
jsr fetch_IP
|
||
|
; we need to be at one less than the given target
|
||
|
cpy #$00
|
||
|
bne :+
|
||
|
sec
|
||
|
sbc #$01
|
||
|
: dey
|
||
|
go: sta IP+1
|
||
|
sty IP
|
||
|
NEXT
|
||
|
eword
|
||
|
|
||
|
|
||
|
; headlerless word to implement control flow
|
||
|
hword _SKIP,"_SKIP"
|
||
|
skip2: jsr fetch_IP
|
||
|
jsr fetch_IP
|
||
|
NEXT
|
||
|
eword
|
||
|
|
||
|
; headerless word to implement state-smartness
|
||
|
; if interpreting, jumps, if compiling, skips
|
||
|
hword _SMART,"_SMART"
|
||
|
lda ZSTATE
|
||
|
ora ZSTATE+1
|
||
|
beq _JUMP::xt
|
||
|
bne _SKIP::xt
|
||
|
eword
|
||
|
|
||
|
; headlerless word to implement control flow
|
||
|
hword _SKIP2,"_SKIP2"
|
||
|
jsr fetch_IP
|
||
|
jsr fetch_IP
|
||
|
jmp _SKIP::skip2
|
||
|
eword
|
||
|
|
||
|
.if 0 ; may not need this
|
||
|
; headlerless word to implement control flow
|
||
|
hword _SKIPJUMP,"SKIPJUMP"
|
||
|
jsr fetch_IP
|
||
|
jsr fetch_IP
|
||
|
jmp _JUMP::jump2
|
||
|
eword
|
||
|
.endif
|
||
|
|
||
|
; Core 6.1.0150
|
||
|
; ( n -- ) compile word into dictionary
|
||
|
dword COMMA,","
|
||
|
jsr popay
|
||
|
jsr cworday
|
||
|
NEXT
|
||
|
eword
|
||
|
|
||
|
; Core 6.1.0860
|
||
|
; ( c -- ) compile char into dictionary
|
||
|
dword CCOMMA,"C,"
|
||
|
jsr popay
|
||
|
tya
|
||
|
jsr cbytea
|
||
|
NEXT
|
||
|
eword
|
||
|
|
||
|
; helper
|
||
|
.proc wrplus2
|
||
|
lda WR
|
||
|
clc
|
||
|
adc #$02
|
||
|
sta WR
|
||
|
lda WR+1
|
||
|
adc #$00
|
||
|
sta WR+1
|
||
|
rts
|
||
|
.endproc
|
||
|
|
||
|
; Core 6.1.0650
|
||
|
; ( adr -- n ) get number n from adr
|
||
|
dword FETCH,"@"
|
||
|
jsr popwr
|
||
|
fetch2: jsr fetchay
|
||
|
PUSHNEXT
|
||
|
fetchay: ldy #$01 ; need to re-use
|
||
|
lda (WR),y
|
||
|
pha
|
||
|
dey
|
||
|
lda (WR),y
|
||
|
tay
|
||
|
pla
|
||
|
rts
|
||
|
eword
|
||
|
|
||
|
; Core 6.1.0350
|
||
|
; ( addr -- x1 ) - store x2,x1 at addr,addr+cell
|
||
|
dword TWOFETCH,"2@"
|
||
|
jsr popwr
|
||
|
jsr FETCH::fetchay
|
||
|
jsr pushay
|
||
|
jsr wrplus2
|
||
|
jmp FETCH::fetch2
|
||
|
eword
|
||
|
|
||
|
; Core 6.1.0870
|
||
|
; ( adr - c ) get char c from adr
|
||
|
dword CFETCH,"C@"
|
||
|
jsr popwr
|
||
|
ldy #$00
|
||
|
lda (WR),y
|
||
|
jsr pusha
|
||
|
NEXT
|
||
|
eword
|
||
|
|
||
|
; Core 6.1.0010
|
||
|
; ( x addr ) - store n at addr
|
||
|
dword STORE,"!"
|
||
|
jsr popwr ; pop addr into WR
|
||
|
store2: jsr popay ; pop n
|
||
|
jsr storeay ; need to re-use
|
||
|
NEXT
|
||
|
storeay: pha ; save high byte of n
|
||
|
tya ; store low byte first
|
||
|
ldy #$00
|
||
|
sta (WR),y
|
||
|
pla ; get high byte back
|
||
|
iny
|
||
|
sta (WR),y
|
||
|
rts
|
||
|
eword
|
||
|
|
||
|
; Core 6.1.0310
|
||
|
; ( x1 x2 addr ) - store x2,x1 at addr,addr+cell
|
||
|
dword TWOSTORE,"2!"
|
||
|
jsr popwr
|
||
|
jsr popay
|
||
|
jsr STORE::storeay
|
||
|
jsr wrplus2
|
||
|
jmp STORE::store2
|
||
|
eword
|
||
|
|
||
|
; Core 6.1.0010
|
||
|
; ( c adr ) - store char c at addr
|
||
|
dword CSTORE,"C!"
|
||
|
jsr popwr
|
||
|
jsr popay
|
||
|
tya
|
||
|
ldy #$00
|
||
|
sta (WR),y
|
||
|
NEXT
|
||
|
eword
|
||
|
|
||
|
; Core 6.1.1290
|
||
|
dword DUP,"DUP"
|
||
|
jsr peekay
|
||
|
PUSHNEXT
|
||
|
eword
|
||
|
|
||
|
; Core 6.1.0630
|
||
|
dword QDUP,"?DUP"
|
||
|
jsr peekay
|
||
|
cmp #$00
|
||
|
bne :+
|
||
|
cpy #$00
|
||
|
bne :+
|
||
|
NEXT
|
||
|
: PUSHNEXT
|
||
|
eword
|
||
|
|
||
|
; Core 6.1.0580
|
||
|
dword PtoR,">R"
|
||
|
jsr popay
|
||
|
pha
|
||
|
tya
|
||
|
pha
|
||
|
NEXT
|
||
|
eword
|
||
|
|
||
|
; Core ext 6.2.0340
|
||
|
; Must be primitive
|
||
|
dword TWOPtoR,"2>R"
|
||
|
jsr _swap
|
||
|
jsr popay
|
||
|
pha
|
||
|
tya
|
||
|
pha
|
||
|
jmp PtoR::xt
|
||
|
eword
|
||
|
|
||
|
; Non-standard helper
|
||
|
; ( x1 .. xn n -- n | r: -- xn .. x1 )
|
||
|
; copy x1-xn to return stack, leave n on param stack, n <= 255
|
||
|
; must be primitive, note not in the same order as TWOPtoR
|
||
|
hword NPtoR,"N>R"
|
||
|
jsr popay ; get n
|
||
|
sty YR
|
||
|
sty YR+1 ; save n
|
||
|
cpy #$00 ; just in case
|
||
|
beq done
|
||
|
: jsr popay
|
||
|
pha
|
||
|
tya
|
||
|
pha
|
||
|
dec YR
|
||
|
bne :-
|
||
|
done: lda #$00
|
||
|
ldy YR+1
|
||
|
PUSHNEXT
|
||
|
eword
|
||
|
|
||
|
|
||
|
; Core 6.1.2060
|
||
|
dword RtoP,"R>"
|
||
|
pla
|
||
|
tay
|
||
|
pla
|
||
|
PUSHNEXT
|
||
|
eword
|
||
|
|
||
|
; Core ext 6.2.0410
|
||
|
; must be a primitive
|
||
|
dword TWORtoP," |