2019-05-10 11:21:08 +10:00
|
|
|
; Manages both constants and labels within a same namespace and registry.
|
2019-05-14 06:53:52 +10:00
|
|
|
;
|
2019-05-16 10:07:21 +10:00
|
|
|
; 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.
|
2019-05-10 11:21:08 +10:00
|
|
|
|
|
|
|
; *** Constants ***
|
2019-05-19 23:54:42 +10:00
|
|
|
; 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
|
2019-05-10 11:21:08 +10:00
|
|
|
|
|
|
|
; 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.
|
2019-05-19 23:54:42 +10:00
|
|
|
.equ SYM_BUFSIZE 0x2000
|
2019-05-16 10:07:21 +10:00
|
|
|
|
|
|
|
; Size of the names buffer for the local context registry
|
|
|
|
.equ SYM_LOC_BUFSIZE 0x200
|
2019-05-10 11:21:08 +10:00
|
|
|
|
|
|
|
; *** Variables ***
|
|
|
|
; Each symbol is mapped to a word value saved here.
|
2019-05-16 10:07:21 +10:00
|
|
|
.equ SYM_VALUES SYM_RAMSTART
|
2019-05-10 11:21:08 +10:00
|
|
|
|
|
|
|
; 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.
|
2019-05-19 08:56:27 +10:00
|
|
|
.equ SYM_NAMES SYM_VALUES+SYM_MAXCOUNT*2
|
2019-05-14 10:23:10 +10:00
|
|
|
|
2019-05-16 10:07:21 +10:00
|
|
|
; Registry for local labels. Wiped out after each context change.
|
|
|
|
.equ SYM_LOC_VALUES SYM_NAMES+SYM_BUFSIZE
|
2019-05-19 23:54:42 +10:00
|
|
|
.equ SYM_LOC_NAMES SYM_LOC_VALUES+SYM_LOC_MAXCOUNT*2
|
2019-05-14 10:23:10 +10:00
|
|
|
|
2019-05-16 04:30:41 +10:00
|
|
|
; Pointer to the currently selected registry
|
2019-05-16 10:07:21 +10:00
|
|
|
.equ SYM_CTX_NAMES SYM_LOC_NAMES+SYM_LOC_BUFSIZE
|
2019-05-16 04:30:41 +10:00
|
|
|
.equ SYM_CTX_NAMESEND SYM_CTX_NAMES+2
|
|
|
|
.equ SYM_CTX_VALUES SYM_CTX_NAMESEND+2
|
2019-05-19 23:54:42 +10:00
|
|
|
; Pointer, in (SYM_CTX_VALUES), to the result of the last symFind
|
|
|
|
.equ SYM_CTX_PTR SYM_CTX_VALUES+2
|
2019-05-16 04:30:41 +10:00
|
|
|
|
2019-05-19 23:54:42 +10:00
|
|
|
.equ SYM_RAMEND SYM_CTX_PTR+2
|
2019-05-10 11:21:08 +10:00
|
|
|
|
|
|
|
; *** Code ***
|
|
|
|
|
2019-05-14 09:47:34 +10:00
|
|
|
; 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.
|
2019-05-17 23:50:11 +10:00
|
|
|
call unsetZ
|
2019-05-14 09:47:34 +10:00
|
|
|
ret
|
|
|
|
.do:
|
|
|
|
; A is already 0
|
2019-05-17 23:50:11 +10:00
|
|
|
call findchar ; find next null char
|
2019-05-14 09:47:34 +10:00
|
|
|
; go to the char after it.
|
|
|
|
inc hl
|
|
|
|
cp a ; ensure Z
|
|
|
|
ret
|
|
|
|
|
2019-05-14 10:23:10 +10:00
|
|
|
symInit:
|
|
|
|
xor a
|
|
|
|
ld (SYM_NAMES), a
|
2019-05-16 10:07:21 +10:00
|
|
|
ld (SYM_LOC_NAMES), a
|
|
|
|
; Continue to symSelectGlobalRegistry
|
|
|
|
|
|
|
|
symSelectGlobalRegistry:
|
|
|
|
push af
|
|
|
|
push hl
|
2019-05-16 04:30:41 +10:00
|
|
|
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
|
2019-05-16 10:07:21 +10:00
|
|
|
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
|
2019-05-14 10:23:10 +10:00
|
|
|
ret
|
|
|
|
|
|
|
|
; Sets Z according to whether label in (HL) is local (starts with a dot)
|
|
|
|
symIsLabelLocal:
|
|
|
|
ld a, '.'
|
|
|
|
cp (hl)
|
|
|
|
ret
|
|
|
|
|
2019-05-19 23:06:24 +10:00
|
|
|
; 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).
|
2019-05-10 11:21:08 +10:00
|
|
|
; If we're within bounds, Z is set, otherwise unset.
|
|
|
|
symNamesEnd:
|
2019-05-19 23:06:24 +10:00
|
|
|
push ix
|
2019-05-19 23:54:42 +10:00
|
|
|
push bc
|
2019-05-10 11:21:08 +10:00
|
|
|
|
2019-05-19 23:06:24 +10:00
|
|
|
ld ix, (SYM_CTX_VALUES)
|
2019-05-16 04:30:41 +10:00
|
|
|
ld hl, (SYM_CTX_NAMES)
|
|
|
|
ld de, (SYM_CTX_NAMESEND)
|
2019-05-10 11:21:08 +10:00
|
|
|
.loop:
|
2019-05-14 09:47:34 +10:00
|
|
|
call _symNext
|
|
|
|
jr nz, .success ; We've reached the end of the chain.
|
2019-05-19 23:06:24 +10:00
|
|
|
inc ix
|
|
|
|
inc ix
|
2019-05-19 23:54:42 +10:00
|
|
|
; Are we out of bounds name-wise?
|
2019-05-10 11:21:08 +10:00
|
|
|
call cpHLDE
|
2019-05-19 23:54:42 +10:00
|
|
|
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:
|
2019-05-17 23:50:11 +10:00
|
|
|
call unsetZ
|
2019-05-10 11:21:08 +10:00
|
|
|
jr .end
|
|
|
|
.success:
|
2019-05-19 23:06:24 +10:00
|
|
|
push ix \ pop de ; our values pos goes in DE
|
2019-05-10 11:21:08 +10:00
|
|
|
cp a ; ensure Z
|
|
|
|
.end:
|
2019-05-19 23:54:42 +10:00
|
|
|
pop bc
|
2019-05-19 23:06:24 +10:00
|
|
|
pop ix
|
2019-05-10 11:21:08 +10:00
|
|
|
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
|
2019-05-28 07:45:05 +10:00
|
|
|
; A is an error code (ERR_*).
|
2019-05-10 11:21:08 +10:00
|
|
|
symRegister:
|
2019-05-20 03:22:14 +10:00
|
|
|
call symFind
|
|
|
|
jr z, .alreadyThere
|
|
|
|
|
2019-05-19 23:06:24 +10:00
|
|
|
push hl ; will be used during processing. it's the symbol to add
|
|
|
|
push de ; will be used during processing. it's our value.
|
2019-05-10 11:21:08 +10:00
|
|
|
|
2019-05-20 03:22:14 +10:00
|
|
|
|
2019-05-10 11:21:08 +10:00
|
|
|
; First, let's get our strlen
|
|
|
|
call strlen
|
|
|
|
ld c, a ; save that strlen for later
|
|
|
|
|
|
|
|
call symNamesEnd
|
2019-05-28 07:45:05 +10:00
|
|
|
jr nz, .outOfMemory
|
2019-05-19 23:06:24 +10:00
|
|
|
|
2019-05-10 11:21:08 +10:00
|
|
|
; Is our new name going to make us go out of bounds?
|
|
|
|
push hl
|
|
|
|
push de
|
2019-05-16 04:30:41 +10:00
|
|
|
ld de, (SYM_CTX_NAMESEND)
|
2019-05-10 11:21:08 +10:00
|
|
|
ld a, c
|
2019-05-17 23:50:11 +10:00
|
|
|
call addHL
|
2019-05-10 11:21:08 +10:00
|
|
|
call cpHLDE
|
|
|
|
pop de
|
|
|
|
pop hl
|
2019-05-28 07:45:05 +10:00
|
|
|
jr nc, .outOfMemory ; HL >= DE
|
2019-05-10 11:21:08 +10:00
|
|
|
|
2019-05-19 23:06:24 +10:00
|
|
|
; 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
|
2019-05-10 11:21:08 +10:00
|
|
|
; Copy HL into DE until we reach null char
|
2019-05-19 23:06:24 +10:00
|
|
|
call strcpyM
|
2019-05-10 11:21:08 +10:00
|
|
|
|
2019-05-16 10:07:21 +10:00
|
|
|
; We need to add a second null char to indicate the end of the name
|
2019-05-19 23:06:24 +10:00
|
|
|
; list. DE is already correctly placed, A is already zero
|
2019-05-16 10:07:21 +10:00
|
|
|
ld (de), a
|
|
|
|
|
2019-05-28 07:45:05 +10:00
|
|
|
cp a ; ensure Z
|
2019-05-19 23:06:24 +10:00
|
|
|
; Nothing to pop. We've already popped our stack in the lines above.
|
|
|
|
ret
|
2019-05-10 11:21:08 +10:00
|
|
|
|
2019-05-28 07:45:05 +10:00
|
|
|
.outOfMemory:
|
|
|
|
ld a, ERR_OOM
|
2019-05-20 03:22:14 +10:00
|
|
|
call unsetZ
|
2019-05-10 11:21:08 +10:00
|
|
|
pop de
|
|
|
|
pop hl
|
|
|
|
ret
|
|
|
|
|
2019-05-20 03:22:14 +10:00
|
|
|
.alreadyThere:
|
2019-05-28 07:45:05 +10:00
|
|
|
; 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
|
2019-05-20 03:22:14 +10:00
|
|
|
push hl
|
|
|
|
ld hl, (SYM_CTX_PTR)
|
|
|
|
ex de, hl
|
|
|
|
call writeHLinDE
|
|
|
|
pop hl
|
|
|
|
cp a ; ensure Z
|
|
|
|
ret
|
2019-05-28 07:45:05 +10:00
|
|
|
.duplicateError:
|
|
|
|
ld a, ERR_DUPSYM
|
|
|
|
jp unsetZ ; return
|
2019-05-20 03:22:14 +10:00
|
|
|
|
2019-05-19 23:54:42 +10:00
|
|
|
; Find name (HL) in (SYM_CTX_NAMES) and make (SYM_CTX_PTR) point to the
|
|
|
|
; corresponding entry in (SYM_CTX_VALUES).
|
2019-05-10 11:21:08 +10:00
|
|
|
; If we find something, Z is set, otherwise unset.
|
|
|
|
symFind:
|
2019-05-19 23:54:42 +10:00
|
|
|
push ix
|
2019-05-10 11:21:08 +10:00
|
|
|
push hl
|
|
|
|
push de
|
|
|
|
|
2019-05-19 08:56:27 +10:00
|
|
|
ex de, hl ; it's easier if HL is haystack and DE is
|
2019-05-10 11:21:08 +10:00
|
|
|
; needle.
|
2019-05-19 23:54:42 +10:00
|
|
|
ld ix, (SYM_CTX_VALUES)
|
2019-05-16 04:30:41 +10:00
|
|
|
ld hl, (SYM_CTX_NAMES)
|
2019-05-10 11:21:08 +10:00
|
|
|
.loop:
|
2019-05-18 03:14:16 +10:00
|
|
|
call strcmp
|
2019-05-10 11:21:08 +10:00
|
|
|
jr z, .match
|
|
|
|
; ok, next!
|
2019-05-14 09:47:34 +10:00
|
|
|
call _symNext
|
|
|
|
jr nz, .nomatch ; end of the chain, nothing found
|
2019-05-19 23:54:42 +10:00
|
|
|
inc ix
|
|
|
|
inc ix
|
|
|
|
jr .loop
|
2019-05-10 11:21:08 +10:00
|
|
|
.nomatch:
|
2019-05-17 23:50:11 +10:00
|
|
|
call unsetZ
|
2019-05-10 11:21:08 +10:00
|
|
|
jr .end
|
|
|
|
.match:
|
2019-05-19 23:54:42 +10:00
|
|
|
ld (SYM_CTX_PTR), ix
|
2019-05-10 11:21:08 +10:00
|
|
|
cp a ; ensure Z
|
|
|
|
.end:
|
|
|
|
pop de
|
2019-05-16 10:07:21 +10:00
|
|
|
pop hl
|
2019-05-19 23:54:42 +10:00
|
|
|
pop ix
|
2019-05-10 11:21:08 +10:00
|
|
|
ret
|
|
|
|
|
2019-07-21 08:07:52 +10:00
|
|
|
; 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.
|
2019-05-19 23:54:42 +10:00
|
|
|
ld de, (SYM_CTX_PTR)
|
2019-07-21 08:07:52 +10:00
|
|
|
call intoDE
|
|
|
|
.end:
|
|
|
|
jp symSelectGlobalRegistry
|