; *** 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)