zasm: make symbol registry a bit more straightforward

Instead of strings of variable length driving the iteration of the
registry, we do so through records that keep track of lengths and
counts.
This commit is contained in:
Virgil Dupras 2019-07-23 15:21:42 -04:00
parent 02c7eb0161
commit 1dec33e02a
4 changed files with 135 additions and 167 deletions

View File

@ -12,79 +12,67 @@
; *** Constants *** ; *** Constants ***
; Maximum number of symbols we can have in the global and consts registry ; Maximum number of symbols we can have in the global and consts registry
.equ SYM_MAXCOUNT 0x100 .equ SYM_MAXCOUNT 0xff
; Maximum number of symbols we can have in the local registry ; Maximum number of symbols we can have in the local registry
.equ SYM_LOC_MAXCOUNT 0x40 .equ SYM_LOC_MAXCOUNT 0x40
; Size of each record in registry
.equ SYM_RECSIZE 3
; Size of the symbol name buffer size. This is a pool. There is no maximum name ; 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. ; length for a single symbol, just a maximum size for the whole pool.
; Global labels and consts have the same buf size ; Global labels and consts have the same buf size
.equ SYM_BUFSIZE 0x1000 .equ SYM_BUFSIZE 0x1000
.equ SYM_REGSIZE SYM_BUFSIZE+1+SYM_MAXCOUNT*SYM_RECSIZE
; Size of the names buffer for the local context registry ; Size of the names buffer for the local context registry
.equ SYM_LOC_BUFSIZE 0x200 .equ SYM_LOC_BUFSIZE 0x200
.equ SYM_LOC_REGSIZE SYM_LOC_BUFSIZE+1+SYM_LOC_MAXCOUNT*SYM_RECSIZE
; *** Variables *** ; *** Variables ***
; A registry has three parts: record count (byte) record list and names pool.
; A record is a 3 bytes structure:
; 1b - name length
; 2b - value associated to symbol
;
; We know we're at the end of the record list when we hit a 0-length one.
;
; The names pool is a list of strings, not null-terminated, associated with
; the value.
;
; It is assumed that the registry is aligned in memory in that order:
; names pool, rec count, reclist
; Global labels registry ; Global labels registry
.equ SYM_GLOB_REG SYM_RAMSTART
; Each symbol is mapped to a word value saved here. .equ SYM_LOC_REG SYM_GLOB_REG+SYM_REGSIZE
.equ SYM_GLOB_VALUES SYM_RAMSTART .equ SYM_CONST_REG SYM_LOC_REG+SYM_LOC_REGSIZE
.equ SYM_RAMEND SYM_CONST_REG+SYM_REGSIZE
; 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_GLOB_VALUES.
.equ SYM_GLOB_NAMES SYM_GLOB_VALUES+SYM_MAXCOUNT*2
; Registry for local labels. Wiped out after each context change.
.equ SYM_LOC_VALUES SYM_GLOB_NAMES+SYM_BUFSIZE
.equ SYM_LOC_NAMES SYM_LOC_VALUES+SYM_LOC_MAXCOUNT*2
; Registry for constants
.equ SYM_CONST_VALUES SYM_LOC_NAMES+SYM_LOC_BUFSIZE
.equ SYM_CONST_NAMES SYM_CONST_VALUES+SYM_MAXCOUNT*2
.equ SYM_RAMEND SYM_CONST_NAMES+SYM_BUFSIZE
; *** Registries *** ; *** Registries ***
; A symbol registry is a 6 bytes record with points to names and values of ; A symbol registry is a 5 bytes record with points to the name pool then the
; one of the register. ; records list of the register and then the max record count.
; It's 3 pointers: names, names end, values
SYM_GLOBAL_REGISTRY: SYM_GLOBAL_REGISTRY:
.dw SYM_GLOB_NAMES, SYM_GLOB_NAMES+SYM_BUFSIZE, SYM_GLOB_VALUES .dw SYM_GLOB_REG, SYM_GLOB_REG+SYM_BUFSIZE
.db SYM_MAXCOUNT
SYM_LOCAL_REGISTRY: SYM_LOCAL_REGISTRY:
.dw SYM_LOC_NAMES, SYM_LOC_NAMES+SYM_LOC_BUFSIZE, SYM_LOC_VALUES .dw SYM_LOC_REG, SYM_LOC_REG+SYM_LOC_BUFSIZE
.db SYM_LOC_MAXCOUNT
SYM_CONST_REGISTRY: SYM_CONST_REGISTRY:
.dw SYM_CONST_NAMES, SYM_CONST_NAMES+SYM_BUFSIZE, SYM_CONST_VALUES .dw SYM_CONST_REG, SYM_CONST_REG+SYM_BUFSIZE
.db SYM_MAXCOUNT
; *** Code *** ; *** 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: symInit:
xor a ld ix, SYM_GLOBAL_REGISTRY
ld (SYM_GLOB_NAMES), a call symClear
ld (SYM_LOC_NAMES), a ld ix, SYM_LOCAL_REGISTRY
ld (SYM_CONST_NAMES), a call symClear
; Continue to symSelectGlobalRegistry ld ix, SYM_CONST_REGISTRY
jp symClear
; Sets Z according to whether label in (HL) is local (starts with a dot) ; Sets Z according to whether label in (HL) is local (starts with a dot)
symIsLabelLocal: symIsLabelLocal:
@ -92,53 +80,6 @@ symIsLabelLocal:
cp (hl) cp (hl)
ret 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
symRegisterGlobal: symRegisterGlobal:
push ix push ix
ld ix, SYM_GLOBAL_REGISTRY ld ix, SYM_GLOBAL_REGISTRY
@ -160,110 +101,115 @@ symRegisterConst:
pop ix pop ix
ret ret
; Register label in (HL) (minus the ending ":") into the symbol registry and ; Register label in (HL) (minus the ending ":") into the symbol registry in IX
; set its value in that registry to DE. ; and set its value in that registry to the value specified in DE.
; If successful, Z is set and A is the symbol index. Otherwise, Z is unset and ; If successful, Z is set. Otherwise, Z is unset and A is an error code (ERR_*).
; A is an error code (ERR_*).
symRegister: symRegister:
push hl ; --> lvl 1. it's the symbol to add push hl ; --> lvl 1. it's the symbol to add
push de ; --> lvl 2. it's our value.
call _symFind
jr z, .duplicateError
call _symIsFull
jr z, .outOfMemory
; First, let's get our strlen ; First, let's get our strlen
call strlen call strlen
ld c, a ; save that strlen for later ld c, a ; save that strlen for later
call _symNamesEnd call _symFind
jr nz, .outOfMemory jr z, .duplicateError
; Is our new name going to make us go out of bounds? ; Is our new name going to make us go out of bounds?
push hl ; --> lvl 3 push hl ; --> lvl 2
push de ; --> lvl 4 push de ; --> lvl 3
ld e, (ix+2) ld e, (ix+2) ; DE --> pointer to record list, which is also
ld d, (ix+3) ld d, (ix+3) ; the end of names pool
; DE --> names end ; DE --> names end
ld a, c ld a, c
call addHL call addHL
call cpHLDE call cpHLDE
pop de ; <-- lvl 4 pop de ; <-- lvl 3
pop hl ; <-- lvl 3 pop hl ; <-- lvl 2
jr nc, .outOfMemory ; HL >= DE jr nc, .outOfMemory ; HL >= DE
; Success. At this point, we have: ; Success. At this point, we have:
; HL -> where we want to add the string ; HL -> where we want to add the string
; DE -> where the value goes ; IY -> target record where the value goes
; SP -> value to register ; DE -> value to register
; SP+2 -> string to register ; SP -> string to register
; Let's start with the value. ; Let's start with the record
push hl \ pop ix ; save HL for later ld (iy), c ; strlen
pop hl ; <-- lvl 2. value to register ld (iy+1), e
call writeHLinDE ; write value where it goes. ld (iy+2), d
; Good! now, the string. ; Good! now, the string. Destination is in HL, source is in SP
ex de, hl ; dest is in DE
pop hl ; <-- lvl 1. string to register pop hl ; <-- lvl 1. string to register
push ix \ pop de ; string destination
; Copy HL into DE until we reach null char ; Copy HL into DE until we reach null char
call strcpyM call strcpyM
; We need to add a second null char to indicate the end of the name ; Last thing: increase record count
; list. DE is already correctly placed, A is already zero ld l, (ix+2)
ld (de), a ld h, (ix+3)
inc (hl)
cp a ; ensure Z xor a ; sets Z
ret ret
.outOfMemory: .outOfMemory:
ld a, ERR_OOM
call unsetZ
pop de ; <-- lvl 2
pop hl ; <-- lvl 1 pop hl ; <-- lvl 1
ret ld a, ERR_OOM
jp unsetZ
.duplicateError: .duplicateError:
pop de ; <-- lvl 2
pop hl ; <-- lvl 1 pop hl ; <-- lvl 1
ld a, ERR_DUPSYM ld a, ERR_DUPSYM
jp unsetZ ; return jp unsetZ ; return
; Assuming that IX points to a registry, find name HL in its names and make DE ; Assuming that IX points to a registry, find name HL in its names and make IY
; point to the corresponding entry in its values. ; point to the corresponding record. If it doesn't find anything, IY will
; conveniently point to the next record after the last, and HL to the next
; name insertion point.
; If we find something, Z is set, otherwise unset. ; If we find something, Z is set, otherwise unset.
_symFind: _symFind:
push iy push de
push hl push bc
ex de, hl ; it's easier if HL is haystack and DE is call strlen
; needle. ld c, a ; save strlen
; IY --> values
ld l, (ix+4) ex de, hl ; easier if needle is in DE
ld h, (ix+5)
; IY --> records
ld l, (ix+2)
ld h, (ix+3)
; first byte is count
ld b, (hl)
inc hl ; first record
push hl \ pop iy push hl \ pop iy
; HL --> names ; HL --> names
ld l, (ix) ld l, (ix)
ld h, (ix+1) ld h, (ix+1)
; do we have an empty reclist?
xor a
cp b
jr z, .nothing ; zero count? nothing
.loop: .loop:
call strcmp ld a, (iy) ; name len
jr z, .match cp c
jr nz, .skip ; different strlen, can't possibly match. skip
call strncmp
jr z, .end ; match! Z already set, IY and HL placed.
.skip:
; ok, next! ; ok, next!
call _symNext ld a, (iy) ; name len again
jr nz, .nomatch ; end of the chain, nothing found call addHL ; advance HL by A chars
inc iy inc iy \ inc iy \ inc iy
inc iy djnz .loop
jr .loop ; end of the chain, nothing found
.nomatch: .nothing:
call unsetZ call unsetZ
jr .end
.match:
push iy \ pop de
; DE has our result
cp a ; ensure Z
.end: .end:
pop hl pop bc
pop iy pop de
ret ret
; For a given symbol name in (HL), find it in the appropriate symbol register ; For a given symbol name in (HL), find it in the appropriate symbol register
@ -276,16 +222,18 @@ symFindVal:
call symIsLabelLocal call symIsLabelLocal
jr z, .local jr z, .local
; global. Let's try labels first, then consts ; global. Let's try labels first, then consts
push hl ; --> lvl 1. we'll need it again if not found.
ld ix, SYM_GLOBAL_REGISTRY ld ix, SYM_GLOBAL_REGISTRY
call _symFind call _symFind
pop hl ; <-- lvl 1
jr z, .found jr z, .found
ld ix, SYM_CONST_REGISTRY ld ix, SYM_CONST_REGISTRY
call _symFind call _symFind
jr nz, .end jr nz, .end
.found: .found:
; Found! let's fetch value ; Found! let's fetch value
; DE is pointing to our result ld e, (iy+1)
call intoDE ld d, (iy+2)
jr .end jr .end
.local: .local:
ld ix, SYM_LOCAL_REGISTRY ld ix, SYM_LOCAL_REGISTRY
@ -300,10 +248,24 @@ symFindVal:
symClear: symClear:
push af push af
push hl push hl
ld l, (ix) ld l, (ix+2)
ld h, (ix+1) ld h, (ix+3)
; HL --> reclist count
xor a xor a
ld (hl), a ld (hl), a
pop hl pop hl
pop af pop af
ret ret
; Returns whether register in IX has reached its capacity.
; Sets Z if full, unset if not.
_symIsFull:
push hl
ld l, (ix+2)
ld h, (ix+3)
ld l, (hl) ; record count
ld a, (ix+4) ; max record count
cp l
pop hl
ret

