; *** CONSTS ***

.equ	D_DB	0x00
.equ	D_DW	0x01
.equ	D_EQU	0x02
.equ	D_ORG	0x03
.equ	D_FIL	0x04
.equ	D_OUT	0x05
.equ	D_INC	0x06
.equ	D_BIN	0x07
.equ	D_BAD	0xff

; *** Variables ***
; Result of the last .equ evaluation. Used for "@" symbol.
.equ	DIREC_LASTVAL		DIREC_RAMSTART
.equ	DIREC_SCRATCHPAD	DIREC_LASTVAL+2
.equ	DIREC_RAMEND		DIREC_SCRATCHPAD+SCRATCHPAD_SIZE
; *** CODE ***

; 3 bytes per row, fill with zero
dirNames:
	.db	"DB", 0
	.db	"DW", 0
	.db	"EQU"
	.db	"ORG"
	.db	"FIL"
	.db	"OUT"
	.db	"INC"
	.db	"BIN"

; This is a list of handlers corresponding to indexes in dirNames
dirHandlers:
	.dw	handleDB
	.dw	handleDW
	.dw	handleEQU
	.dw	handleORG
	.dw	handleFIL
	.dw	handleOUT
	.dw	handleINC
	.dw	handleBIN

handleDB:
	push	de
	push	hl
.loop:
	call	readWord
	jr	nz, .badfmt
	ld	hl, scratchpad
	call	enterDoubleQuotes
	jr	z, .stringLiteral
	call	parseExpr
	jr	nz, .badarg
	ld	a, d
	or	a		; cp 0
	jr	nz, .overflow	; not zero? overflow
	ld	a, e
	call	ioPutB
	jr	nz, .ioError
.stopStrLit:
	call	readComma
	jr	z, .loop
	cp	a		; ensure Z
.end:
	pop	hl
	pop	de
	ret
.ioError:
	ld	a, SHELL_ERR_IO_ERROR
	jr	.error
.badfmt:
	ld	a, ERR_BAD_FMT
	jr	.error
.badarg:
	ld	a, ERR_BAD_ARG
	jr	.error
.overflow:
	ld	a, ERR_OVFL
.error:
	or	a		; unset Z
	jr	.end

.stringLiteral:
	ld	a, (hl)
	inc	hl
	or	a		; when we encounter 0, that was what used to
	jr	z, .stopStrLit	; be our closing quote. Stop.
	; Normal character, output
	call	ioPutB
	jr	nz, .ioError
	jr	.stringLiteral

handleDW:
	push	de
	push	hl
.loop:
	call	readWord
	jr	nz, .badfmt
	ld	hl, scratchpad
	call	parseExpr
	jr	nz, .badarg
	ld	a, e
	call	ioPutB
	jr	nz, .ioError
	ld	a, d
	call	ioPutB
	jr	nz, .ioError
	call	readComma
	jr	z, .loop
	cp	a		; ensure Z
.end:
	pop	hl
	pop	de
	ret
.ioError:
	ld	a, SHELL_ERR_IO_ERROR
	jr	.error
.badfmt:
	ld	a, ERR_BAD_FMT
	jr	.error
.badarg:
	ld	a, ERR_BAD_ARG
.error:
	or	a		; unset Z
	jr	.end

handleEQU:
	call	zasmIsLocalPass	; Are we in local pass? Then ignore all .equ.
	jr	z, .skip		; they mess up duplicate symbol detection.
	; We register constants on both first and second pass for one little
	; reason: .org. Normally, we'd register constants on second pass only
	; so that we have values for forward label references, but we need .org
	; to be effective during the first pass and .org needs to support
	; expressions. So, we double-parse .equ, clearing the const registry
	; before the second pass.
	push	hl
	push	de
	push	bc
	; Read our constant name
	call	readWord
	jr	nz, .badfmt
	; We can't register our symbol yet: we don't have our value!
	; Let's copy it over.
	ld	de, DIREC_SCRATCHPAD
	ld	bc, SCRATCHPAD_SIZE
	ldir

	; Now, read the value associated to it
	call	readWord
	jr	nz, .badfmt
	ld	hl, scratchpad
	call	parseExpr
	jr	nz, .badarg
	ld	hl, DIREC_SCRATCHPAD
	; Save value in "@" special variable
	ld	(DIREC_LASTVAL), de
	call	symRegisterConst	; A and Z set
	jr	z, .end			; success
	; register ended up in error. We need to figure which error. If it's
	; a duplicate error, we ignore it and return success because, as per
	; ".equ" policy, it's fine to define the same const twice. The first
	; value has precedence.
	cp	ERR_DUPSYM
	; whatever the value of Z, it's the good one, return
	jr	.end
