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