; 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

; Select global or local registry according to label name in (HL)
symSelect:
	call	symIsLabelLocal
	jp	z, symSelectLocalRegistry
	jp	symSelectGlobalRegistry

; 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

; Return value that (SYM_CTX_PTR) is pointing at in DE.
symGetVal:
	ld	de, (SYM_CTX_PTR)
	jp	intoDE