.badfmt:
	ld	a, ERR_BAD_FMT
	jr	.error
.badarg:
	ld	a, ERR_BAD_ARG
.error:
	call	unsetZ
.end:
	pop	bc
	pop	de
	pop	hl
	ret
.skip:
	; consume args and return
	call	readWord
	jp	readWord

handleORG:
	push	de
	call	readWord
	jr	nz, .badfmt
	call	parseExpr
	jr	nz, .badarg
	ex	de, hl
	ld	(DIREC_LASTVAL), hl
	call	zasmSetOrg
	cp	a		; ensure Z
.end:
	pop	de
	ret
.badfmt:
	ld	a, ERR_BAD_FMT
	jr	.error
.badarg:
	ld	a, ERR_BAD_ARG
.error:
	or	a		; unset Z
	jr	.end

handleFIL:
	call	readWord
	jr	nz, .badfmt
	call	parseExpr
	jr	nz, .badarg
	ld	a, d
	cp	0xd0
	jr	nc, .overflow
.loop:
	ld	a, d
	or	e
	jr	z, .loopend
	xor	a
	call	ioPutB
	jr	nz, .ioError
	dec	de
	jr	.loop
.loopend:
	cp	a	; ensure Z
	ret
.ioError:
	ld	a, SHELL_ERR_IO_ERROR
	jp	unsetZ
.badfmt:
	ld	a, ERR_BAD_FMT
	jp	unsetZ
.badarg:
	ld	a, ERR_BAD_ARG
	jp	unsetZ
.overflow:
	ld	a, ERR_OVFL
	jp	unsetZ

handleOUT:
	push	de
	push	hl
	; Read our expression
	call	readWord
	jr	nz, .badfmt
	call	zasmIsFirstPass		; No .out during first pass
	jr	z, .end
	ld	hl, scratchpad
	call	parseExpr
	jr	nz, .badarg
	ld	a, d
	out	(ZASM_DEBUG_PORT), a
	ld	a, e
	out	(ZASM_DEBUG_PORT), a
	jr	.end
.badfmt:
	ld	a, ERR_BAD_FMT
	jr	.error
.badarg:
	ld	a, ERR_BAD_ARG
.error:
	or	a		; unset Z
.end:
	pop	hl
	pop	de
	ret

handleINC:
	call	readWord
	jr	nz, .badfmt
	; HL points to scratchpad
	call	enterDoubleQuotes
	jr	nz, .badfmt
	call	ioOpenInclude
	jr	nz, .badfn
	cp	a		; ensure Z
	ret
.badfmt:
	ld	a, ERR_BAD_FMT
	jr	.error
.badfn:
	ld	a, ERR_FILENOTFOUND
.error:
	call	unsetZ
	ret

handleBIN:
	call	readWord
	jr	nz, .badfmt
	; HL points to scratchpad
	call	enterDoubleQuotes
	jr	nz, .badfmt
	call	ioSpitBin
	jr	nz, .badfn
	cp	a		; ensure Z
	ret
.badfmt:
	ld	a, ERR_BAD_FMT
	jr	.error
.badfn:
	ld	a, ERR_FILENOTFOUND
.error:
	call	unsetZ
	ret

; Reads string in (HL) and returns the corresponding ID (D_*) in A. Sets Z if
; there's a match.
getDirectiveID:
	ld	a, (hl)
	cp	'.'
	ret	nz
	push	hl
	push	bc
	push	de
	inc	hl
	ld	b, D_BIN+1		; D_BIN is last
	ld	c, 3
	ld	de, dirNames
	call	findStringInList
	pop	de
	pop	bc
	pop	hl
	ret

; Parse directive specified in A (D_* const) with args in I/O and act in
; an appropriate manner. If the directive results in writing data at its
; current location, that data is directly written through ioPutB.
; Each directive has the same return value pattern: Z on success, not-Z on
; error, A contains the error number (ERR_*).
parseDirective:
	push	de
	; double A to have a proper offset in dirHandlers
	add	a, a
	ld	de, dirHandlers
	call	addDE
	call	intoDE
	push	de \ pop ix
	pop	de
	jp	(ix)