View File

@ -24,6 +24,12 @@ subDEFromHL:
pop af pop af
ret ret
; make Z the opposite of what it is now
toggleZ:
jp z, unsetZ
cp a
ret
; Returns length of string at (HL) in A. ; Returns length of string at (HL) in A.
; Doesn't include null termination. ; Doesn't include null termination.
strlen: strlen:

Binary file not shown.

View File

@ -8,18 +8,14 @@ jp test
.equ SYM_RAMSTART RAMSTART .equ SYM_RAMSTART RAMSTART
#include "zasm/symbol.asm" #include "zasm/symbol.asm"
; Pretend that we aren't in first pass
zasmIsFirstPass:
jp unsetZ
testNum: .db 1 testNum: .db 1
sFOO: .db "FOO", 0 sFOO: .db "FOO", 0
sFOOBAR: .db "FOOBAR", 0 sFOOBAR: .db "FOOBAR", 0
sOther: .db "Other", 0
test: test:
ld hl, 0xffff ld sp, 0xffff
ld sp, hl
; Check that we compare whole strings (a prefix will not match a longer ; Check that we compare whole strings (a prefix will not match a longer
; string). ; string).
@ -44,14 +40,18 @@ test:
jp nz, fail jp nz, fail
call nexttest call nexttest
ld hl, sOther
call symFindVal
jp z, fail
call nexttest
; success ; success
xor a xor a
halt halt
nexttest: nexttest:
ld a, (testNum) ld hl, testNum
inc a inc (hl)
ld (testNum), a
ret ret
fail: fail: