311d04e9aa
I'm about to split the global registry in two (labels and consts) and the previous state of registry selection made things murky. Now it's much better.
312 lines
8.0 KiB
NASM
312 lines
8.0 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 active registry
|
|
.equ SYM_CTX SYM_LOC_NAMES+SYM_LOC_BUFSIZE
|
|
|
|
; Pointer, in the value list, to the result of the last _symFind
|
|
.equ SYM_CTX_PTR SYM_CTX+2
|
|
.equ SYM_RAMEND SYM_CTX_PTR+2
|
|
|
|
; *** Registries ***
|
|
; A symbol registry is a 6 bytes record with points to names and values of
|
|
; one of the register.
|
|
; It's 3 pointers: names, names end, values
|
|
|
|
SYM_GLOBAL_REGISTRY:
|
|
.dw SYM_NAMES, SYM_NAMES+SYM_BUFSIZE, SYM_VALUES
|
|
|
|
SYM_LOCAL_REGISTRY:
|
|
.dw SYM_LOC_NAMES, SYM_LOC_NAMES+SYM_LOC_BUFSIZE, SYM_LOC_VALUES
|
|
; *** Code ***
|
|
|
|
; Assuming that HL points in to a symbol name list, advance HL to the beginning
|
|
; of the next symbol name 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 hl
|
|
ld hl, SYM_GLOBAL_REGISTRY
|
|
ld (SYM_CTX), hl
|
|
pop hl
|
|
ret
|
|
|
|
symSelectLocalRegistry:
|
|
push hl
|
|
ld hl, SYM_LOCAL_REGISTRY
|
|
ld (SYM_CTX), hl
|
|
pop hl
|
|
ret
|
|
|
|
; Sets Z according to whether label in (HL) is local (starts with a dot)
|
|
symIsLabelLocal:
|
|
ld a, '.'
|
|
cp (hl)
|
|
ret
|
|
|
|
; Given a registry in (IX), place HL at the end of its names that is, at the
|
|
; point where we have two consecutive null chars and DE at the corresponding
|
|
; position in its values.
|
|
; If we're within bounds, Z is set, otherwise unset.
|
|
_symNamesEnd:
|
|
push iy
|
|
push bc
|
|
|
|
; IY --> values
|
|
ld l, (ix+4)
|
|
ld h, (ix+5)
|
|
push hl \ pop iy
|
|
; HL --> names
|
|
ld l, (ix)
|
|
ld h, (ix+1)
|
|
; DE --> names end
|
|
ld e, (ix+2)
|
|
ld d, (ix+3)
|
|
.loop:
|
|
call _symNext
|
|
jr nz, .success ; We've reached the end of the chain.
|
|
inc iy
|
|
inc iy
|
|
; Are we out of bounds name-wise?
|
|
call cpHLDE
|
|
jr nc, .outOfBounds ; HL >= DE
|
|
; are we out of bounds value-wise? check if IY == (IX)'s names
|
|
; Is is assumed that values are placed right before names
|
|
push hl
|
|
push iy \ pop bc
|
|
ld l, (ix)
|
|
ld h, (ix+1)
|
|
sbc hl, bc
|
|
pop hl
|
|
jr z, .outOfBounds ; IY == (IX)'s names
|
|
jr .loop
|
|
.outOfBounds:
|
|
call unsetZ
|
|
jr .end
|
|
.success:
|
|
push iy \ pop de ; our values pos goes in DE
|
|
cp a ; ensure Z
|
|
.end:
|
|
pop bc
|
|
pop iy
|
|
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:
|
|
push ix ; --> lvl 1
|
|
ld ix, (SYM_CTX)
|
|
call _symFind
|
|
jr z, .alreadyThere
|
|
|
|
push hl ; --> lvl 2. it's the symbol to add
|
|
push de ; --> lvl 3. 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 ; --> lvl 4
|
|
push de ; --> lvl 5
|
|
ld e, (ix+2)
|
|
ld d, (ix+3)
|
|
; DE --> names end
|
|
ld a, c
|
|
call addHL
|
|
call cpHLDE
|
|
pop de ; <-- lvl 5
|
|
pop hl ; <-- lvl 4
|
|
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 ; <-- lvl 3. value to register
|
|
call writeHLinDE ; write value where it goes.
|
|
|
|
; Good! now, the string.
|
|
pop hl ; <-- lvl 2. 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
|
|
|
|
pop ix ; <-- lvl 1
|
|
cp a ; ensure Z
|
|
ret
|
|
|
|
.outOfMemory:
|
|
ld a, ERR_OOM
|
|
call unsetZ
|
|
pop de ; <-- lvl 3
|
|
pop hl ; <-- lvl 2
|
|
pop ix ; <-- lvl 1
|
|
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.
|
|
|
|
; Let's pop our lvl 1 IX now, we don't need it any more.
|
|
pop ix ; <-- lvl 1
|
|
|
|
call zasmIsFirstPass
|
|
jr z, .duplicateError
|
|
; Second pass. Don't error out, just update value
|
|
push hl ; --> lvl 1
|
|
ld hl, (SYM_CTX_PTR)
|
|
ex de, hl
|
|
call writeHLinDE
|
|
pop hl ; <-- lvl 1
|
|
cp a ; ensure Z
|
|
ret
|
|
.duplicateError:
|
|
ld a, ERR_DUPSYM
|
|
jp unsetZ ; return
|
|
|
|
; Assuming that IX points to a register context, find name HL in its names and
|
|
; make the context pointer point to the corresponding entry in its values.
|
|
; If we find something, Z is set, otherwise unset.
|
|
_symFind:
|
|
push iy
|
|
push hl
|
|
push de
|
|
|
|
ex de, hl ; it's easier if HL is haystack and DE is
|
|
; needle.
|
|
; IY --> values
|
|
ld l, (ix+4)
|
|
ld h, (ix+5)
|
|
push hl \ pop iy
|
|
; HL --> names
|
|
ld l, (ix)
|
|
ld h, (ix+1)
|
|
.loop:
|
|
call strcmp
|
|
jr z, .match
|
|
; ok, next!
|
|
call _symNext
|
|
jr nz, .nomatch ; end of the chain, nothing found
|
|
inc iy
|
|
inc iy
|
|
jr .loop
|
|
.nomatch:
|
|
call unsetZ
|
|
jr .end
|
|
.match:
|
|
push iy \ pop hl
|
|
ld (SYM_CTX_PTR), hl
|
|
cp a ; ensure Z
|
|
.end:
|
|
pop de
|
|
pop hl
|
|
pop iy
|
|
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:
|
|
push ix
|
|
ld ix, SYM_GLOBAL_REGISTRY
|
|
call symIsLabelLocal
|
|
jp nz, .notLocal
|
|
ld ix, SYM_LOCAL_REGISTRY
|
|
.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:
|
|
pop ix
|
|
ret
|