forth: make readCompWord read from RS' BOS instead of TOS

Previous approach was broken with regards to defined word using CREATE.

Also, reduce name length by one to make space for a new flags field for
"immediate" (which isn't used yet).
This commit is contained in:
Virgil Dupras 2020-03-09 19:50:51 -04:00
parent 0e8af3cea4
commit 03bd9ee39b
3 changed files with 86 additions and 67 deletions

View File

@ -1,5 +1,6 @@
; A dictionary entry has this structure: ; A dictionary entry has this structure:
; - 8b name (zero-padded) ; - 7b name (zero-padded)
; - 1b flags (bit 0: IMMEDIATE)
; - 2b prev pointer ; - 2b prev pointer
; - 2b code pointer ; - 2b code pointer
; - Parameter field (PF) ; - Parameter field (PF)
@ -168,14 +169,16 @@ exit:
jp compiledWord jp compiledWord
; ( R:I -- ) ; ( R:I -- )
.db "QUIT", 0, 0, 0, 0 .db "QUIT"
.fill 4
.dw EXIT .dw EXIT
QUIT: QUIT:
.dw nativeWord .dw nativeWord
quit: quit:
jp forthRdLine jp forthRdLine
.db "ABORT", 0, 0, 0 .db "ABORT"
.fill 3
.dw QUIT .dw QUIT
ABORT: ABORT:
.dw nativeWord .dw nativeWord
@ -198,7 +201,8 @@ BYE:
ret ret
; ( c -- ) ; ( c -- )
.db "EMIT", 0, 0, 0, 0 .db "EMIT"
.fill 4
.dw BYE .dw BYE
EMIT: EMIT:
.dw nativeWord .dw nativeWord
@ -208,7 +212,8 @@ EMIT:
jp exit jp exit
; ( addr -- ) ; ( addr -- )
.db "EXECUTE", 0 .db "EXECUTE"
.db 0
.dw EMIT .dw EMIT
EXECUTE: EXECUTE:
.dw nativeWord .dw nativeWord
@ -239,19 +244,18 @@ DEFINE:
; All we need to do is to know how many bytes to copy. To do so, we ; All we need to do is to know how many bytes to copy. To do so, we
; skip compwords until EXIT is reached. ; skip compwords until EXIT is reached.
ld (HERE), hl ; where we write compwords. ld (HERE), hl ; where we write compwords.
; Let's save old RS TOS ld l, (ix)
ld e, (ix) ld h, (ix+1)
ld d, (ix+1)
.loop: .loop:
call RSIsEXIT call HLPointsEXIT
jr z, .loopend jr z, .loopend
call compSkip call compSkip
jr .loop jr .loop
.loopend: .loopend:
; At this point, RS' TOS points to EXIT compword. We'll copy it too. ; At this point, HL points to EXIT compword. We'll copy it too.
; We'll use LDIR. BC will be RSTOP-OLDRSTOP+2 ; We'll use LDIR. BC will be RSTOP-OLDRSTOP+2
ld l, (ix) ld e, (ix)
ld h, (ix+1) ld d, (ix+1)
inc hl \ inc hl ; our +2 inc hl \ inc hl ; our +2
or a ; clear carry or a ; clear carry
sbc hl, de sbc hl, de
@ -267,7 +271,8 @@ DEFINE:
ld (HERE), de ; update HERE ld (HERE), de ; update HERE
jp exit jp exit
.db "DOES>", 0, 0, 0 .db "DOES>"
.fill 3
.dw DEFINE .dw DEFINE
DOES: DOES:
.dw nativeWord .dw nativeWord
@ -300,7 +305,8 @@ KEY:
push hl push hl
jp exit jp exit
.db "INTERPRE" .db "INTERPR"
.db 0
.dw KEY .dw KEY
INTERPRET: INTERPRET:
.dw nativeWord .dw nativeWord
@ -317,7 +323,8 @@ interpret:
ld iy, COMPBUF ld iy, COMPBUF
jp compiledWord jp compiledWord
.db "CREATE", 0, 0 .db "CREATE"
.fill 2
.dw INTERPRET .dw INTERPRET
CREATE: CREATE:
.dw nativeWord .dw nativeWord
@ -338,7 +345,8 @@ HERE_: ; Caution: conflicts with actual variable name
.dw sysvarWord .dw sysvarWord
.dw HERE .dw HERE
.db "CURRENT", 0 .db "CURRENT"
.db 0
.dw HERE_ .dw HERE_
CURRENT_: CURRENT_:
.dw sysvarWord .dw sysvarWord
@ -514,7 +522,8 @@ STOREINC:
; ( n -- ) ; ( n -- )
; HERE +! ; HERE +!
.db "ALLOT", 0, 0, 0 .db "ALLOT"
.fill 3
.dw STOREINC .dw STOREINC
ALLOT: ALLOT:
.dw compiledWord .dw compiledWord
@ -523,7 +532,8 @@ ALLOT:
.dw EXIT .dw EXIT
; CREATE 2 ALLOT ; CREATE 2 ALLOT
.db "VARIABLE" .db "VARIABL"
.db 0
.dw ALLOT .dw ALLOT
VARIABLE: VARIABLE:
.dw compiledWord .dw compiledWord
@ -535,7 +545,8 @@ VARIABLE:
; ( n -- ) ; ( n -- )
; CREATE HERE @ ! DOES> @ ; CREATE HERE @ ! DOES> @
.db "CONSTANT" .db "CONSTAN"
.db 0
.dw VARIABLE .dw VARIABLE
CONSTANT: CONSTANT:
.dw compiledWord .dw compiledWord

View File

@ -4,9 +4,9 @@
; Number of bytes we keep as a padding between HERE and the scratchpad ; Number of bytes we keep as a padding between HERE and the scratchpad
.equ PADDING 0x20 .equ PADDING 0x20
; Max length of dict entry names ; Max length of dict entry names
.equ NAMELEN 8 .equ NAMELEN 7
; Offset of the code link relative to the beginning of the word ; Offset of the code link relative to the beginning of the word
.equ CODELINK_OFFSET NAMELEN+2 .equ CODELINK_OFFSET NAMELEN+3
; *** Variables *** ; *** Variables ***
.equ INITIAL_SP FORTH_RAMSTART .equ INITIAL_SP FORTH_RAMSTART

View File

@ -43,78 +43,78 @@ readword:
inc a ; unset Z inc a ; unset Z
ret ret
RSIsDE: ; Sets Z if (HL) == E and (HL+1) == D
push hl HLPointsDE:
ld l, (ix)
ld h, (ix+1)
ld a, (hl) ld a, (hl)
cp e cp e
jr nz, .end ; no ret nz ; no
inc hl inc hl
ld a, (hl) ld a, (hl)
dec hl
cp d ; Z has our answer cp d ; Z has our answer
.end:
pop hl
ret ret
; Is RS' TOS pointing to a NUMBER word? HLPointsNUMBER:
; Z if yes, NZ if no.
RSIsNUMBER:
push de push de
ld de, NUMBER ld de, NUMBER
call RSIsDE call HLPointsDE
pop de pop de
ret ret
; Is RS' TOS pointing to a LIT word? HLPointsLIT:
; Z if yes, NZ if no.
RSIsLIT:
push de push de
ld de, LIT ld de, LIT
call RSIsDE call HLPointsDE
pop de pop de
ret ret
; Is RS' TOS pointing to EXIT? HLPointsEXIT:
; Z if yes, NZ if no.
RSIsEXIT:
push de push de
ld de, EXIT ld de, EXIT
call RSIsDE call HLPointsDE
pop de pop de
ret ret
; Skip the compword where RS' TOS is currently pointing. If it's a regular word, ; Skip the compword where HL is currently pointing. If it's a regular word,
; it's easy: we inc by 2. If it's a NUMBER, we inc by 4. If it's a LIT, we skip ; it's easy: we inc by 2. If it's a NUMBER, we inc by 4. If it's a LIT, we skip
; to after null-termination. ; to after null-termination.
compSkip: compSkip:
push hl call HLPointsNUMBER
ld l, (ix)
ld h, (ix+1)
; At the minimum, we skip by 2
inc hl \ inc hl
call RSIsNUMBER
jr z, .isNum jr z, .isNum
call RSIsLIT call HLPointsLIT
jr nz, .end ; A word jr nz, .isWord
; We have a literal ; We have a literal
inc hl \ inc hl
call strskip call strskip
inc hl ; byte after word termination inc hl ; byte after word termination
jr .end ret
.isNum: .isNum:
; skip by 4 ; skip by 4
inc hl \ inc hl inc hl \ inc hl
.end: ; continue to isWord
; HL is good, write it to RS .isWord:
ld (ix), l ; skip by 2
ld (ix+1), h inc hl \ inc hl
pop hl
ret ret
; Checks RS' TOS and, if it points to a string literal (LIT), makes HL point ; The goal of this routine is to read a string literal following the currently
; to it and advance IP to byte following null-termination. ; executed words. For example, CREATE and DEFINE need this. Things are a little
; If it doesn't, things get interesting: If it's a word reference, then it's ; twisted, so bear with me while I explain how it works.
;
; When we call this routine, everything has been compiled. We're on an atom and
; we're executing it. Now, we're looking for a string literal or a word-with-a
; name that follows our readCompWord caller. We could think that this word is
; right there on RS' TOS, but no! You have to account for words wrapping the
; caller. For example, "VARIABLE" calls "CREATE". If you call "VARIABLE foo",
; if CREATE looks at what follows in RS' TOS, it will only find the "2" in
; "CREATE 2 ALLOT".
;
; Therefore, we actually need to check in RS' *bottom of stack* for our answer.
; If that atom is a LIT, we're good. We make HL point to it and advance IP to
; byte following null-termination.
;
; If it isn't, things get interesting: If it's a word reference, then it's
; not an invalid literal. For example, one could want to redefine an existing ; not an invalid literal. For example, one could want to redefine an existing
; word. So in that case, we'll copy the word's name on the pad (it might not be ; word. So in that case, we'll copy the word's name on the pad (it might not be
; null-terminated) and set HL to point to it. ; null-terminated) and set HL to point to it.
@ -124,18 +124,22 @@ compSkip:
; word. Yeah, it means that ";" can't be overridden... ; word. Yeah, it means that ";" can't be overridden...
; If name can't be read, we abort ; If name can't be read, we abort
readCompWord: readCompWord:
; In all cases, we want RS' TOS in HL. Let's get it now. ; In all cases, we want RS' BOS in HL. Let's get it now.
ld l, (ix) ld hl, (RS_ADDR)
ld h, (ix+1) call HLPointsLIT
call RSIsLIT
jr nz, .notLIT jr nz, .notLIT
; RS TOS is a LIT, make HL point to string, then skip this RS compword. ; RS BOS is a LIT, make HL point to string, then skip this RS compword.
inc hl \ inc hl ; HL now points to string itself inc hl \ inc hl ; HL now points to string itself
jr compSkip push hl ; --> lvl 1, our result
call strskip
inc hl ; byte after word termination
ld (RS_ADDR), hl
pop hl ; <-- lvl 1, our result
ret
.notLIT: .notLIT:
; Alright, not a literal, but is it a word? If it's not a number, then ; Alright, not a literal, but is it a word? If it's not a number, then
; it's a word. ; it's a word.
call RSIsNUMBER call HLPointsNUMBER
jr z, .notWord jr z, .notWord
; Not a number, then it's a word. Copy word to pad and point to it. ; Not a number, then it's a word. Copy word to pad and point to it.
call intoHL call intoHL
@ -153,7 +157,9 @@ readCompWord:
; null-terminate ; null-terminate
xor a xor a
ld (de), a ld (de), a
call compSkip ; Advance RS' BOS by 2
ld hl, RS_ADDR
inc (hl) \ inc (hl)
pop hl ; <-- lvl 1 pop hl ; <-- lvl 1
ret ret
.notWord: .notWord:
@ -251,13 +257,15 @@ compile:
; HL points to new (HERE) ; HL points to new (HERE)
entryhead: entryhead:
call readCompWord call readCompWord
call printstr
ld de, (HERE) ld de, (HERE)
call strcpy call strcpy
ex de, hl ; (HERE) now in HL ex de, hl ; (HERE) now in HL
ld de, (CURRENT) ld de, (CURRENT)
ld a, NAMELEN ld a, NAMELEN
call addHL call addHL
xor a ; IMMED
ld (hl), a
inc hl
ld (hl), e ld (hl), e
inc hl inc hl
ld (hl), d ld (hl), d