mirror of
https://github.com/hsoft/collapseos.git
synced 2024-09-07 13:48:45 +10:00
812 lines
11 KiB
NASM
812 lines
11 KiB
NASM
; A dictionary entry has this structure:
|
|
; - 7b name (zero-padded)
|
|
; - 2b prev pointer
|
|
; - 1b flags (bit 0: IMMEDIATE. bit 1: UNWORD)
|
|
; - 2b code pointer
|
|
; - Parameter field (PF)
|
|
;
|
|
; The code pointer point to "word routines". These routines expect to be called
|
|
; with IY pointing to the PF. They themselves are expected to end by jumping
|
|
; to the address at (IP). They will usually do so with "jp next".
|
|
;
|
|
; That's for "regular" words (words that are part of the dict chain). There are
|
|
; also "special words", for example NUMBER, LIT, FBR, that have a slightly
|
|
; different structure. They're also a pointer to an executable, but as for the
|
|
; other fields, the only one they have is the "flags" field.
|
|
|
|
; This routine is jumped to at the end of every word. In it, we jump to current
|
|
; IP, but we also take care of increasing it my 2 before jumping
|
|
next:
|
|
; Before we continue: are stacks within bounds?
|
|
call chkPSRS
|
|
ld de, (IP)
|
|
ld h, d
|
|
ld l, e
|
|
inc de \ inc de
|
|
ld (IP), de
|
|
; HL is an atom list pointer. We need to go into it to have a wordref
|
|
ld e, (hl)
|
|
inc hl
|
|
ld d, (hl)
|
|
push de
|
|
jp EXECUTE+2
|
|
|
|
|
|
; Execute a word containing native code at its PF address (PFA)
|
|
nativeWord:
|
|
jp (iy)
|
|
|
|
; Execute a list of atoms, which always end with EXIT.
|
|
; IY points to that list. What do we do:
|
|
; 1. Push current IP to RS
|
|
; 2. Set new IP to the second atom of the list
|
|
; 3. Execute the first atom of the list.
|
|
compiledWord:
|
|
ld hl, (IP)
|
|
call pushRS
|
|
push iy \ pop hl
|
|
inc hl
|
|
inc hl
|
|
ld (IP), hl
|
|
; IY still is our atom reference...
|
|
ld l, (iy)
|
|
ld h, (iy+1)
|
|
push hl ; argument for EXECUTE
|
|
jp EXECUTE+2
|
|
|
|
; Pushes the PFA directly
|
|
cellWord:
|
|
push iy
|
|
jp next
|
|
|
|
; Pushes the address in the first word of the PF
|
|
sysvarWord:
|
|
ld l, (iy)
|
|
ld h, (iy+1)
|
|
push hl
|
|
jp next
|
|
|
|
; The word was spawned from a definition word that has a DOES>. PFA+2 (right
|
|
; after the actual cell) is a link to the slot right after that DOES>.
|
|
; Therefore, what we need to do push the cell addr like a regular cell, then
|
|
; follow the link from the PFA, and then continue as a regular compiledWord.
|
|
doesWord:
|
|
push iy ; like a regular cell
|
|
ld l, (iy+2)
|
|
ld h, (iy+3)
|
|
push hl \ pop iy
|
|
jr compiledWord
|
|
|
|
; This is not a word, but a number literal. This works a bit differently than
|
|
; others: PF means nothing and the actual number is placed next to the
|
|
; numberWord reference in the compiled word list. What we need to do to fetch
|
|
; that number is to play with the IP.
|
|
numberWord:
|
|
ld hl, (IP) ; (HL) is out number
|
|
ld e, (hl)
|
|
inc hl
|
|
ld d, (hl)
|
|
inc hl
|
|
ld (IP), hl ; advance IP by 2
|
|
push de
|
|
jp next
|
|
|
|
.db 0b10 ; Flags
|
|
NUMBER:
|
|
.dw numberWord
|
|
|
|
; Similarly to numberWord, this is not a real word, but a string literal.
|
|
; Instead of being followed by a 2 bytes number, it's followed by a
|
|
; null-terminated string. When called, puts the string's address on PS
|
|
litWord:
|
|
ld hl, (IP)
|
|
push hl
|
|
call strskip
|
|
inc hl ; after null termination
|
|
ld (IP), hl
|
|
jp next
|
|
|
|
.db 0b10 ; Flags
|
|
LIT:
|
|
.dw litWord
|
|
|
|
; Pop previous IP from Return stack and execute it.
|
|
; ( R:I -- )
|
|
.db "EXIT"
|
|
.fill 3
|
|
.dw 0
|
|
.db 0
|
|
EXIT:
|
|
.dw nativeWord
|
|
call popRS
|
|
ld (IP), hl
|
|
jp next
|
|
|
|
; ( R:I -- )
|
|
.db "QUIT"
|
|
.fill 3
|
|
.dw EXIT
|
|
.db 0
|
|
QUIT:
|
|
.dw nativeWord
|
|
quit:
|
|
jp forthRdLine
|
|
|
|
.db "ABORT"
|
|
.fill 2
|
|
.dw QUIT
|
|
.db 0
|
|
ABORT:
|
|
.dw nativeWord
|
|
abort:
|
|
; flush rest of input
|
|
ld hl, (INPUTPOS)
|
|
xor a
|
|
ld (hl), a
|
|
; Reinitialize PS (RS is reinitialized in forthInterpret)
|
|
ld sp, (INITIAL_SP)
|
|
jp forthRdLineNoOk
|
|
|
|
; prints msg in (HL) then aborts
|
|
abortMsg:
|
|
call printstr
|
|
jr abort
|
|
|
|
abortUnknownWord:
|
|
ld hl, .msg
|
|
jr abortMsg
|
|
.msg:
|
|
.db "unknown word", 0
|
|
|
|
.db "BYE"
|
|
.fill 4
|
|
.dw ABORT
|
|
.db 0
|
|
BYE:
|
|
.dw nativeWord
|
|
; Goodbye Forth! Before we go, let's restore the stack
|
|
ld sp, (INITIAL_SP)
|
|
; unwind stack underflow buffer
|
|
pop af \ pop af \ pop af
|
|
; success
|
|
xor a
|
|
ret
|
|
|
|
; ( c -- )
|
|
.db "EMIT"
|
|
.fill 3
|
|
.dw BYE
|
|
.db 0
|
|
EMIT:
|
|
.dw nativeWord
|
|
pop hl
|
|
ld a, l
|
|
call stdioPutC
|
|
jp next
|
|
|
|
; ( c port -- )
|
|
.db "PC!"
|
|
.fill 4
|
|
.dw EMIT
|
|
.db 0
|
|
PSTORE:
|
|
.dw nativeWord
|
|
pop bc
|
|
pop hl
|
|
out (c), l
|
|
jp next
|
|
|
|
; ( port -- c )
|
|
.db "PC@"
|
|
.fill 4
|
|
.dw PSTORE
|
|
.db 0
|
|
PFETCH:
|
|
.dw nativeWord
|
|
pop bc
|
|
ld h, 0
|
|
in l, (c)
|
|
push hl
|
|
jp next
|
|
|
|
; ( addr -- )
|
|
.db "EXECUTE"
|
|
.dw PFETCH
|
|
.db 0
|
|
EXECUTE:
|
|
.dw nativeWord
|
|
pop iy ; is a wordref
|
|
ld l, (iy)
|
|
ld h, (iy+1)
|
|
; HL points to code pointer
|
|
inc iy
|
|
inc iy
|
|
; IY points to PFA
|
|
jp (hl) ; go!
|
|
|
|
|
|
.db "[COMPIL"
|
|
.dw EXECUTE
|
|
.db 1 ; IMMEDIATE
|
|
COMPILE:
|
|
.dw nativeWord
|
|
call readword
|
|
call find
|
|
jr nz, .maybeNum
|
|
ex de, hl
|
|
call HLisIMMED
|
|
jr z, .immed
|
|
ex de, hl
|
|
call .writeDE
|
|
jp next
|
|
.maybeNum:
|
|
push hl ; --> lvl 1. save string addr
|
|
call parseLiteral
|
|
pop hl ; <-- lvl 1
|
|
jr nz, .undef
|
|
; a valid number in DE!
|
|
ex de, hl
|
|
ld de, NUMBER
|
|
call .writeDE
|
|
ex de, hl ; number in DE
|
|
call .writeDE
|
|
jp next
|
|
.undef:
|
|
call printstr
|
|
jp abortUnknownWord
|
|
.immed:
|
|
push hl
|
|
jp EXECUTE+2
|
|
.writeDE:
|
|
push hl
|
|
ld hl, (HERE)
|
|
call DEinHL
|
|
ld (HERE), hl
|
|
pop hl
|
|
ret
|
|
|
|
|
|
.db ":"
|
|
.fill 6
|
|
.dw COMPILE
|
|
.db 1 ; IMMEDIATE
|
|
DEFINE:
|
|
.dw nativeWord
|
|
call entryhead
|
|
ld de, compiledWord
|
|
call DEinHL
|
|
ld (HERE), hl
|
|
.loop:
|
|
; did we reach ";"?
|
|
call toword
|
|
ld a, (hl)
|
|
cp ';'
|
|
jr nz, .compile
|
|
inc hl
|
|
ld a, (hl)
|
|
cp ' '+1
|
|
jr c, .loopend ; whitespace, we have semicol. end
|
|
.compile:
|
|
ld hl, (IP)
|
|
call pushRS
|
|
ld hl, .retRef
|
|
ld (IP), hl
|
|
ld hl, COMPILE
|
|
push hl
|
|
jp EXECUTE+2
|
|
.loopend:
|
|
; Advance (INPUTPOS) to after semicol. HL is already there.
|
|
ld (INPUTPOS), hl
|
|
; write EXIT and return
|
|
ld hl, (HERE)
|
|
ld de, EXIT
|
|
call DEinHL
|
|
ld (HERE), hl
|
|
jp next
|
|
.retRef:
|
|
.dw $+2
|
|
.dw $+2
|
|
call popRS
|
|
ld (IP), hl
|
|
jr .loop
|
|
|
|
|
|
.db "DOES>"
|
|
.fill 2
|
|
.dw DEFINE
|
|
.db 0
|
|
DOES:
|
|
.dw nativeWord
|
|
; We run this when we're in an entry creation context. Many things we
|
|
; need to do.
|
|
; 1. Change the code link to doesWord
|
|
; 2. Leave 2 bytes for regular cell variable.
|
|
; 3. Write down IP+2 to entry.
|
|
; 3. exit. we're done here.
|
|
ld iy, (CURRENT)
|
|
ld hl, doesWord
|
|
call wrCompHL
|
|
inc iy \ inc iy ; cell variable space
|
|
ld hl, (IP)
|
|
call wrCompHL
|
|
ld (HERE), iy
|
|
jp EXIT+2
|
|
|
|
|
|
.db "IMMEDIA"
|
|
.dw DOES
|
|
.db 0
|
|
IMMEDIATE:
|
|
.dw nativeWord
|
|
ld hl, (CURRENT)
|
|
dec hl
|
|
set FLAG_IMMED, (hl)
|
|
jp next
|
|
|
|
; ( n -- )
|
|
.db "LITN"
|
|
.fill 3
|
|
.dw IMMEDIATE
|
|
.db 1 ; IMMEDIATE
|
|
LITN:
|
|
.dw nativeWord
|
|
ld hl, (HERE)
|
|
ld de, NUMBER
|
|
call DEinHL
|
|
pop de ; number from stack
|
|
call DEinHL
|
|
ld (HERE), hl
|
|
jp next
|
|
|
|
.db "LITS"
|
|
.fill 3
|
|
.dw LITN
|
|
.db 1 ; IMMEDIATE
|
|
LITS:
|
|
.dw nativeWord
|
|
ld hl, (HERE)
|
|
ld de, LIT
|
|
call DEinHL
|
|
ex de, hl ; (HERE) in DE
|
|
call readword
|
|
call strcpyM
|
|
ld (HERE), de
|
|
jp next
|
|
|
|
.db "'"
|
|
.fill 6
|
|
.dw LITS
|
|
.db 0
|
|
APOS:
|
|
.dw nativeWord
|
|
call readword
|
|
call find
|
|
jr nz, .notfound
|
|
push de
|
|
jp next
|
|
.notfound:
|
|
ld hl, .msg
|
|
call printstr
|
|
jp abort
|
|
.msg:
|
|
.db "word not found", 0
|
|
|
|
.db "[']"
|
|
.fill 4
|
|
.dw APOS
|
|
.db 0b01 ; IMMEDIATE
|
|
APOSI:
|
|
.dw nativeWord
|
|
call readword
|
|
call find
|
|
jr nz, .notfound
|
|
ld hl, (HERE)
|
|
push de ; --> lvl 1
|
|
ld de, NUMBER
|
|
call DEinHL
|
|
pop de ; <-- lvl 1
|
|
call DEinHL
|
|
ld (HERE), hl
|
|
jp next
|
|
.notfound:
|
|
ld hl, .msg
|
|
call printstr
|
|
jp abort
|
|
.msg:
|
|
.db "word not found", 0
|
|
|
|
; ( -- c )
|
|
.db "KEY"
|
|
.fill 4
|
|
.dw APOSI
|
|
.db 0
|
|
KEY:
|
|
.dw nativeWord
|
|
call stdioGetC
|
|
ld h, 0
|
|
ld l, a
|
|
push hl
|
|
jp next
|
|
|
|
.db "WORD"
|
|
.fill 3
|
|
.dw KEY
|
|
.db 0
|
|
WORD:
|
|
.dw nativeWord
|
|
call readword
|
|
push hl
|
|
jp next
|
|
|
|
.db "CREATE"
|
|
.fill 1
|
|
.dw WORD
|
|
.db 0
|
|
CREATE:
|
|
.dw nativeWord
|
|
call entryhead
|
|
ld de, cellWord
|
|
ld (hl), e
|
|
inc hl
|
|
ld (hl), d
|
|
inc hl
|
|
ld (HERE), hl
|
|
jp next
|
|
|
|
.db "HERE"
|
|
.fill 3
|
|
.dw CREATE
|
|
.db 0
|
|
HERE_: ; Caution: conflicts with actual variable name
|
|
.dw sysvarWord
|
|
.dw HERE
|
|
|
|
.db "CURRENT"
|
|
.dw HERE_
|
|
.db 0
|
|
CURRENT_:
|
|
.dw sysvarWord
|
|
.dw CURRENT
|
|
|
|
.db "IN>"
|
|
.fill 4
|
|
.dw CURRENT_
|
|
.db 0
|
|
INP:
|
|
.dw sysvarWord
|
|
.dw INPUTPOS
|
|
|
|
; ( n a -- )
|
|
.db "!"
|
|
.fill 6
|
|
.dw INP
|
|
.db 0
|
|
STORE:
|
|
.dw nativeWord
|
|
pop iy
|
|
pop hl
|
|
ld (iy), l
|
|
ld (iy+1), h
|
|
jp next
|
|
|
|
; ( n a -- )
|
|
.db "C!"
|
|
.fill 5
|
|
.dw STORE
|
|
.db 0
|
|
CSTORE:
|
|
.dw nativeWord
|
|
pop hl
|
|
pop de
|
|
ld (hl), e
|
|
jp next
|
|
|
|
; ( a -- n )
|
|
.db "@"
|
|
.fill 6
|
|
.dw CSTORE
|
|
.db 0
|
|
FETCH:
|
|
.dw nativeWord
|
|
pop hl
|
|
call intoHL
|
|
push hl
|
|
jp next
|
|
|
|
; ( a -- c )
|
|
.db "C@"
|
|
.fill 5
|
|
.dw FETCH
|
|
.db 0
|
|
CFETCH:
|
|
.dw nativeWord
|
|
pop hl
|
|
ld l, (hl)
|
|
ld h, 0
|
|
push hl
|
|
jp next
|
|
|
|
; ( a -- )
|
|
.db "DROP"
|
|
.fill 3
|
|
.dw CFETCH
|
|
.db 0
|
|
DROP:
|
|
.dw nativeWord
|
|
pop hl
|
|
jp next
|
|
|
|
; ( a b -- b a )
|
|
.db "SWAP"
|
|
.fill 3
|
|
.dw DROP
|
|
.db 0
|
|
SWAP:
|
|
.dw nativeWord
|
|
pop hl
|
|
ex (sp), hl
|
|
push hl
|
|
jp next
|
|
|
|
; ( a b c d -- c d a b )
|
|
.db "2SWAP"
|
|
.fill 2
|
|
.dw SWAP
|
|
.db 0
|
|
SWAP2:
|
|
.dw nativeWord
|
|
pop de ; D
|
|
pop hl ; C
|
|
pop bc ; B
|
|
|
|
ex (sp), hl ; A in HL
|
|
push de ; D
|
|
push hl ; A
|
|
push bc ; B
|
|
jp next
|
|
|
|
; ( a -- a a )
|
|
.db "DUP"
|
|
.fill 4
|
|
.dw SWAP2
|
|
.db 0
|
|
DUP:
|
|
.dw nativeWord
|
|
pop hl
|
|
push hl
|
|
push hl
|
|
jp next
|
|
|
|
; ( a b -- a b a b )
|
|
.db "2DUP"
|
|
.fill 3
|
|
.dw DUP
|
|
.db 0
|
|
DUP2:
|
|
.dw nativeWord
|
|
pop hl ; B
|
|
pop de ; A
|
|
push de
|
|
push hl
|
|
push de
|
|
push hl
|
|
jp next
|
|
|
|
; ( a b -- a b a )
|
|
.db "OVER"
|
|
.fill 3
|
|
.dw DUP2
|
|
.db 0
|
|
OVER:
|
|
.dw nativeWord
|
|
pop hl ; B
|
|
pop de ; A
|
|
push de
|
|
push hl
|
|
push de
|
|
jp next
|
|
|
|
; ( a b c d -- a b c d a b )
|
|
.db "2OVER"
|
|
.fill 2
|
|
.dw OVER
|
|
.db 0
|
|
OVER2:
|
|
.dw nativeWord
|
|
pop hl ; D
|
|
pop de ; C
|
|
pop bc ; B
|
|
pop iy ; A
|
|
push iy ; A
|
|
push bc ; B
|
|
push de ; C
|
|
push hl ; D
|
|
push iy ; A
|
|
push bc ; B
|
|
jp next
|
|
|
|
.db ">R"
|
|
.fill 5
|
|
.dw OVER2
|
|
.db 0
|
|
P2R:
|
|
.dw nativeWord
|
|
pop hl
|
|
call pushRS
|
|
jp next
|
|
|
|
.db "R>"
|
|
.fill 5
|
|
.dw P2R
|
|
.db 0
|
|
R2P:
|
|
.dw nativeWord
|
|
call popRS
|
|
push hl
|
|
jp next
|
|
|
|
.db "I"
|
|
.fill 6
|
|
.dw R2P
|
|
.db 0
|
|
I:
|
|
.dw nativeWord
|
|
ld l, (ix)
|
|
ld h, (ix+1)
|
|
push hl
|
|
jp next
|
|
|
|
.db "I'"
|
|
.fill 5
|
|
.dw I
|
|
.db 0
|
|
IPRIME:
|
|
.dw nativeWord
|
|
ld l, (ix-2)
|
|
ld h, (ix-1)
|
|
push hl
|
|
jp next
|
|
|
|
.db "J"
|
|
.fill 6
|
|
.dw IPRIME
|
|
.db 0
|
|
J:
|
|
.dw nativeWord
|
|
ld l, (ix-4)
|
|
ld h, (ix-3)
|
|
push hl
|
|
jp next
|
|
|
|
; ( a b -- c ) A + B
|
|
.db "+"
|
|
.fill 6
|
|
.dw J
|
|
.db 0
|
|
PLUS:
|
|
.dw nativeWord
|
|
pop hl
|
|
pop de
|
|
add hl, de
|
|
push hl
|
|
jp next
|
|
|
|
; ( a b -- c ) A - B
|
|
.db "-"
|
|
.fill 6
|
|
.dw PLUS
|
|
.db 0
|
|
MINUS:
|
|
.dw nativeWord
|
|
pop de ; B
|
|
pop hl ; A
|
|
or a ; reset carry
|
|
sbc hl, de
|
|
push hl
|
|
jp next
|
|
|
|
; ( a b -- c ) A * B
|
|
.db "*"
|
|
.fill 6
|
|
.dw MINUS
|
|
.db 0
|
|
MULT:
|
|
.dw nativeWord
|
|
pop de
|
|
pop bc
|
|
call multDEBC
|
|
push hl
|
|
jp next
|
|
|
|
|
|
.db "/MOD"
|
|
.fill 3
|
|
.dw MULT
|
|
.db 0
|
|
DIVMOD:
|
|
.dw nativeWord
|
|
pop de
|
|
pop hl
|
|
call divide
|
|
push hl
|
|
push bc
|
|
jp next
|
|
|
|
; ( a1 a2 -- b )
|
|
.db "SCMP"
|
|
.fill 3
|
|
.dw DIVMOD
|
|
.db 0
|
|
SCMP:
|
|
.dw nativeWord
|
|
pop de
|
|
pop hl
|
|
call strcmp
|
|
call flagsToBC
|
|
push bc
|
|
jp next
|
|
|
|
; ( n1 n2 -- f )
|
|
.db "CMP"
|
|
.fill 4
|
|
.dw SCMP
|
|
.db 0
|
|
CMP:
|
|
.dw nativeWord
|
|
pop hl
|
|
pop de
|
|
or a ; clear carry
|
|
sbc hl, de
|
|
call flagsToBC
|
|
push bc
|
|
jp next
|
|
|
|
.db "SKIP?"
|
|
.fill 2
|
|
.dw CMP
|
|
.db 0
|
|
CSKIP:
|
|
.dw nativeWord
|
|
pop hl
|
|
ld a, h
|
|
or l
|
|
jp z, next ; False, do nothing.
|
|
ld hl, (IP)
|
|
call compSkip
|
|
ld (IP), hl
|
|
jp next
|
|
|
|
; This word's atom is followed by 1b *relative* offset (to the cell's addr) to
|
|
; where to branch to. For example, The branching cell of "IF THEN" would
|
|
; contain 3. Add this value to RS.
|
|
.db "(fbr)"
|
|
.fill 2
|
|
.dw CSKIP
|
|
.db 0
|
|
FBR:
|
|
.dw nativeWord
|
|
push de
|
|
ld hl, (IP)
|
|
ld a, (hl)
|
|
call addHL
|
|
ld (IP), hl
|
|
pop de
|
|
jp next
|
|
|
|
.db "(bbr)"
|
|
.fill 2
|
|
.dw FBR
|
|
.db 0
|
|
BBR:
|
|
.dw nativeWord
|
|
ld hl, (IP)
|
|
ld d, 0
|
|
ld e, (hl)
|
|
or a ; clear carry
|
|
sbc hl, de
|
|
ld (IP), hl
|
|
jp next
|
|
|
|
LATEST:
|
|
.dw BBR
|