; *** Variables ***

; A bool flag indicating that we're on first pass. When we are, we don't care
; about actual output, but only about the length of each upcode. This means
; that when we parse instructions and directive that error out because of a
; missing symbol, we don't error out and just write down a dummy value.
equ     ZASM_FIRST_PASS         ZASM_RAMSTART
; whether we're in "local pass", that is, in local label scanning mode. During
; this special pass, ZASM_FIRST_PASS will also be set so that the rest of the
; code behaves as is we were in the first pass.
equ     ZASM_LOCAL_PASS         @+1
; What IO_PC was when we started our context
equ     ZASM_CTX_PC             @+1
; current ".org" offset, that is, what we must offset all our label by.
equ     ZASM_ORG                @+2
equ     ZASM_RAMEND             @+2

; Takes 2 byte arguments, blkdev in and blkdev out, expressed as IDs.
; Can optionally take a 3rd argument which is the high byte of the initial
; .org. For example, passing 0x42 to this 3rd arg is the equivalent of beginning
; the unit with ".org 0x4200".
; Read file through blkdev in and outputs its upcodes through blkdev out.
; HL is set to the last lineno to be read.
; Sets Z on success, unset on error. On error, A contains an error code (ERR_*)
zasmMain:
       ; Parse args in (HL)
       ; blkdev in
       call    parseHexadecimal        ; --> DE
       jr      nz, .badargs
       ld      a, e
       ld      de, IO_IN_BLK
       call    blkSel

       ; blkdev in
       call    rdWS
       jr      nz, .badargs
       call    parseHexadecimal        ; --> DE
       jr      nz, .badargs
       ld      a, e
       ld      de, IO_OUT_BLK
       call    blkSel

       ; .org high byte
       ld      e, 0                    ; in case we .skipOrgSet
       call    rdWS
       jr      nz, .skipOrgSet         ; no org argument
       call    parseHexadecimal        ; --> DE
       jr      nz, .badargs

skipOrgSet:
       ; Init .org with value of E
       ; Save in "@" too
       ld      a, e
       ld      (ZASM_ORG+1), a         ; high byte of .org
       ld      (DIREC_LASTVAL+1), a
       xor     a
       ld      (ZASM_ORG), a           ; low byte zero in all cases
       ld      (DIREC_LASTVAL), a

       ; And then the rest.
       ld      (ZASM_LOCAL_PASS), a
       call    ioInit
       call    symInit

       ; First pass
       ld      hl, .sFirstPass
       call    ioPrintLN
       ld      a, 1
       ld      (ZASM_FIRST_PASS), a
       call    zasmParseFile
       jr      nz, .end
       ; Second pass
       ld      hl, .sSecondPass
       call    ioPrintLN
       xor     a
       ld      (ZASM_FIRST_PASS), a
       ; before parsing the file for the second pass, let's clear the const
       ; registry. See comment in handleEQU.
       ld      ix, SYM_CONST_REGISTRY
       call    symClear
       call    zasmParseFile
end:
       jp      ioLineNo                ; --> HL, --> DE, returns

badargs:
       ; bad args
       ld      a, SHELL_ERR_BAD_ARGS
       ret

sFirstPass:
       .db     "First pass", 0
sSecondPass:
       .db     "Second pass", 0

; Sets Z according to whether we're in first pass.
zasmIsFirstPass:
       ld      a, (ZASM_FIRST_PASS)
       cp      1
       ret

; Sets Z according to whether we're in local pass.
zasmIsLocalPass:
       ld      a, (ZASM_LOCAL_PASS)
       cp      1
       ret

; Set ZASM_ORG to specified number in HL
zasmSetOrg:
       ld      (ZASM_ORG), hl
       ret

; Return current PC (properly .org offsetted) in HL
zasmGetPC:
       push    de
       ld      hl, (ZASM_ORG)
       ld      de, (IO_PC)
       add     hl, de
       pop     de
       ret

