collapseos/apps/zasm/symbol.asm
Virgil Dupras 311d04e9aa zasm: make symbol registry easily parametrizable
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.
2019-07-22 15:13:09 -04:00

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