fe15bafeca
During expression parsing, if a local label was parsed, it would select the local registry and keep that selection, making subsequent global labels register in the wrong place.
292 lines
7.6 KiB
NASM
292 lines
7.6 KiB
NASM
; Manages both constants and labels within a same namespace and registry.
|
|
;
|
|
; Local Labels
|
|
;
|
|
; Local labels during the "official" first pass are ignored. To register them
|
|
; in the global registry during that pass would be wasteful in terms of memory.
|
|
;
|
|
; What we don instead is set up a separate register for them and have a "second
|
|
; first pass" whenever we encounter a new context. That is, we wipe the local
|
|
; registry, parse the code until the next global symbol (or EOF), then rewind
|
|
; and continue second pass as usual.
|
|
|
|
; *** Constants ***
|
|
; Maximum number of symbols we can have in the global registry
|
|
.equ SYM_MAXCOUNT 0x200
|
|
; Maximum number of symbols we can have in the local registry
|
|
.equ SYM_LOC_MAXCOUNT 0x40
|
|
|
|
; Size of the symbol name buffer size. This is a pool. There is no maximum name
|
|
; length for a single symbol, just a maximum size for the whole pool.
|
|
.equ SYM_BUFSIZE 0x2000
|
|
|
|
; Size of the names buffer for the local context registry
|
|
.equ SYM_LOC_BUFSIZE 0x200
|
|
|
|
; *** Variables ***
|
|
; Each symbol is mapped to a word value saved here.
|
|
.equ SYM_VALUES SYM_RAMSTART
|
|
|
|
; A list of symbol names separated by null characters. When we encounter a
|
|
; symbol name and want to get its value, we search the name here, retrieve the
|
|
; index of the name, then go get the value at that index in SYM_VALUES.
|
|
.equ SYM_NAMES SYM_VALUES+SYM_MAXCOUNT*2
|
|
|
|
; Registry for local labels. Wiped out after each context change.
|
|
.equ SYM_LOC_VALUES SYM_NAMES+SYM_BUFSIZE
|
|
.equ SYM_LOC_NAMES SYM_LOC_VALUES+SYM_LOC_MAXCOUNT*2
|
|
|
|
; Pointer to the currently selected registry
|
|
.equ SYM_CTX_NAMES SYM_LOC_NAMES+SYM_LOC_BUFSIZE
|
|
.equ SYM_CTX_NAMESEND SYM_CTX_NAMES+2
|
|
.equ SYM_CTX_VALUES SYM_CTX_NAMESEND+2
|
|
; Pointer, in (SYM_CTX_VALUES), to the result of the last symFind
|
|
.equ SYM_CTX_PTR SYM_CTX_VALUES+2
|
|
|
|
.equ SYM_RAMEND SYM_CTX_PTR+2
|
|
|
|
; *** Code ***
|
|
|
|
; Advance HL to the beginning of the next symbol name in SYM_NAMES except if
|
|
; (HL) is already zero, meaning we're at the end of the chain. In this case,
|
|
; do nothing.
|
|
; Sets Z if it succeeded, unset it if there is no next.
|
|
_symNext:
|
|
xor a
|
|
cp (hl)
|
|
jr nz, .do ; (HL) is not zero? we can advance.
|
|
; (HL) is zero? we're at the end of the chain.
|
|
call unsetZ
|
|
ret
|
|
.do:
|
|
; A is already 0
|
|
call findchar ; find next null char
|
|
; go to the char after it.
|
|
inc hl
|
|
cp a ; ensure Z
|
|
ret
|
|
|
|
symInit:
|
|
xor a
|
|
ld (SYM_NAMES), a
|
|
ld (SYM_LOC_NAMES), a
|
|
; Continue to symSelectGlobalRegistry
|
|
|
|
symSelectGlobalRegistry:
|
|
push af
|
|
push hl
|
|
ld hl, SYM_NAMES
|
|
ld (SYM_CTX_NAMES), hl
|
|
ld hl, SYM_NAMES+SYM_BUFSIZE
|
|
ld (SYM_CTX_NAMESEND), hl
|
|
ld hl, SYM_VALUES
|
|
ld (SYM_CTX_VALUES), hl
|
|
pop hl
|
|
pop af
|
|
ret
|
|
|
|
symSelectLocalRegistry:
|
|
push af
|
|
push hl
|
|
ld hl, SYM_LOC_NAMES
|
|
ld (SYM_CTX_NAMES), hl
|
|
ld hl, SYM_LOC_NAMES+SYM_LOC_BUFSIZE
|
|
ld (SYM_CTX_NAMESEND), hl
|
|
ld hl, SYM_LOC_VALUES
|
|
ld (SYM_CTX_VALUES), hl
|
|
ld a, h
|
|
ld a, l
|
|
pop hl
|
|
pop af
|
|
ret
|
|
|
|
; Sets Z according to whether label in (HL) is local (starts with a dot)
|
|
symIsLabelLocal:
|
|
ld a, '.'
|
|
cp (hl)
|
|
ret
|
|
|
|
; Place HL at the end of (SYM_CTX_NAMES) end (that is, at the point where we
|
|
; have two consecutive null chars and DE at the corresponding position in
|
|
; SYM_CTX_VALUES).
|
|
; If we're within bounds, Z is set, otherwise unset.
|
|
symNamesEnd:
|
|
push ix
|
|
push bc
|
|
|
|
ld ix, (SYM_CTX_VALUES)
|
|
ld hl, (SYM_CTX_NAMES)
|
|
ld de, (SYM_CTX_NAMESEND)
|
|
.loop:
|
|
call _symNext
|
|
jr nz, .success ; We've reached the end of the chain.
|
|
inc ix
|
|
inc ix
|
|
; Are we out of bounds name-wise?
|
|
call cpHLDE
|
|
jr nc, .outOfBounds ; HL >= DE
|
|
; are we out of bounds value-wise? check if IX == (SYM_CTX_NAMES)
|
|
; Is is assumed that values are placed right before names
|
|
push hl
|
|
push ix \ pop bc
|
|
ld hl, (SYM_CTX_NAMES)
|
|
sbc hl, bc
|
|
pop hl
|
|
jr z, .outOfBounds ; IX == (SYM_CTX_NAMES)
|
|
jr .loop
|
|
.outOfBounds:
|
|
call unsetZ
|
|
jr .end
|
|
.success:
|
|
push ix \ pop de ; our values pos goes in DE
|
|
cp a ; ensure Z
|
|
.end:
|
|
pop bc
|
|
pop ix
|
|
ret
|
|
|
|
; Register label in (HL) (minus the ending ":") into the symbol registry and
|
|
; set its value in that registry to DE.
|
|
; If successful, Z is set and A is the symbol index. Otherwise, Z is unset and
|
|
; A is an error code (ERR_*).
|
|
symRegister:
|
|
call symFind
|
|
jr z, .alreadyThere
|
|
|
|
push hl ; will be used during processing. it's the symbol to add
|
|
push de ; will be used during processing. it's our value.
|
|
|
|
|
|
; First, let's get our strlen
|
|
call strlen
|
|
ld c, a ; save that strlen for later
|
|
|
|
call symNamesEnd
|
|
jr nz, .outOfMemory
|
|
|
|
; Is our new name going to make us go out of bounds?
|
|
push hl
|
|
push de
|
|
ld de, (SYM_CTX_NAMESEND)
|
|
ld a, c
|
|
call addHL
|
|
call cpHLDE
|
|
pop de
|
|
pop hl
|
|
jr nc, .outOfMemory ; HL >= DE
|
|
|
|
; Success. At this point, we have:
|
|
; HL -> where we want to add the string
|
|
; DE -> where the value goes
|
|
; SP -> value to register
|
|
; SP+2 -> string to register
|
|
|
|
; Let's start with the value.
|
|
push hl \ pop ix ; save HL for later
|
|
pop hl ; value to register
|
|
call writeHLinDE ; write value where it goes.
|
|
|
|
; Good! now, the string.
|
|
pop hl ; string to register
|
|
push ix \ pop de ; string destination
|
|
; Copy HL into DE until we reach null char
|
|
call strcpyM
|
|
|
|
; We need to add a second null char to indicate the end of the name
|
|
; list. DE is already correctly placed, A is already zero
|
|
ld (de), a
|
|
|
|
cp a ; ensure Z
|
|
; Nothing to pop. We've already popped our stack in the lines above.
|
|
ret
|
|
|
|
.outOfMemory:
|
|
ld a, ERR_OOM
|
|
call unsetZ
|
|
pop de
|
|
pop hl
|
|
ret
|
|
|
|
.alreadyThere:
|
|
; We are in a tricky situation with regards to our handling of the
|
|
; duplicate symbol error. Normally, it should be straightforward: We
|
|
; only register labels during first pass and evaluate constants during
|
|
; the second. Easy.
|
|
; We can *almost* do that... but we have ".org". .org affects label
|
|
; values and supports expressions, which means that we have to evaluate
|
|
; constants during first pass. But because we can possibly have forward
|
|
; references in ".equ", some constants are going to have a bad value.
|
|
; Therefore, we really can't evaluate all constants during the first
|
|
; pass.
|
|
; With this situation, how do you manage detection of duplicate symbols?
|
|
; By limiting the "duplicate error" condition to the first pass. During,
|
|
; first pass, sure, we don't have our proper values, but we have all our
|
|
; symbol names. So, if we end up in .alreadyThere during first pass,
|
|
; then it's an error condition. If it's not first pass, then we need
|
|
; to update our value.
|
|
call zasmIsFirstPass
|
|
jr z, .duplicateError
|
|
; Second pass. Don't error out, just update value
|
|
push hl
|
|
ld hl, (SYM_CTX_PTR)
|
|
ex de, hl
|
|
call writeHLinDE
|
|
pop hl
|
|
cp a ; ensure Z
|
|
ret
|
|
.duplicateError:
|
|
ld a, ERR_DUPSYM
|
|
jp unsetZ ; return
|
|
|
|
; Find name (HL) in (SYM_CTX_NAMES) and make (SYM_CTX_PTR) point to the
|
|
; corresponding entry in (SYM_CTX_VALUES).
|
|
; If we find something, Z is set, otherwise unset.
|
|
symFind:
|
|
push ix
|
|
push hl
|
|
push de
|
|
|
|
ex de, hl ; it's easier if HL is haystack and DE is
|
|
; needle.
|
|
ld ix, (SYM_CTX_VALUES)
|
|
ld hl, (SYM_CTX_NAMES)
|
|
.loop:
|
|
call strcmp
|
|
jr z, .match
|
|
; ok, next!
|
|
call _symNext
|
|
jr nz, .nomatch ; end of the chain, nothing found
|
|
inc ix
|
|
inc ix
|
|
jr .loop
|
|
.nomatch:
|
|
call unsetZ
|
|
jr .end
|
|
.match:
|
|
ld (SYM_CTX_PTR), ix
|
|
cp a ; ensure Z
|
|
.end:
|
|
pop de
|
|
pop hl
|
|
pop ix
|
|
ret
|
|
|
|
; For a given symbol name in (HL), find it in the appropriate symbol register
|
|
; and return its value in DE. If (HL) is a local label, the local register is
|
|
; searched. Otherwise, the global one. It is assumed that this routine is
|
|
; always called when the global registry is selected. Therefore, we always
|
|
; reselect it afterwards.
|
|
symFindVal:
|
|
call symIsLabelLocal
|
|
jp nz, .notLocal
|
|
call symSelectLocalRegistry
|
|
.notLocal:
|
|
call symFind
|
|
jr nz, .end
|
|
; Found! let's fetch value
|
|
; Return value that (SYM_CTX_PTR) is pointing at in DE.
|
|
ld de, (SYM_CTX_PTR)
|
|
call intoDE
|
|
.end:
|
|
jp symSelectGlobalRegistry
|