; Repeatedly reads lines from IO, assemble them and spit the binary code in
; IO. Z is set on success, unset on error. DE contains the last line number to
; be read (first line is 1).
zasmParseFile:
       call    ioRewind
loop:
       call    parseLine
       ret     nz              ; error
       ld      a, b            ; TOK_*
       cp      TOK_EOF
       jr      z, .eof
       jr      .loop
eof:
       call    zasmIsLocalPass
       jr      nz, .end        ; EOF and not local pass
       ; we're in local pass and EOF. Unwind this
       call    _endLocalPass
       jr      .loop
end:
       cp      a               ; ensure Z
       ret

; Parse next token and accompanying args (when relevant) in I/O, write the
; resulting opcode(s) through ioPutB and increases (IO_PC) by the number of
; bytes written. BC is set to the result of the call to tokenize.
; Sets Z if parse was successful, unset if there was an error. EOF is not an
; error. If there is an error, A is set to the corresponding error code (ERR_*).
parseLine:
       call    tokenize
       ld      a, b            ; TOK_*
       cp      TOK_INSTR
       jp      z, _parseInstr
       cp      TOK_DIRECTIVE
       jp      z, _parseDirec
       cp      TOK_LABEL
       jr      z, _parseLabel
       cp      TOK_EOF
       ret     z               ; We're finished, no error.
       ; Bad token
       ld      a, ERR_UNKNOWN
       jp      unsetZ          ; return with Z unset

_parseInstr:
       ld      a, c            ; I_*
       jp      parseInstruction

_parseDirec:
       ld      a, c            ; D_*
       jp      parseDirective

_parseLabel:
       ; The string in (scratchpad) is a label with its trailing ':' removed.
       ld      hl, scratchpad

       call    zasmIsLocalPass
       jr      z, .processLocalPass

       ; Is this a local label? If yes, we don't process it in the context of
       ; parseLine, whether it's first or second pass. Local labels are only
       ; parsed during the Local Pass
       call    symIsLabelLocal
       jr      z, .success             ; local? don't do anything.

       ld      ix, SYM_GLOBAL_REGISTRY
       call    zasmIsFirstPass
       jr      z, .registerLabel       ; When we encounter a label in the first
                                       ; pass, we register it in the symbol
                                       ; list
       ; At this point, we're in second pass, we've encountered a global label
       ; and we'll soon continue processing our file. However, before we do
       ; that, we should process our local labels.
       call    _beginLocalPass
       jr      .success
processLocalPass:
       ld      ix, SYM_LOCAL_REGISTRY
       call    symIsLabelLocal
       jr      z, .registerLabel       ; local label? all good, register it
                                       ; normally
       ; not a local label? Then we need to end local pass
       call    _endLocalPass
       jr      .success
registerLabel:
       push    hl
       call    zasmGetPC
       ex      de, hl
       pop     hl
       call    symRegister
       jr      nz, .error
       ; continue to .success
success:
       xor     a               ; ensure Z
       ret
error:
       call    unsetZ
       ret

_beginLocalPass:
       ; remember were I/O was
       call    ioSavePos
       ; Remember where PC was
       ld      hl, (IO_PC)
       ld      (ZASM_CTX_PC), hl
       ; Fake first pass
       ld      a, 1
       ld      (ZASM_FIRST_PASS), a
       ; Set local pass
       ld      (ZASM_LOCAL_PASS), a
       ; Empty local label registry
       ld      ix, SYM_LOCAL_REGISTRY
       jp      symClear


_endLocalPass:
       ; recall I/O pos
       call    ioRecallPos
       ; recall PC
       ld      hl, (ZASM_CTX_PC)
       ld      (IO_PC), hl
       ; unfake first pass
       xor     a
       ld      (ZASM_FIRST_PASS), a
       ; Unset local pass
       ld      (ZASM_LOCAL_PASS), a
       cp      a               ; ensure Z
       ret